public class Continuations extends Object
A continuation is a snapshot of a thread's call stack which can
be captured via callWithCurrentContinuation and later
restored any number of times. The program may restore this
snapshot by either feeding it a result (to be returned by
callWithCurrentContinuation) or feeding it an
exception (to be thrown by
callWithCurrentContinuation). Continuations may be
used to implement features such as coroutines, generators, and
cooperative multitasking.
This class provides two static methods,
callWithCurrentContinuation and
dynamicWind, with similar semantics to the Scheme
functions call-with-current-continuation and
dynamic-wind, respectively. In addition, we define
how continuations work with respect to native code, exceptions,
try/finally blocks, synchronized blocks, and multithreading.
A continuation can be thought of as a singly-linked list of
stack frames representing the call trace, where the head of the
list is the frame of the method most recently called (i.e. the top
of the stack). However, this trace only extends as far as the most
recent chain of Java frames - it ends just prior to the most recent
native frame in the stack. The reason for this is that the VM
cannot, in general, safely capture and restore native frames.
Therefore, each call from native code to Java (including the
original invocation of main(String[]) or
Thread.run()) represents a new continuation context in
which continuations may be captured, and these will only contain
frames from within that context.
Calling a continuation (i.e. feeding it a result or exception) causes the current continuation to be replaced with the called continuation. When the last method in this new continuation returns, it returns to the native frame which created the current context, which may or may not be the same as the context in which that continuation was created.
We define the return type of a continuation context as the return type of the first method called in that context. A continuation may be called from a different context than the one in which it was created, provided the return type of the latter is compatible with the current context.
Given a thread executing in context "A" which wants to call a continuation created in context "B", the following rules apply:
void, the return
type of "B" may be anything, including voidA thread may call a continuation created by a different thread
provided the return types are compatible. Multiple threads may
safely call the same continuation simultaneously without
synchronization. Any attempt to call a continuation from a context
with an incompatible return type will throw an IncompatibleContinuationException.
Traditionally, Java provides one way to wind the execution stack (method calls) and two ways to unwind it (normal returns and exception unwinding). With continuations, we add a new way to rewind the stack and a new way to unwind it.
The call stack of a continuation may share frames with other continuations - in which case they share a common history. When calling a continuation "B" from the current continuation "A", the VM must unwind past any frames which are in "A" but not in "B" and rewind past any frames in "B" but not in "A". During this unwinding and rewinding, control may pass through synchronized and try/finally blocks while going down the old stack and up the new stack.
However, unlike the traditional processes of winding and unwinding, the VM will ignore these blocks - monitors will not be released or acquired and finally blocks will not execute. This is by design. The purpose of such a block is to acquire a resource, such as a file handle or monitor, once before executing a task and release it after the task is finished, regardless of how often the task might temporarily yield control to other continuations.
Alternatively, one might wish to acquire and release a resource
each time control (re)winds to or unwinds from a continuation,
respectively. In this case, one may use dynamicWind
to register functions which will run every time that frame is
passed, regardless of how the stack is wound or unwound.
| Modifier and Type | Method and Description |
|---|---|
static <T> T |
callWithCurrentContinuation(CallbackReceiver<T> receiver)
Captures the current continuation, passing a reference to the
specified receiver.
|
static <T> T |
dynamicWind(Runnable before,
Callable<T> thunk,
Runnable after)
Calls the specified "before" and "after" tasks each time a
continuation containing the call is wound or unwound,
respectively.
|
public static <T> T callWithCurrentContinuation(CallbackReceiver<T> receiver) throws Exception
This method will either return the result returned by
receiver.receive(Callback), propagate the exception
thrown by that method, return the result passed to the
handleResult(T) method of the continuation, or throw the
exception passed to the handleException(Throwable) method of the
continuation.
Exceptionpublic static <T> T dynamicWind(Runnable before, Callable<T> thunk, Runnable after) throws Exception
This method first calls before.run(), then
thunk.call(), and finally after.run(),
returning the result of the second call. If
before.run() does not return normally, the second
and third calls will not happen. If thunk.call()
throws an exception, after.run(), will be called
before the exception is propagated.
If thunk.call() calls a continuation (directly or
via a subroutine) which does not include the current call to
dynamicWind, after.run() will be called
before control passes to that continuation. If this call throws
an exception, the exception will propagate to the current caller
of dynamicWind.
If thunk.call() creates a continuation which is
later called from a continuation which does not include the
current call to dynamicWind,
before.run() will be called before control passes to
that continuation. As above, if this call throws an exception,
the exception will propagate to the current caller of
dynamicWind.
Exception