SWF Flow error handling relies on the idea that any asynchronous task (which includes methods annotated as {@link Asynchronous}) is executed in a parent context. In the case of an exception being thrown from a task, all sibling tasks that share the same parent context are canceled. After successful cancellation the exception is propagated to the parent context for handling.
TryCatchFinally
{@link #doTry()}, {@link #doFinally()} and{@link #doCatch(Throwable)} methods serve as parent scopes for tasks createdduring their execution.
For example if any {@link Task} (or method annotated with{@link Asynchronous}) that originates (possibly indirectly through parent task) in {@link #doTry()} throws an exception all other tasks that originatedin {@link #doTry()} are canceled and only then {@link #doCatch(Throwable)} iscalled. Through this cancellation mechanism it is guaranteed that {@link #doCatch(Throwable)} is called at most once even if there are multipleparallel tasks executing in the {@link #doTry()} scope.
If failure happens in a task originated in {@link #doCatch(Throwable)} thenall its siblings are canceled first and then {@link #doFinally()} is called.
The same happens if task originated in {@link #doFinally()} fails. All itsiblings are canceled and then parent scope of the TryCatchFinally
is given chance to handle the exception.
The cancellation semantic depends on the task implementation. {@link Task}(or method annotated with {@link Asynchronous}) that has not started execution is never given chance to execute after cancellation. Task which is already executing is not interrupted and completes (or fails). {@link ExternalTask} cancellation depends on the external resource. Forexample SWF activities and child workflows that are modeled as {@link ExternalTask}s are canceled through SWF API.
When TryCatchFinally
itself is canceled because of sibling task failure it handles its own cancellation in the following order:
TryCatchFinally
is considered canceled immediately.{@link #doCatch(Throwable)} and {@link #doFinally()}are not cancelable. It means that cancellation request always waits for completion of all tasks that originate in these methods. Special care should be taken when writing code in {@link #doCatch(Throwable)} and in{@link #doFinally()} to ensure that in all possible failure scenarios theydon't end up in a stuck state.
TryCatchFinally
can be canceled explicitly through {@link #cancel(Throwable)} method. In case of explicit cancellation anyexception including {@link CancellationException} that is rethrown from{@link #doCatch(Throwable)} or {@link #doFinally()} is propagated to theparent context.
It is pretty common to have tasks that have their lifecycle linked to some other tasks. For example notification activity that is sent out if some other activity is not completed in a specified period of time. The timer and the notification activity should be canceled as soon as the monitored activity is completed. Such use case can be supported by wrapping timer and notification activity in TryCatchFinally
which is explicitly canceled upon monitored activity completion. The more convenient way to perform such cleanup is by marking a Task (or method annotated with {@link Asynchronous}) as daemon. The asynchronous scope in absence of failures is executed in the following sequence:
Pass true
to the first argument of {@link Task#Task(boolean,Promise)} constructor to mark a Task as daemon.Use {@link Asynchronous#daemon()} annotation parameter to mark anasynchronous method as daemon. Any task that is created during execution of a daemon task is marked as daemon. TryCatchFinally
also can be marked as daemon through {@link #TryCatchFinally(boolean,Promise)}constructor or by by being created by a daemon task. Note that TryCatchFinally doesn't pass its daemon flag to tasks created in {@link #doTry()}, {@link #doCatch(Throwable)} and {@link #doFinally()}. It is because each of these methods acts as a separate asynchronous scope and the rules of execution described above apply to them.
In case of multiple simultaneous exceptions (which is possible for external tasks) or if cancellation of a child results in any exception that is not {@link CancellationException} the last exception "wins". I.e. it becomes theexception delivered to {@link #doCatch(Throwable)} method.
Note that instances of {@link Promise} do not participate in error handlingthe way {@link Future} does. If method that returns Promise throws anexception the Promise state and return value are not changed. It is similar to behavior of variables in case of synchronous code.
Basic error handling:
new TryCatchFinally() { final List<Promise<String>> instances = new ArrayList<Promise<String>>(); protected void doTry() throws Throwable { for (int i = 0; i < count; i++) { Promise<String> instanceId = ec2.startInstance(); instances.add(instanceId); } performSimulation(instances); } protected void doCatch(Throwable e) throws Throwable { mail.notifySimulationFailure(e); } protected void doFinally() throws Throwable { for (int i = 0; i < count; i++) { Promise<String> instanceId = instances.get(i); if (instanceId.isReady()) { ec2.stopInstance(instanceId.get()); } } } };
Daemon example:
protected void doTry() throws Throwable { for (int i = 0; i < count; i++) { Promise<String> instanceId = ec2.startInstance(); instances.add(instanceId); } performSimulation(instances); notifyOnDelay(); } @Asynchronous(daemon = true) public void notifyOnDelay() { Promise<Void> timer = clock.scheduleTimer(3600); mail.notifyDelay(timer); }@author fateev
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|