Continuation Support in Rhino

by Attila Szegedi

You can create a continuation using a Continuation constructor:

var c = new Continuation();

Continuation is a standard object in Rhino, available in all scopes that have been initialized with a call to Context.initStandardObjects(), along with String, Date, Number, etc. standard objects.

When you construct a continuation, it captures the state of all the call frames on the call stack at the point of the constructor invocation. The call frames carry the information about the invoked function, its current program counter, and the values of all local variables.

Pretty much the only thing you can do with a continuation is to call it as a function with a single parameter:

c(anyExpression);

What happens when you invoke a continuation? First, the current call stack is unwound to the first (starting from top) call frame that it has in common with the stack stored in the continuation, with "finally" blocks being executed along the way. When a common call frame is reached, the frames stored in the continuation above it are placed on the stack. What follows is a bit strange, but that's the way it works: the execution doesn't resume at the point where the continuation was created, but rather the function in which the constructor was invoked immediately returns with "anyExpression" (the value passed to continuation invocation) as its result. This makes it possible to communicate data from the invoker to the (re)invoked code. This has some interesting consequences, i.e. this program:

function f()
{
    return new Continuation();
}

var c = f();
if(c instanceof Continuation)
{
    print("c is a continuation");
    c("Hello World");
}
else
{
    print("c is " + c);
}
will print:
c is a continuation
c is Hello World

As an implementation detail, finally blocks in f() itself won't be executed before it returns on continuation invocation (they will be executed when the f() normally executes, though). Also, no code in c() below continuation constructor will be executed when the continuation is invoked:

function f()
{
    var c;
    try
    {
        c = new Continuation();
        print("created continuation");
    }
    finally
    {
        print("finally in f");
    }
    return c;
}

var c = f();
if(c instanceof Continuation)
{
    print("c is a continuation");
    c("Hello World");
}
else
{
    print("c is " + c);
}
will print:
created continuation
finally in f
c is a continuation
c is Hello World

Another consequence of execution immediately returning to the next-to-top function is that a continuation captured at the top level of the script will terminate the interpreter:

var c = new Continuation();

function f()
{
   c(null);
}
f();

Invoking c will unwind the stack to its bottom (finally blocks get executed, naturally) and terminate the interpreter. This is often used in a programming pattern where a continuation is captured and stored for later execution, and then the interpreter is terminated and thus the thread freed up to do something else. In the below code __engine__ is the host object:

var __terminate_interpreter__ = new Continuation();
var isGoingToSleep = false;

function yield()
{
    __engine__.setContinuation(new Continuation());
    this.__terminate_interpreter__(null);
}

function isYielding()
{
    return __engine__.hasContinuation;
}

function foo()
{
   ...
   try
   {
      ...
      yield();
      ...
   }
   finally
   {
      if(!isYielding())
      {
          ...
      }
   }
}

Note the use of the "isYielding" function - we often want the finally blocks to execute only when the script is indeed finally leaving the block of code, not because of a temporary "yield". The underlying framework guarantees that the script will be restarted sooner or later from the obtained continuation, so semantically it's usually not yet the time to execute finally blocks. However, if the intent is to cleanup some transient resources whenever the script yields, then it can be done too.

try
{
    ...
    yield();
    ...
}
finally
{
    if(isYielding())
    {
        /* transient cleanup */
    }
    else
    {
       /* permanently leaving the block */
    }
}

There is however no facility to obtain transient resources when the continuation is invoked again. I have found that in practice, I neither use such transient cleanup nor need to obtain such transient resources. My "reinstantiated" continuations are serialized/deserialized objects, and I have a serialization stubbing mechanism that allows stubbing of shared resource objects on serialization and reconnecting to same shared instances of them upon deserialization, and it works fine for my needs.