1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| Overview:
A CompletableFuture may have dependent completion actions, collected in a linked stack. It atomically completes by CASing a result field, and then pops off and runs those actions. This applies across normal vs exceptional outcomes, sync vs async actions, binary triggers, and various forms of completions. Non-nullness of field result (set via CAS) indicates done. An AltResult is used to box null as a result, as well as to hold exceptions. Using a single field makes completion simple to detect and trigger. Encoding and decoding is straightforward but adds to the sprawl of trapping and associating exceptions with targets. Minor simplifications rely on (static) NIL (to box null results) being the only AltResult with a null exception field, so we don't usually need explicit comparisons. Even though some of the generics casts are unchecked (see SuppressWarnings annotations), they are placed to be appropriate even if checked. Dependent actions are represented by Completion objects linked as Treiber stacks headed by field "stack". There are Completion classes for each kind of action, grouped into single-input (UniCompletion), two-input (BiCompletion), projected (BiCompletions using either (not both) of two inputs), shared (CoCompletion, used by the second of two sources), zero-input source actions, and Signallers that unblock waiters. Class Completion extends ForkJoinTask to enable async execution (adding no space overhead because we exploit its "tag" methods to maintain claims). It is also declared as Runnable to allow usage with arbitrary executors. Support for each kind of CompletionStage relies on a separate class, along with two CompletableFuture methods:
* A Completion class with name X corresponding to function, prefaced with "Uni", "Bi", or "Or". Each class contains fields for source(s), actions, and dependent. They are boringly similar, differing from others only with respect to underlying functional forms. We do this so that users don't encounter layers of adaptors in common usages. We also include "Relay" classes/methods that don't correspond to user methods; they copy results from one stage to another. * Boolean CompletableFuture method x(...) (for example uniApply) takes all of the arguments needed to check that an action is triggerable, and then either runs the action or arranges its async execution by executing its Completion argument, if present. The method returns true if known to be complete. * Completion method tryFire(int mode) invokes the associated x method with its held arguments, and on success cleans up. The mode argument allows tryFire to be called twice (SYNC, then ASYNC); the first to screen and trap exceptions while arranging to execute, and the second when called from a task. (A few classes are not used async so take slightly different forms.) The claim() callback suppresses function invocation if already claimed by another thread. * CompletableFuture method xStage(...) is called from a public stage method of CompletableFuture x. It screens user arguments and invokes and/or creates the stage object. If not async and x is already complete, the action is run immediately. Otherwise a Completion c is created, pushed to x's stack (unless done), and started or triggered via c.tryFire. This also covers races possible if x completes while pushing. Classes with two inputs (for example BiApply) deal with races across both while pushing actions. The second completion is a CoCompletion pointing to the first, shared so that at most one performs the action. The multiple-arity methods allOf and anyOf do this pairwise to form trees of completions. Note that the generic type parameters of methods vary according to whether "this" is a source, dependent, or completion. Method postComplete is called upon completion unless the target is guaranteed not to be observable (i.e., not yet returned or linked). Multiple threads can call postComplete, which atomically pops each dependent action, and tries to trigger it via method tryFire, in NESTED mode. Triggering can propagate recursively, so NESTED mode returns its completed dependent (if one exists) for further processing by its caller (see method postFire). Blocking methods get() and join() rely on Signaller Completions that wake up waiting threads. The mechanics are similar to Treiber stack wait-nodes used in FutureTask, Phaser, and SynchronousQueue. See their internal documentation for algorithmic details. Without precautions, CompletableFutures would be prone to garbage accumulation as chains of Completions build up, each pointing back to its sources. So we null out fields as soon as possible (see especially method Completion.detach). The screening checks needed anyway harmlessly ignore null arguments that may have been obtained during races with threads nulling out fields. We also try to unlink fired Completions from stacks that might never be popped (see method postFire). Completion fields need not be declared as final or volatile because they are only visible to other threads upon safe publication
|