/* * Written by Doug Lea with assistance from members of JCP JSR-166 * Expert Group and released to the public domain, as explained at * */ package jsr166y.forkjoin; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; /** * AsyncActions that may be linked in parent-child relationships. * * <p> Upon construction, an LinkedAsyncAction may register as a * subtask of a given parent task. In this case, completion of this * task will propagate to its parent. If the parent's pending subtask * completion count becomes zero, it too will finish. * LinkedAsyncActions rarely use methods <tt>join</tt> or * <tt>invoke</tt> but instead propagate completion to parents * implicitly via <tt>finish</tt>. While typical, it is not necessary * for each task to <tt>finish</tt> itself. For example, it is * possible to treat one subtask as a continuation of the current task * by not registering it on construction. In this case, a * <tt>finish</tt> of the subtask will trigger <tt>finish</tt> of the * parent without the parent explicitly doing so. * * <p> In addition to supporting these different computation styles * compared to Recursive tasks, LinkedAsyncActions may have smaller * stack space footprints while executing, but may have greater * per-task overhead. * * <p> <b>Sample Usage.</b> Here is a sketch of an LinkedAsyncAction * that visits all of the nodes of a graph. The details of the graph's * Node and Edge classes are omitted, but we assume each node contains * an <tt>AtomicBoolean</tt> mark that starts out false. To execute * this, you would create a GraphVisitor for the root node with null * parent, and <tt>invoke</tt> in a ForkJoinPool. Upon return, all * reachable nodes will have been visited. * * <pre> * class GraphVisitor extends LinkedAsyncAction { * final Node node; * GraphVisitor(GraphVistor parent, Node node) { * super(parent); this.node = node; * } * protected void compute() { * if (node.mark.compareAndSet(false, true)) { * for (Edge e : node.edges()) { * Node dest = e.getDestination(); * if (!dest.mark.get()) * new GraphVisitor(this, dest).fork(); * } * visit(node); * } * finish(); * } * } * </pre> * */ public abstract class LinkedAsyncAction extends AsyncAction { /** * Parent to notify on completion */ private LinkedAsyncAction parent; /* * Note: we also piggyback pending join count on * ForkJoinTask.status field. */ /** * Creates a new action with no parent. (You can add a parent * later (but before forking) via <tt>reinitialize</tt>). */ protected LinkedAsyncAction() { } /** * Creates a new action with the given parent. If the parent is * non-null, this tasks registers with the parent, in which case, * the parent task cannot complete until this task completes. * @param parent the parent task, or null if none */ protected LinkedAsyncAction(LinkedAsyncAction parent) { this.parent = parent; if (parent != null) parent.incrementStatus(); } /** * Creates a new action with the given parent, optionally * registering with the parent. If the parent is non-null and * <tt>register</tt> is true, this tasks registers with the * parent, in which case, the parent task cannot complete until * this task completes. * @param parent the parent task, or null if none * @param register true if parent must wait for this task * to complete before it completes */ protected LinkedAsyncAction(LinkedAsyncAction parent, boolean register) { this.parent = parent; if (parent != null && register) parent.incrementStatus(); } /** * Creates a new action with the given parent, optionally * registering with the parent, and setting the pending join count * to the given value. If the parent is non-null and * <tt>register</tt> is true, this tasks registers with the * parent, in which case, the parent task cannot complete until * this task completes. Setting the pending join count requires * care -- it is correct only if child tasks do not themselves * register. * @param parent the parent task, or null if none * @param register true if parent must wait for this task * to complete before it completes * @param pending the pending join count */ protected LinkedAsyncAction(LinkedAsyncAction parent, boolean register, int pending) { if (pending < 0) throw new IllegalArgumentException(); this.parent = parent; status = pending; if (parent != null && register) parent.incrementStatus(); } /** * Overridable callback action triggered by <tt>finish</tt>. Upon * invocation, all subtasks have completed. After return, this * task <tt>isDone</tt> and is joinable by other tasks. The * default version of this method does nothing. But it may may be * overridden in subclasses to perform some action when this task * is about to complete. */ protected void onCompletion() { } /** * Overridable callback action triggered by * <tt>finishExceptionally</tt>. Upon invocation, this task has * aborted due to an exception (accessible via * <tt>getException</tt>). If this method returns <tt>true</tt>, * the exception propagates to the current task's * parent. Otherwise, normal completion is propagated. The * default version of this method does nothing and returns * <tt>true</tt>. * @return true if this task's exception should be propagated to * this tasks parent. */ protected boolean onException() { return true; } /** * Completes this task. If the pending subtask completion count is * zero, invokes <tt>onCompletion</tt>, then causes this task to * be joinable (<tt>isDone</tt> becomes true), and then * recursively applies to this tasks's parent, if it exists. If an * exception is encountered in any <tt>onCompletion</tt> * invocation, that task and its ancestors * <tt>finishExceptionally</tt>. */ public final void finish() { LinkedAsyncAction a = this; while (a != null && !a.isDone()) { int c = a.status; if (c <= 0) { try { a.onCompletion(); } catch (Throwable rex) { a.finishExceptionally(rex); return; } a.setDone(); a = a.parent; } else if (a.casStatus(c, c-1)) return; } } /** * Completes this task abnormally. Unless this task already * cancelled or aborted, upon invocation, this method invokes * <tt>onException</tt>, and then, depending on its return value, * finishes parent (if one exists) exceptionally or normally. To * avoid unbounded exception loops, this method aborts if an * exception is encountered in any <tt>onException</tt> * invocation. * @param ex the exception to throw when joining this task * @throws NullPointerException if ex is null * @throws Throwable if any invocation of * <tt>onException</tt> does so. */ public final void finishExceptionally(Throwable ex) { if (!(ex instanceof RuntimeException) && !(ex instanceof Error)) throw new IllegalArgumentException(ex); doFinishExceptionally(ex); } /** * Internal version without argument screening */ private void doFinishExceptionally(Throwable ex) { LinkedAsyncAction a = this; for (;;) { if (a.status == ForkJoinTask.HAS_EXCEPTION) break; a.setDoneExceptionally(ex); boolean up = a.onException(); // abort if this throws a = a.parent; if (a == null) break; if (!up) { a.finish(); break; } } } /** * Returns this task's parent, or null if none. * @return this task's parent, or null if none. */ public final LinkedAsyncAction getParent() { return parent; } /** * Returns the number of subtasks that have not yet completed. * @return the number of subtasks that have not yet completed. */ public final int getPendingSubtaskCount() { return status; } /** * Resets the internal bookkeeping state of this task, maintaining * the current parent but clearing pending joins. */ public void reinitialize() { super.reinitialize(); } /** * Resets the internal bookkeeping state of this task, maintaining * the current parent and setting pending joins to the given value. * @param pending the number of pending joins */ public void reinitialize(int pending) { if (pending < 0) throw new IllegalArgumentException(); super.reinitialize(); status = pending; } /** * Reinitialize with the given parent, optionally registering. * @param parent the parent task, or null if none * @param register true if parent must wait for this task * to complete before it completes */ public void reinitialize(LinkedAsyncAction parent, boolean register) { super.reinitialize(); this.parent = parent; if (parent != null && register) parent.incrementStatus(); } /** * Reinitialize with the given parent, optionally registering * and setting pending join count. * @param parent the parent task, or null if none * @param register true if parent must wait for this task * to complete before it completes * @param pending the pending join count */ public void reinitialize(LinkedAsyncAction parent, boolean register, int pending) { if (pending < 0) throw new IllegalArgumentException(); super.reinitialize(); status = pending; this.parent = parent; if (parent != null && register) parent.incrementStatus(); } final boolean exec() { if (status >= 0) { try { compute(); } catch(Throwable rex) { doFinishExceptionally(rex); } } return false; } }