The OSGi programming model is based on the collaboration of standard and custom components. In such a model there is no central authority that has global knowledge of the complete application. Though this lack of authority can significantly increase reusability (and robustness) there are times when the activities of the collaborators must be coordinated. For example, a service that is repeatedly called in a task could optimize performance by caching intermediate results until it knew the task was ended.
To know when a task involving multiple collaborators has ended is the primary purpose of the Coordinator service specification. The Coordinator service provides a rendezvous for an initiator to create a Coordination where collaborators can decide to participate. When the Coordination has ended, all participants are informed.
This Coordinator service provides an explicit Coordination model, the Coordination is explicitly passed as a parameter, and an implicit model where the Coordination is associated with the current thread. Implicit Coordinations can be nested.
Coordinators share the coordination aspects of the resource model of transactions. However, the model is much lighter-weight because it does not support any of the ACID properties.
-
Coordination - Provide a solution to allow multiple collaborators to coordinate the outcome of a task initiated by an initiator.
-
Initiator - An initiator must be able to initiate a coordination and control the final outcome.
-
Participants - Participants in the task must be informed when the coordination has ended or failed as well as being able to terminate the Coordination.
-
Time-out - A Coordination should fail after a given time-out.
-
Blocking - Provide support for blocking and serializing access to Participants.
-
Nesting - It must be possible to nest Coordinations.
-
Per Thread Model - Provide a per-thread current Coordination model.
-
Variables - Provide a variable space per Coordination
-
Coordinator - A service that can create and enumerate Coordinations.
-
Coordination - Represents the ongoing Coordination.
-
Initiator - The party that initiates a Coordination.
-
Participant - A party that wants to be informed of the outcome of a Coordination.
-
Collaborator - Either a participant or initiator.
This section is an introduction in the usage of the Coordinator service. It is not the formal specification, the normative part starts at Coordinator Service. This section leaves out some of the details for clarity.
The Coordinator service provides a mechanism for multiple parties to collaborate on a common task without a priori knowledge of who will collaborate in that task. A collaborator can participate by adding a Participant to the Coordination. The Coordination will notify the Participants when the coordination is ended or when it is failed.
Each Coordination has an initiator that
creates the Coordination
object through the Coordinator
service. The initiator can then push this object on a thread-local stack
to make it an implicit Coordination or it can pass this object around as
a parameter for explicit Coordinations.
Collaborators can then use the current Coordination
on the stack or get it from a parameter. Whenever a bundle wants to
participate in the Coordination it adds itself to the Coordination as a
participant. If necessary, a collaborator can initiate a new
Coordination, which could be a nested Coordination for implicit
Coordinations.
A Coordination must be terminated.
Termination is either a normal end when the initiator calls the
end
method or it is failed when the fail
method is called. A Coordination can be failed by any of the
collaborators. A Coordination can also fail independently due to a
time-out or when the initiator releases its
Coordinator service. All participants in the Coordination are informed
in reverse participation order about the outcome in a callback for ended
or failed Coordinations.
A typical action diagram with a successful outcome is depicted in Figure 130.2.
The general pattern for an initiator is to create a Coordination through the Coordinator service, perform the work in a try block, catch any exceptions and fail the Coordination in the catch block, and then ensure ending the Coordination in the finally block. The finally block can cause an exception. This is demonstrated in the following example:
Coordination c = coordinator.create("com.example.work",0);
try {
doWork(c);
} catch( Exception e ) {
c.fail(e);
} finally {
c.end();
}
This deceptively small template is quite robust:
-
If the
doWork
method throws an Exception then the template fails with a Coordination Exception because it is failed in the try block. -
Any exceptions thrown in the try block are automatically causing the Coordination to fail.
-
The Coordination is always terminated and removed from the stack due to the finally block.
-
All failure paths, Coordinations that are failed by any of the collaborators, time-outs, or other problems are handled by the
end
method in the finally block. It will throw a FAILED or PARTIALLY_ENDED Coordination Exception for any of the failures.
The different failure paths and their handling is pictured in Figure 130.3.
The example shows an explicit Coordination because the
create
method is used, implicit Coordinations are used in
Implicit Coordinations. The parameters of the create
method are the name of the Coordination and its time-out. The name is
used for informational purposes as well as security. For security
reasons, the name must follow the same syntax as the Bundle Symbolic
Name. In a secure environment the name can be used to limit
Coordinations to a limited set of bundles. For example, a set of bundles
signed by a specific signer can use names like com.acme.*
that are denied to all other bundles.
The zero time-out specifies that the Coordination will not have a time-out. Otherwise it must be a positive long, indicating the number of milliseconds the Coordination may take. However, implementations should have a configurable time-out to ensure that the system remains alive.
In the doWork
method the real work is done in
conjunction with the collaborators. Explicit Coordinations can be passed
to other threads if needed. Collaborators can decide to add participants
whenever they require a notification when the Coordination has been
terminated. For example, the following code could be called from the
doWork
method:
void foo(Coordination c) {
doPrepare();
c.addParticipant(this);
}
This method does the preparation work but does not finalize it so
that next time it can use some intermediate results. For example, the
prepare
method could cache a connection to a database that
should be reused during the Coordination. The collaborator can assume
that it will be called back on either the failed
or
ended
method. These methods could look like:
public void ended(Coordination c) { doFinish(); }
public void failed(Coordination c) { doFailed(); }
The Coordinator provides the guarantee that this code will always
call the doFinish
method when the Coordination succeeds and
doFailed
method when it failed.
The Participant must be aware that the ended(Coordination) and failed(Coordination) methods can be called on any thread.
If the doWork
method throws an exception it will end
up in the catch block of the initiator. The catch block will then fail
the Coordination by calling the fail
method with the given
exception. If the Coordination was already terminated because something
else already had failed it then the method call is ignored, only the
first fail is used, later fails are ignored.
In all cases, the finally block is executed last. The finally block ends the Coordination. If this coordination was failed then it will throw a Coordination Exception detailing the reason of the failure. Otherwise it will terminate it and notify all the participants.
The Coordination Exception is a Runtime Exception making it unnecessary to declare it.
Explicit Coordinations allow the Coordination objects to be passed
to many different collaborators who can perform the work on different
threads. Each collaborator can fail the Coordination at any moment in
time or the time-out can occur on yet another thread. Participants must
therefore be aware that the callbacks ended
and
failed
can happen on any thread. The following example
shows a typical case where a task is parallelized. If any thread fails
the Coordination, all other threads could be notified before they're
finished.
Executor executor = ...
final CountDownLatch latch = new CountdownLatch(10);
final Coordination c = coordinator.create("parallel", 0);
for ( int i=0; i<10; i++) {
executor.execute(
new Runnable() {
public void run() { baz(c); latch.countDown(); }
});
}
latch.await();
c.end();
The Coordination
object is thread safe so it can be
freely passed around.
An explicit Coordination requires that the Coordination is passed
as a parameter to the doWork
method. The Coordinator also
supports implicit Coordinations. With implicit
Coordinations the Coordinator maintains a thread local stack of
Coordinations where the top of this stack is the
current Coordination for that thread. The usage of
the implicit Coordination is almost identical to the explicit
Coordinations except that all the work occurs on a single thread. The
control flow is almost identical to explicit Coordinations:
Coordination c = coordinator.begin("com.example.work",0);
try {
doWork();
} catch( Exception e ) {
c.fail(e);
} finally {
c.end();
}
See also Figure 130.3. However, in this case the
finally block with the call to the end method is even more important.
With an implicit Coordination the Coordination is put on a thread local
stack in the begin
method and must therefore be popped when
the Coordination is finished. The finally block ensures therefore the
proper cleanup of this thread local stack.
The difference between implicit and explicit Coordinations is that the implicit Coordination is not passed as a parameter, instead, collaborators use the current Coordination. With implicit Coordinations all method invocations in a thread can always access the current Coordination, even if they have many intermediates on the stack. The implicit model allows a collaborator many levels down the stack to detect a current Coordination and register itself without the need to modify all intermediate methods to contain a Coordination parameter. The explicit model has the advantage of explicitness but requires all APIs to be modified to hold the parameter. This model does not support passing the parameter through layers that are not aware of the Coordination. For example, OSGi services in general do not have a Coordination parameter in their methods making the use of explicit Coordinations impossible.
Collaborators can act differently in the presence of a current Coordination. For example, a collaborator can optimize its work flow depending on the presence of a current Coordination.
Coordinator coordinator = ...
void foo() {
doPrepare();
if ( !coordinator.addParticipant(this))
doFinish();
}
The Coordinator service has an addParticipant
method
that makes working with the current Coordination simple. If there is a
current Coordination then the Coordinator service will add the
participant and return true
, otherwise it returns
false
. It is therefore easy to react differently in the
presence of a current Coordination. In the previous example, the
doFinish
method will be called immediately if there was no
current Coordination, otherwise it is delayed until the Coordination
fails or succeeds. The participant callbacks look the same as in the
previous section:
public void ended(Coordination c) { doFinish(); }
public void failed(Coordination c) { doFailed(); }
Though the code looks very similar for the implicit and explicit Coordinations there are some additional rules for implicit Coordinations.
The end
method must be called on the same thread as
the begin
method, trying to end it on another thread
results in a WRONG_THREAD Coordination Exception being thrown.
Even though the end
method must be called on the
initiating thread, the callbacks to the Participants can be done on any
thread as the specification allows the Coordinator to use multiple
threads for all callbacks.
The Coordination is a best effort mechanism to coordinate, not a
transaction model with integrity guarantees. This means that users of
the Coordinator service must understand that there are cases where a
Coordination ends in limbo. This happens when one of the Participants
throws an Exception in the ended
callback. This is similar
to a transactional resource manager failing to commit in a 2-phase
commit after it has voted yes in the prepare phase; a problem that is
the cause of much of the complexity of a transaction manager. The
Coordinator is limited to use cases that do not require full ACID
properties and can therefore be much simpler. However, users of the
Coordinator service must be aware of this limitation.
If a Participant throws an exception in the ended method, the end method that terminated the Coordination must throw a PARTIALLY_ENDED Coordination Exception. It is then up to the initiator to correct the situations. In most cases, this means allowing the exception to be re-thrown and handle the failure at the top level. Handling in those cases usually implies logging and continuing.
The following code shows how the PARTIALLY_ENDED case can be handled more explicitly.
Coordination c = coordinator.begin("work",0);
try {
doWork();
} catch( Excption e ) {
c.fail(e);
} finally {
try {
c.end();
} catch( CoordinationException e ) {
if ( e.getType() == CoordinationException.PARTIALLY_ENDED) {
// limbo!
...
}
}
}
To participate in a Coordination and receive callbacks a
collaborator must add a Participant
object to the
Coordination. The addParticipant(Participant) method blocks if the given
Participant
object is already used in another Coordination.
This blocking facility can be used to implement a number of simple
locking schemes that can simplify maintaining state in a concurrent
environment.
Using the Participant
object as the key for the lock
makes it simple to do course grained locking. For example, a service
implementation could use the service object as a lock, effectively
serializing access to this service when it is used in a Coordination.
Coarse grained locking allows all the state to be maintained in the
coarse object and not having to worry about multiplexing simultaneous
requests. The following code uses the coarse locking pattern because the
collaborator implements the Participant
interface
itself:
public class Collaborator implements Participant{
public void doWork(Coordination coordination ) {
...
coordination.addParticipant(this);
}
public void ended(Coordination c) { ... }
public void failed(Coordination c) { ... }
}
The simplicity of the coarse grained locking is at the expense of lower performance because tasks are serialized even if it would have no contention. Locks can therefore also be made more fine grained, allowing more concurrency. In the extreme case, creating a new object for each participation makes it possible to never lock. For example, the following code never locks because it always creates a new object for the Participant:
public void doWork(Coordination coordination){
final State state = ...
coordination.addParticipant(
new Participant() {
public void ended(Coordination c) { state ... }
public void failed(Coordination c) { state ...}
} ); }
Any collaborator can fail an ongoing Coordination by calling the
fail(Throwable) method, the Throwable parameter must not be
null
. When the Coordination has already terminated then
this is a no-op. The Coordinator service has a convenience method that
fails the current Coordination if present. The fail
methods
return a boolean that is true
when the method call causes
the termination of the Coordination, in all other cases it is
false
.
Failing a Coordination will immediately perform the callbacks and reject any additional Participants by throwing an ALREADY_ENDED Coordination Exception. The asynchronous nature of the fail method implies that it is possible to have been called even before the addParticipant(Participant) method has returned. Anybody that has the Coordination object can check the failed state with the getFailure() method.
In general, the best and most robust strategy to handle failures is to throw an Exception from the collaborator, allowing the initiator to catch the exception and properly fail the Coordination.
The time-out is specified in the Coordinator create(String,long) or begin(String,long) methods. A time-out of zero is indefinite, otherwise the time-out specifies the number of milliseconds the Coordination can take to terminate. A given time-out can be extended with the extendTimeout(long) method. This method will add an additional time-out to the existing deadline if a prior deadline was set. For example, the following code extends the time-out with 5 seconds whenever a message must be sent to a remote address:
Object sendMessage(Message m) {
Coordination c = coordinator.peek();
Address a = m.getDestination();
if ( c != null && a.isRemote() ) {
c.extendTimeout(5000);
}
return sendMessage0(m);
}
Applications should not rely on the exact time-out of the Coordination and only use it as a safety function against deadlocks and hanging collaborators.
When a Coordination is terminated it is not yet completely finished, the callback to the Participants happens after the atomic termination. In certain cases it is necessary to ensure that a method does not progress until all the participants have been notified. It is therefore possible to wait for the Coordination to completely finish with the join(long) method. This method can have a time-out. For example:
void collaborate( final Coordination c ) {
doWork();
Thread t = new Thread() {
public void run(){
try {
c.join(0);
... // really terminated here, all participantscalled back
} catch( Exception e) { ... }
}
};
t.start();
}
A Participant is likely to have to maintain state that is
particular for the collaboration. This state is usually needed in the
ended
method to properly finalize the work. In general, the
best place to store this state is in the Participant
object
itself, inner classes and final variables are a good technique for
storing the state. However, the state can also be stored in a
Coordination variable. Each Coordination has a
private set of variables that can be obtained with the getVariables() method. The resulting map takes a class as the
key and returns an Object. The map is not synchronized, any changes to
the map must be synchronized on the returned Map object to ensure the
visibility of the changes to other threads. The class used for the key
is not related to the returned type, it is a Class
object
to provide a convenient namespace.
The following example shows how the state can be stored with variables.
public void doWork(Coordination coordination){
Map<Class<?>,Object> map = coordination.getVariables();
synchronized(map) {
State state = (State) map.get( SharedWorker.class );
if ( state == null ) {
state = new State(this);
map.put( state );
... do initial work
}
}
... do other work
coordination.addParticipant( this );
}
public void ended(Coordination c) {
Map<Class<?>,Object> map = coordination.getVariables();
synchronized(map) {
State state = (State) map.get( SharedWorker.class );
.. finalize
}
}
public void failed(Coordination c) {
Map<Class<?>,Object> map = coordination.getVariables();
synchronized(map) {
State state = (State) map.get( SharedWorker.class );
.. finalize
}
}
For example, a web based system has a charge service:
public interface Charge {
void charge( String reason, int amount );
}
This service is used throughout the system for charging the tasks the system performs. Each servlet request can actually create multiple Charge Data Records (CDR). For this reason, a Coordination is started before the page is constructed. Each part of the page that has an associated cost must create a CDR. There are the following issues at stake:
-
Charging should not take place when failing, and
-
Performance can be optimized to only persist the CDRs once, and
-
The user must be passed to the Charge service.
To begin with the request code:
public void doGet(HttpServletRequest rq, HttpServletResponsersp) {
Coordination c = coordinator.begin("com.acme.request", 30000);
try {
Principal p = rq.getUserPrincipal();
Map<Class<?>,Object> map = c.getVariables();
map.put( Principal.class, p );
buildPage(rq,rsp);
} catch( Exception e ) { c.fail(e); }
finally { c.end(); }
}
Each method that has a charge will call the Charge service. The following code shows an implementation of this Charge service.
public class ChargeImpl implements Charge,Participant {
final List<CDR> records = new ArrayList<CDR>();
public void charge( String reason, int amount ) {
Coordination c = coordinator.peek();
if ( c == null ) {
save( Arrays.asList( new CDR(null, reason, amount)));
} else {
Principal p = getPrincipal(c);
records.add( new CDR(p, reason, amount ) );
c.addParticipant( this );
}
}
Principal getPrincipal(Coordination c) {
if ( c == null )
return null;
Map<Class<?>,Object> map = c.getVariables();
synchronized(map) {
Principal p = (Principal) map.get( Principal.class );
return p != null ? p : getPrincipal(c.getEnclosingCoordination());
}
}
public void ended(Coordination c) {
save(records);
records.clear();
}
public void failed(Coordination c) {
records.clear();
}
void save(List<CDR> records) { ... }
}
The Coordination Permission is a filter based permission that is asserted for many of the methods in the API, the bundle that is checked is always the bundle that created the corresponding Coordination. For example:
ALLOW {
[ BundleSignerCondition "cn=ACME" ]
( CoordinationPermission "(signer=cn=ACME)" "*" )
}
This example allows bundles signed by ACME to perform all Coordination actions on Coordinations created by bundles signed by ACME.
The filter can also assert the name of the Coordination:
coordination.name
It is therefore possible to create a name based protection scheme. By denying all bundles except a select group through the use of a name prefix, the use of Coordinations can be restricted to this select group:
DENY {
[ BundleSignerCondition "cn=ACME" "!" ]
( CoordinationPermission "(coordination.name=com.acme.*)""*" )
}
ALLOW {
( CoordinationPermission "(coordination.name=*)" "*" )
}
If a bundle is not signed by ACME it will be denied the use of
Coordination names starting with com.acme.
though it will
be allowed to use any other name. This effectively enables only bundles
signed by ACME to create Coordinations with this name prefix.
The Coordinator service is the entry point for the Coordination. It provides the following functions:
-
Coordination creation
-
Life cycle management of a Coordination
-
Thread based Coordinations
-
Introspection
A Coordination
object is created by an
initiator. An initiator can create a
Coordination
object with the Coordinator create(String,long) or begin(String,long) method. Each Coordination when created gets a
positive long identity that is available with getId(). Ids are a unique identifier for a specific
Coordinator service. The id is always increasing, that is, a
Coordination with a higher id is created later.
The create methods specify the name of the Coordination. This name is a security concept, see Security, as well as used for debugging. The coordination name must therefore conform to the same syntax as a bundle symbolic name:
coordination-name ::= symbolic-name // see OSGi Core Release 8
Passing a name that does not conform to this syntax must throw an Illegal Argument Exception. There are no constraints on duplicates, multiple different Coordinations can use the same name. The name of the Coordination is available with the getName() method.
The Coordination
object can be passed to
collaborators as a parameter in a method call. Some
of these collaborators might be interested in
participating in the given Coordination, they can
achieve this by adding a Participant
object to the
Coordination.
A Participant is a collaborator that requires a callback after the
Coordination has been terminated, either when it ended or when it
failed. To participate, it must add a Participant
object to
a Coordination with the addParticipant(Participant) method on Coordination. This method throws an
ALREADY_ENDED or FAILED Coordination Exception when the Coordination has
been terminated.
When a Participant is:
-
Not in any Coordination - Add it to the given Coordination and return.
-
In target Coordination - Ignore, participant is already present. A Participant can participate in the same Coordination multiple times by calling addParticipant(Participant) but will only be called back once when the Coordination is terminated. Its order must be defined by the first addition.
-
In another Coordination - Lock until after the other Coordination has notified all the Participants. Implementations can detect deadlocks in certain cases and throw a Coordination Exception if a dead lock exist, otherwise the deadlock is solved when the Coordination times out.
Verifying if a Participant object is already in another Coordination must use identity and not equality.
A Coordination is active until it is terminated. A Coordination can terminate because it is ended, or it is failed. The following methods cause a termination:
-
end() - A normal end. All participants that were added before the end call are called back on their ended(Coordination) method.
-
fail(Throwable) - The Coordination has failed, this will call back the failed(Coordination) method on the participants. This method can be called by the Coordinator, the initiator, or any of the collaborators. There are a number of failures that are built in to the Coordinator. These failures use singleton Exception instances defined in the
Coordination
interface:
The state diagram for the Coordination is pictured in Figure 130.4.
The Coordinator supports two very different models of usage: explicit and implicit. The explicit model is when a Coordination is created and passed around as a parameter. The second model is the implicit model where the Coordinator maintains a thread local stack of Coordinations. Any collaborator can then decide to use the top of the stack as the current Coordination. The peek() method provides access to the current Coordination.
The begin(String,long) method creates a new Coordination and pushes this on the stack, beginning an implicit Coordination. This is identical to:
coordinator.create("work",0).push();
Once a Coordination is pushed on a stack it is from that moment on associated with the current thread. A Coordination can only be pushed once, the ALREADY_PUSHED Coordination Exception must be thrown when the Coordination is already associated with one of the thread local stacks maintained by the Coordinator service.
The Coordination is removed from the stack in the end() method. The end() method must not only terminate itself but it must also terminate all nested Coordinations.
The current Coordination can also be explicitly removed with the Coordinator pop() method.
A Coordination that is pushed on a thread local stack returns the
associated thread on the getThread() method. This method returns null
for Coordinations not on any stack, that is, explicit
Coordinations.
Both the end() and fail(Throwable) methods terminate the Coordination if it was not
already terminated. Termination is atomic, only the end
or
the fail
method can terminate the Coordination. Though this
happens on different threads, a Coordination can never both end and fail
from any perspective. That is, if a fail races with end then only one of
them can win and the other provides the feedback that the Coordination
was already terminated.
Terminating a Coordination has the following effects:
-
It is atomic, it can only happen once in a Coordination
-
It freezes the set of participants, no more participants can be added
The end() method should always be called at the end of a Coordination to ensure proper termination, notification, and cleanup. The end method throws a FAILED or PARTIALLY_ENDED Coordination Exception if the Coordination was failed before.
If the Coordination had already been ended before then this is a programming error and an ALREADY_ENDED Configuration Exception is thrown. The end() method should never be called twice on the same Coordination.
If the termination succeeds then the participants must be notified by calling the ended(Coordination) method on each Participant that had been successfully added to the Coordination. This callback can take place on any thread but must be in reverse order of adding. That is, the last added Participant is called back first.
Participants must never make any assumptions about the current Coordination in the callback. The Coordination it was added to is therefore given as an explicit parameter in the ended(Coordination) method.
If a Participant throws an Exception then this must not prevent the calling of the remaining participants. The Exception should be logged. If a Participant has thrown an Exception then the end() method must throw a PARTIALLY_ENDED Coordination Exception after the last Participant has returned from its callback, otherwise the method returns normally. Participants should normally not throw Exceptions in their callbacks.
If the Coordination is implicit (it is pushed on a stack) then the Coordination must be removed from its stack after the participants have been called back. This requires that the ending thread is the same as the thread of the Coordination. The end thread is the thread of the end() method call. If the Coordination's thread is not the same as the ending thread then a WRONG_THREAD Coordination Exception is thrown.
If the ending Coordination is on the stack but it is not the current Coordination then each nested Coordination must be ended before the current Coordination, see Nesting Implicit Coordinations for more information.
The fail(Throwable) method must not remove the current Coordination,
it must remain on the stack. The initiator must always call the end() method. Always calling end() in a finally
block is therefore
paramount.
Failing can happen asynchronously during the
time a Coordination is active. A Coordination is failed by calling fail(Throwable). The Throwable argument must not be
null
, it is the cause of the failure.
Failing a Coordination must first terminate it. If the Coordination was already terminated the fail(Throwable) method has no effect. Otherwise, it must callback all its added Participants on the failed(Coordination) callback method. Exceptions thrown from this method should be logged and further ignored. The callback can occur on any thread, including the caller's.
Implicit Coordinations must not be popped from its stack in a fail
nor is it necessary to call the fail
method from any
particular thread. The removal of the Coordination from the stack must
happen in the end
method.
There are two asynchronous events that can also fail the
Coordination. If the Coordination times out, it will be treated as a
fail(
TIMEOUT )
and if the Coordinator is
ungotten with active Coordinations then each of those Coordinations must
fail as if fail(
RELEASED )
is called.
A Coordination can also be orphaned. An orphaned Coordination has no longer any outside references. This means that the Coordination can no longer be ended or failed. Such Coordinations must fail with an ORPHANED Exception.
Implicit Coordinations can be nested. For this reason, the Coordinator maintains a thread local stack of Coordinations where the top, accessible with the peek() method, is the current Coordination. Each time a new Coordination is begun with the begin(String,long) method, the current Coordination is replaced with the newly created Coordination. When that Coordination is ended, the previous current Coordination is restored. Nesting is always on the same thread, implicit Coordinations are always associated with a single thread, available through its getThread() method. The end method must be called on the same thread as the begin(String,long) or last push() method.
Using the standard model for implicit Coordinations, where the initiator always ends the Coordination on the same thread as it begun, ensures that nesting is properly handled. However, in certain cases it is necessary to manipulate the stack or make implicit Coordinations explicit or vice versa. For this reason, it is possible to pop Coordinations from the stack with the pop() method. This method disassociates the Coordination from the current thread and restores the previous (if any) Coordination as the current Thread. A Coordination can then be made the current Coordination for a thread by calling the push() method. However, a Coordination can be pushed on the stack at most once. If a Coordination is pushed a second time, in any thread, the ALREADY_PUSHED Coordination Exception must be thrown.
The Coordination is removed from its stack when the end() method is called. It is therefore highly
recommended to always end a Coordination in the nesting order. However,
it is possible that a Coordination is ended that is not the current
Coordination, it has nested Coordinations that were not properly ended.
In that case all nested Coordinations must be ended in reverse creation
order, that is, the current Coordination first, by calling the
end
method on it.
If any Coordination fails to end properly (including PARTIALLY_ENDED ) then the remaining Coordinations on the stack must fail and chain the exceptions. In pseudo code:
while (coordinator.peek() != this) {
try {
coordinator.peek().end();
} catch (CoordinationException e) {
coordinator.peek().fail(e);
}
}
When a Coordination is created it will receive a time-out. A time-out is a positive value or zero. A zero value indicates that the Coordination should have no time-out. This does not imply that a Coordination will never time-out, implementations are allowed to be configured with a limit to the maximum active time for a Coordination.
Collaborators can extend the time out with the extendTimeout(long) method. If no time-out was set (0), this method will be ignored. Otherwise the given amount (which must be positive) is added to the existing deadline. A Coordinator implementation can fail the Coordination earlier, however, when configured to do so.
If a Coordination is timed out, the Coordination is failed with a
fail(TIMEOUT)
method call from an unspecified thread, see
Failing, TIMEOUT, ORPHANED, and RELEASED.
The Coordination's life cycle is bound to the Coordinator service
that created it. If the initiator's bundle ungets this service then the
Coordinator must fail all the Coordinations created by this Coordinator
by calling the fail(RELEASED)
method.
Participants from bundles that are stopped are not taken into account. This means that it is possible that a participant is called while its bundle is stopped. Stopped Participants should fail any Coordinations that they participate in.
The Coordinator contains a number of convenience methods that can be used by collaborators to interact with the current Coordination.
-
begin(String,long) - Is logically the same as create(String,long). push().
-
addParticipant(Participant) - This method makes it easy to react differently to the presence of a current implicit Coordination. If a current Coordination exists, the participant is added and
true
is returned (or an exception thrown if the Coordination is already terminated), otherwisefalse
is returned. -
fail(Throwable) - If there is no current Coordination, this method returns false. Otherwise it returns the result of calling fail(Throwable) on the current Coordination. This method therefore only returns
true
when a current Coordination was actually terminated due to this call.
The Coordination
objects provide a number of methods
that are used for administrating the Coordinations and the
Coordinator.
-
getBundle() - Provide the bundle that created the Coordination. This bundle is the bundle belonging to the Bundle Context used to get the Coordinator service.
-
getFailure() - The Exception that caused this Coordination to fail or
null
. There are two fixed exception instances for a time out ( TIMEOUT ), when the Coordination is orphaned ( ORPHANED ), and when the Coordinator service is released ( RELEASED ). -
getId() - The Coordination's id.
-
getName() - The name of the Coordination.
-
getParticipants() - The current list of participants. This is a mutable snapshot of the added participants. Changing the snapshot has no effect on the Coordination.
-
getThread() - Answer the thread associated with an implicit Coordination. If the Coordination is not implicit then the answer is
null
. -
getEnclosingCoordination() - Return the enclosing Coordination.
And for the Coordinator:
-
getCoordination(long) - Retrieve a Coordination by its id.
-
getCoordinations() - Get a list of active Coordinations
A Coordination can exist in three different states ACTIVE, END, and FAIL. During its life it will transition from ACTIVE to either END or FAIL. The entry (when the state is entered) and exit (when the state is left) actions when this transition takes place and the effect on the different methods are summarized in the following table.
Table 130.1 States and transitions
State/Method | ACTIVE | END | FAIL |
---|---|---|---|
entry action |
Notify all the participants by calling the ended(Coordination) method. |
Notify all the participants by calling the failed(Coordination) method. |
|
exit action |
Terminate |
||
-> Can throw PARTIALLY_ENDED |
throws ALREADY_ENDED |
throws FAILED |
|
-> |
return |
return |
This specification provides a Coordination Permission. This permission can enforce the name of the coordination as well as assert the properties of the initiating bundle, like for example the signer or bundle symbolic name. The permission therefore uses a filter as name, as defined in the filter based permissions section in OSGi Core Release 8, see OSGi Core Release 8. There is one additional parameter for the filter:
coordination.name
The value is the given name of the Coordination. Restricting the name of a Coordination allows the deployer to limit the use of this name to a restricted set of bundles.
The following actions are defined:
-
INITIATE - Required to initiate and control a Coordination.
-
PARTICIPATE - Required to participate in a Coordination.
-
ADMIN - Required to administrate a Coordinator.
The target bundle of the Coordination Permission is the initiator's bundle. This is the bundle that got the Coordinator service to create the Coordination. An initiator must therefore have permission to create Coordinations for itself.
There are two constructors available:
-
CoordinationPermission(String,String) - The constructor for the granted permission. It is given a filter expression and the actions that the permission applies to.
-
CoordinationPermission(String,Bundle,String) - The constructor for the requested permission. It is given the name of the permission, the bundle that created the corresponding coordination, and the requested actions.
Coordinator Package Version 1.0.
Bundles wishing to use this package must list the package in the Import-Package header of the bundle's manifest. This package has two types of users: the consumers that use the API in this package and the providers that implement the API in this package.
Example import for consumers using the API in this package:
Import-Package: org.osgi.service.coordinator; version="[1.0,2.0)"
Example import for providers implementing the API in this package:
Import-Package: org.osgi.service.coordinator; version="[1.0,1.1)"
-
Coordination
- A Coordination object is used to coordinate a number of independent Participants. -
CoordinationException
- Unchecked exception which may be thrown by a Coordinator implementation. -
CoordinationPermission
- A bundle's authority to create or use a Coordination. -
Coordinator
- A Coordinator service coordinates activities between different parties. -
Participant
- A Participant participates in a Coordination.
A Coordination object is used to coordinate a number of independent Participants.
Once a Coordination is created, it can be used to add Participant objects. When the Coordination is ended, the participants are notified. A Coordination can also fail for various reasons. When this occurs, the participants are notified of the failure.
A Coordination must be in one of two states, either ACTIVE or TERMINATED. The transition between ACTIVE and TERMINATED must be atomic, ensuring that a Participant can be guaranteed of either receiving an exception when adding itself to a Coordination or of receiving notification the Coordination has terminated.
A Coordination object is thread safe and can be passed as a parameter to other parties regardless of the threads these parties use.
The following example code shows how a Coordination should be used.
void foo() {
Coordination c = coordinator.create("work", 0);
try {
doWork(c);
}
catch (Exception e) {
c.fail(e);
}
finally {
c.end();
}
}
Thread-safe
Consumers of this API must not implement this type
A singleton exception that will be the failure cause when a Coordination has been orphaned.
A singleton exception that will be the failure cause when the Coordinations created by a bundle are terminated because the bundle released the Coordinator service.
A singleton exception that will be the failure cause when a Coordination times out.
The Participant to register with this Coordination.
The participant must not be null
.
Register a Participant with this Coordination.
Once a Participant is registered with this Coordination, it is guaranteed to receive a notification for either normal or failure termination when this Coordination is terminated.
Participants are registered using their object identity. Once a Participant is registered with this Coordination, subsequent attempts to register the Participant again with this Coordination are ignored and the Participant is only notified once when this Coordination is terminated.
A Participant can only be registered with a single active Coordination at a time. If a Participant is already registered with an active Coordination, attempts to register the Participation with another active Coordination will block until the Coordination the Participant is registered with terminates. Notice that in edge cases the notification to the Participant that this Coordination has terminated can happen before this method returns.
Attempting to register a Participant with a terminated Coordination will result in a CoordinationException being thrown.
The ordering of notifying Participants must follow the reverse order in which the Participants were registered.
CoordinationException
– If the Participant could not be registered
with this Coordination. This exception should normally not be
caught by the caller but allowed to be caught by the initiator of
this Coordination.
SecurityException
– If the caller does not have
CoordinationPermission[PARTICIPATE]
for this
Coordination.
Terminate this Coordination normally.
If this Coordination has been pushed on the thread local Coordination stack of another thread, this method does nothing except throw a CoordinationException of type CoordinationException.WRONG_THREAD.
If this Coordination has been pushed on the thread local Coordination stack of this thread but is not the current Coordination, then the Coordinations on the thread local Coordination stack above this Coordination must be terminated and removed from the thread local Coordination stack before this Coordination is terminated. Each of these Coordinations, starting with the current Coordination, will be terminated normally . If the termination throws a CoordinationException, then the next Coordination on the thread local Coordination stack will be terminated as a failure with a failure cause of the thrown CoordinationException. At the end of this process, this Coordination will be the current Coordination and will have been terminated as a failure if any of the terminated Coordinations threw a CoordinationException
If this Coordination is the current Coordination, then it will be removed from the thread local Coordination stack.
If this Coordination is already terminated, a CoordinationException is thrown. If this Coordination was terminated as a failure, the failure cause will be the cause of the thrown CoordinationException.
Otherwise, this Coordination is terminated normally and then all registered Participants are notified. Participants should finalize any work associated with this Coordination. The successful return of this method indicates that the Coordination has terminated normally and all registered Participants have been notified of the normal termination.
It is possible that one of the Participants throws an exception during notification. If this happens, this Coordination is considered to have partially failed and this method must throw a CoordinationException of type CoordinationException.PARTIALLY_ENDED after all the registered Participants have been notified.
CoordinationException
– If this Coordination has failed, including
timed out, or partially failed or this Coordination is on the
thread local Coordination stack of another thread.
SecurityException
– If the caller does not have
CoordinationPermission[INITIATE]
for this Coordination.
The time in milliseconds to extend the current timeout. If the initial timeout was specified as 0, no extension must take place. A zero must have no effect.
Extend the time out of this Coordination.
Participants can call this method to extend the timeout of this Coordination with at least the specified time. This can be done by Participants when they know a task will take more than normal time.
This method will return the new deadline if an extension took place or the current deadline if, for whatever reason, no extension takes place. Note that if a maximum timeout is in effect, the deadline may not be extended by as much as was requested, if at all. If there is no deadline, zero is returned. Specifying a timeout extension of 0 will return the existing deadline.
The new deadline in milliseconds. If the specified time is 0, the existing deadline is returned. If this Coordination was created with an initial timeout of 0, no timeout is set and 0 is returned.
CoordinationException
– If this Coordination
is terminated.
IllegalArgumentException
– If the specified time is negative.
SecurityException
– If the caller does not have
CoordinationPermission[PARTICIPATE]
for this
Coordination.
The failure cause. The failure cause must not be
null
.
Terminate this Coordination as a failure with the specified failure cause.
If this Coordination is already terminated, this
method does nothing and returns false
.
Otherwise, this Coordination is terminated as a failure with the
specified failure cause and then all registered
Participants are
notified. Participants should
discard any work associated with this Coordination. This method will
return true
.
If this Coordination has been pushed onto a thread local Coordination stack, this Coordination is not removed from the stack. The creator of this Coordination must still call end() on this Coordination to cause it to be removed from the thread local Coordination stack.
true
if this Coordination was active and was terminated
by this method, otherwise false
.
SecurityException
– If the caller does not have
CoordinationPermission[PARTICIPATE]
for this
Coordination.
Returns the bundle that created this Coordination. This is the bundle that obtained the Coordinator service that was used to create this Coordination.
The bundle that created this Coordination.
SecurityException
– If the caller does not have
CoordinationPermission[ADMIN]
for this Coordination.
Returns the Coordination enclosing this Coordination if this Coordination is on the thread local Coordination stack.
When a Coordination is pushed onto the thread local Coordination stack, the former current Coordination, if any, is the enclosing Coordination of this Coordination. When this Coordination is removed from the thread local Coordination stack, this Coordination no longer has an enclosing Coordination.
The Coordination enclosing this Coordination if this Coordination
is on the thread local Coordination stack or null
if this
Coordination is not on the thread local Coordination stack or has
no enclosing Coordination.
SecurityException
– If the caller does not have
CoordinationPermission[ADMIN]
for this Coordination.
Returns the failure cause of this Coordination.
If this Coordination has failed, then this method will return the failure cause.
If this Coordination timed out, this method will return TIMEOUT as the failure cause. If this Coordination was active when the bundle that created it released the Coordinator service, this method will return RELEASED as the failure cause. If the Coordination was orphaned, this method will return ORPHANED as the failure cause.
The failure cause of this Coordination or null
if this
Coordination has not terminated as a failure.
SecurityException
– If the caller does not have
CoordinationPermission[INITIATE]
for this Coordination.
Returns the id assigned to this Coordination. The id is assigned by the Coordinator service which created this Coordination and is unique among all the Coordinations created by the Coordinator service and must not be reused as long as the Coordinator service remains registered. The id must be positive and monotonically increases for each Coordination created by the Coordinator service.
The id assigned to this Coordination.
Returns the name of this Coordination. The name is specified when this Coordination was created.
The name of this Coordination.
Returns a snapshot of the Participants registered with this Coordination.
A snapshot of the Participants registered with this Coordination. If no Participants are registered with this Coordination, the returned list will be empty. The list is ordered in the order the Participants were registered. The returned list is the property of the caller and can be modified by the caller.
SecurityException
– If the caller does not have
CoordinationPermission[INITIATE]
for this Coordination.
Returns the thread in whose thread local Coordination stack this Coordination has been pushed.
The thread in whose thread local Coordination stack this
Coordination has been pushed or null
if this Coordination
is not in any thread local Coordination stack.
SecurityException
– If the caller does not have
CoordinationPermission[ADMIN]
for this Coordination.
Returns the variable map associated with this Coordination. Each Coordination has a map that can be used for communicating between different Participants. The key of the map is a class, allowing for private data to be stored in the map by using implementation classes or shared data by using shared interfaces. The returned map is not synchronized. Users of the map must synchronize on the Map object while making changes.
The variable map associated with this Coordination.
SecurityException
– If the caller does not have
CoordinationPermission[PARTICIPANT]
for this
Coordination.
Returns whether this Coordination is terminated.
true
if this Coordination is terminated, otherwise
false
if this Coordination is active.
Maximum time in milliseconds to wait. Specifying a time of 0 will wait until this Coordination is terminated.
Wait until this Coordination is terminated and all registered Participants have been notified.
InterruptedException
– If the wait is interrupted.
IllegalArgumentException
– If the specified time is negative.
SecurityException
– If the caller does not have
CoordinationPermission[PARTICIPATE]
for this
Coordination.
Push this Coordination object onto the thread local Coordination stack to make it the current Coordination.
This Coordination.
CoordinationException
– If this Coordination is already on the any
thread's thread local Coordination stack or this Coordination
is terminated.
SecurityException
– If the caller does not have
CoordinationPermission[INITIATE]
for this Coordination.
Unchecked exception which may be thrown by a Coordinator implementation.
The Coordination has already terminated normally.
The Coordination was already on a thread's thread local Coordination stack.
Registering a Participant with a Coordination would have resulted in a deadlock.
The Coordination has terminated as a failure with Coordination.fail(Throwable). When this exception type is used, the getCause() method must return a non-null value.
The current thread was interrupted while waiting to register a Participant with a Coordination.
The Coordination cannot be ended by the calling thread since the Coordination is on the thread local Coordination stack of another thread.
The detail message for this exception.
The Coordination associated with this exception.
The cause associated with this exception.
The type of this exception.
Create a new Coordination Exception with a cause.
IllegalArgumentException
– If the specified type is FAILED
and the specified cause is null
.
The detail message for this exception.
The Coordination associated with this exception.
The type of this exception.
Create a new Coordination Exception.
IllegalArgumentException
– If the specified type is FAILED
.
Returns the id of the Coordination associated with this exception.
The id of the Coordination associated with this exception or
-1
if no Coordination is associated with this exception.
Returns the name of the Coordination associated with this exception.
The name of the Coordination associated with this exception or
"<>"
if no Coordination is associated with this
exception.
A bundle's authority to create or use a Coordination.
CoordinationPermission
has three actions: initiate
,
participate
and admin
.
Thread-safe
A filter expression. Filter attribute names are processed
in a case sensitive manner. A special value of "*"
can be
used to match all coordinations.
admin
, initiate
or participate
(canonical order).
Creates a new granted CoordinationPermission
object.
This constructor must only be used to create a permission that is going
to be checked.
Examples:
(coordination.name=com.acme.*)
(&(signer=\*,o=ACME,c=US)(coordination.name=com.acme.*))
(signer=\*,o=ACME,c=US)
When a signer key is used within the filter expression the signer value must escape the special filter chars ('*', '(', ')').
The name is specified as a filter expression. The filter gives access to the following attributes:
-
signer - A Distinguished Name chain used to sign the exporting bundle. Wildcards in a DN are not matched according to the filter string rules, but according to the rules defined for a DN chain.
-
location - The location of the exporting bundle.
-
id - The bundle ID of the exporting bundle.
-
name - The symbolic name of the exporting bundle.
-
coordination.name - The name of the requested coordination.
Filter attribute names are processed in a case sensitive manner.
IllegalArgumentException
– If the filter has an invalid syntax.
The name of the requested Coordination.
The bundle which created the requested Coordination.
admin
, initiate
or participate
(canonical order).
Creates a new requested CoordinationPermission
object to be used
by the code that must perform checkPermission
.
CoordinationPermission
objects created with this constructor
cannot be added to an CoordinationPermission
permission
collection.
The object to test for equality with this
CoordinationPermission
object.
Determines the equality of two CoordinationPermission
objects.
This method checks that specified permission has the same name and
CoordinationPermission
actions as this
CoordinationPermission
object.
true
if obj
is a CoordinationPermission
,
and has the same name and actions as this
CoordinationPermission
object; false
otherwise.
Returns the canonical string representation of the
CoordinationPermission
actions.
Always returns present CoordinationPermission
actions in the
following order: admin
, initiate
, participate
.
Canonical string representation of the
CoordinationPermission
actions.
Returns the hash code value for this object.
A hash code value for this object.
The requested permission.
Determines if the specified permission is implied by this object.
This method checks that the filter of the target is implied by the
coordination name of this object. The list of
CoordinationPermission
actions must either match or allow for the
list of the target object to imply the target
CoordinationPermission
action.
true
if the specified permission is implied by this
object; false
otherwise.
A Coordinator service coordinates activities between different parties.
A bundle can use the Coordinator service to create Coordination objects. Once a Coordination object is created, it can be pushed on the thread local Coordination stack to be an implicit parameter as the current Coordination for calls to other parties, or it can be passed directly to other parties as an argument. The current Coordination, which is on the top of the current thread's thread local Coordination stack, can be obtained with peek().
Any active Coordinations created by a bundle must be terminated when the bundle releases the Coordinator service. The Coordinator service must fail these Coordinations with the RELEASED exception.
A Participant can register to participate in a Coordination and receive notification of the termination of the Coordination.
The following example code shows a example usage of the Coordinator service.
void foo() {
Coordination c = coordinator.begin("work", 0);
try {
doWork();
} catch (Exception e) {
c.fail(e);
} finally {
c.end();
}
}
In the doWork
method, code can be called that requires notification
of the termination of the Coordination. The doWork
method can then
register a Participant with the Coordination.
void doWork() {
if (coordinator.addParticipant(this)) {
beginWork();
} else {
beginWork();
finishWork();
}
}
void ended(Coordination c) {
finishWork();
}
void failed(Coordination c) {
undoWork();
}
Thread-safe
Consumers of this API must not implement this type
The Participant to register with the current
Coordination. The participant must not be null
.
Register a Participant with the current Coordination.
If there is no current Coordination, this method does nothing and returns
false
.
Otherwise, this method calls
Coordination.addParticipant(Participant) with the specified
Participant on the current Coordination and returns true
.
false
if there was no current Coordination, otherwise
returns true
.
CoordinationException
– If the Participant could not be registered
with the current Coordination. This exception should normally not
be caught by the caller but allowed to be caught by the initiator
of this Coordination.
SecurityException
– If the caller does not have
CoordinationPermission[PARTICIPATE]
for the current
Coordination.
The name of this coordination. The name does not have to be
unique but must follow the symbolic-name
syntax from the
Core specification.
Timeout in milliseconds. A value of 0 means no timeout is required. If the Coordination is not terminated within the timeout, the Coordinator service will fail the Coordination with a TIMEOUT exception.
Create a new Coordination and make it the current Coordination.
This method does that same thing as calling create(name, timeMillis).push()
A new Coordination object
IllegalArgumentException
– If the specified name does not follow
the symbolic-name
syntax or the specified time is
negative.
SecurityException
– If the caller does not have
CoordinationPermission[INITIATE]
for the specified name
and creating bundle.
The name of this coordination. The name does not have to be
unique but must follow the symbolic-name
syntax from the
Core specification.
Timeout in milliseconds. A value of 0 means no timeout is required. If the Coordination is not terminated within the timeout, the Coordinator service will fail the Coordination with a TIMEOUT exception.
Create a new Coordination.
The new Coordination object.
IllegalArgumentException
– If the specified name does not follow
the symbolic-name
syntax or the specified time is
negative.
SecurityException
– If the caller does not have
CoordinationPermission[INITIATE]
for the specified name
and creating bundle.
The failure cause. The failure cause must not be
null
.
Terminate the current Coordination as a failure with the specified failure cause.
If there is no current Coordination, this method does nothing and returns
false
.
Otherwise, this method returns the result from calling Coordination.fail(Throwable) with the specified failure cause on the current Coordination.
false
if there was no current Coordination, otherwise
returns the result from calling
Coordination.fail(Throwable) on the current Coordination.
SecurityException
– If the caller does not have
CoordinationPermission[PARTICIPATE]
for the current
Coordination.
The id of the requested Coordination.
Returns the Coordination with the specified id.
A Coordination having with specified id or null
if no
Coordination with the specified id exists, the Coordination with
the specified id is terminated or the caller does not have
CoordinationPermission[ADMIN]
for the Coordination with
the specified id.
Returns a snapshot of all active Coordinations.
Since Coordinations can be terminated at any time, Coordinations in the returned collection can be terminated before the caller examines the returned collection.
The returned collection must only contain the Coordinations for which the
caller has CoordinationPermission[ADMIN]
.
A snapshot of all active Coordinations. If there are no active Coordinations, the returned list will be empty. The returned collection is the property of the caller and can be modified by the caller.
Returns the current Coordination.
The current Coordination is the Coordination at the top of the thread local Coordination stack. If the thread local Coordination stack is empty, there is no current Coordination. Each Coordinator service maintains thread local Coordination stacks.
This method does not alter the thread local Coordination stack.
The current Coordination or null
if the thread local
Coordination stack is empty.
Remove the current Coordination from the thread local Coordination stack.
The current Coordination is the Coordination at the top of the thread local Coordination stack. If the thread local Coordination stack is empty, there is no current Coordination. Each Coordinator service maintains its own thread local Coordination stacks.
This method alters the thread local Coordination stack, if it is not empty, by removing the Coordination at the top of the thread local Coordination stack.
The Coordination that was the current Coordination or
null
if the thread local Coordination stack is empty.
SecurityException
– If the caller does not have
CoordinationPermission[INITIATE]
for the current
Coordination.
A Participant participates in a Coordination.
A Participant can participate in a Coordination by registering itself with the Coordination. After successfully registering itself, the Participant is notified when the Coordination is terminated.
If a Coordination terminates normally, then all registered Participants are notified on their ended(Coordination) method. If the Coordination terminates as a failure, then all registered Participants are notified on their failed(Coordination) method.
Participants are required to be thread safe as notification can be made on any thread.
A Participant can only be registered with a single active Coordination at a time. If a Participant is already registered with an active Coordination, attempts to register the Participation with another active Coordination will block until the Coordination the Participant is registered with terminates. Notice that in edge cases the notification to the Participant that the Coordination has terminated can happen before the registration method returns.
Thread-safe
The Coordination that has terminated normally.
Notification that a Coordination has terminated normally.
This Participant should finalize any work associated with the specified Coordination.
Exception
– If this Participant throws an exception, the
Coordinator service should log the exception. The
Coordination.end() method which is notifying this
Participant must continue notification of other registered
Participants. When this is completed, the
Coordination.end() method must throw a
CoordinationException of type
CoordinationException.PARTIALLY_ENDED.
The Coordination that has terminated as a failure.
Notification that a Coordination has terminated as a failure.
This Participant should discard any work associated with the specified Coordination.
Exception
– If this Participant throws an exception, the
Coordinator service should log the exception. The
Coordination.fail(Throwable) method which is notifying
this Participant must continue notification of other registered
Participants.