diff --git a/classpath/avian/Continuations.java b/classpath/avian/Continuations.java index 0953995320..3aaf9c17c1 100644 --- a/classpath/avian/Continuations.java +++ b/classpath/avian/Continuations.java @@ -12,10 +12,159 @@ package avian; import java.util.concurrent.Callable; +/** + * This class provides access to the Avian VM's continuation support. + * + *

A continuation is a snapshot of a thread's call stack which can + * be captured via callWithCurrentContinuation and later + * restored any number of times. We 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 + * methods call-with-current-continuation and + * dynamic-wind, respectively. Additionally, we define + * how continuations work with respect to native code, exceptions, + * try/finally blocks, synchronized blocks, and multithreading. + * + *

Continuations and Continuation Contexts

+ * + *

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 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 calling + * continuation. When the last method in this new continuation + * returns, it returns to the native frame which created the current + * context, which 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 least-recently called method in the call trace + * (i.e. the end of the list). A continuation may be called from a + * different continuation context than the one in which it was created + * provided the return type of the new context is compatible with the + * original context. + * + *

Given a thread executing in continuation context "A" which wants + * to call a continuation created in context "B", the following rules + * apply: + * + *

+ * + *

A 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 result in an + * avian/IncompatibleContinuationException being thrown. + * + *

Winding, Unwinding, and Rewinding

+ * + *

Traditionally, Java provides one way to wind the execution stack + * (recursive method calls) and two ways to unwind it (normal returns + * and exception unwinding). With continuations, we add a new way to + * (re)wind 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", we + * 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, we 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 + * before executing a task and release when that task is done. With + * continuations, we may wish to yield control temporarily to another + * continuation while executing such a task, and we might do so + * several times. In such a case we would only want to acquire the + * resource when we start the task and only release it when we're + * finished, regardless of how often we unwound to reach other + * continuations or rewound to return to the next step. + * + *

Conversely, we may wish to acquire and release a resource each + * time we (re)wind to or unwind from a continuation, respectively. + * In this case, we use dynamicWind to register functions + * which will run every time that frame is passed, regardless of how + * the stack is wound or unwound. + */ public abstract class Continuations { + /** + * Captures the current continuation, passing a reference to the + * specified receiver. + * + *

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) of the + * continuation. + */ public static native T callWithCurrentContinuation (CallbackReceiver receiver) throws Exception; + /** + * Calls the specified "before" and "after" tasks each time a + * continuation containing the call is wound or unwound, + * respectively. + * + *

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. + */ public static T dynamicWind(Runnable before, Callable thunk, Runnable after) diff --git a/makefile b/makefile index a6845d4abb..c4710d5511 100644 --- a/makefile +++ b/makefile @@ -363,7 +363,7 @@ tarball: .PHONY: javadoc javadoc: - javadoc -sourcepath classpath -d build/javadoc -subpackages java \ + javadoc -sourcepath classpath -d build/javadoc -subpackages avian:java \ -windowtitle "Avian v$(version) Class Library API" \ -doctitle "Avian v$(version) Class Library API" \ -header "Avian v$(version)" \