Search code examples
javascriptrhinojava-scripting-engine

Rhino: Ability to pause, save state and resume javascript


I'm using Rhino to interpret javascripts by Java. My use case requires these javascripts making (multiple) service calls (RESTful/Webservices/HTTP GET/POST). Some of these service calls are async in nature (with 24 Hours of SLA).

I want the ability to pause the execution of my script in such cases, serialize the state (saving it on, say S3 with key passed to the payload of async service which the service returns back on callback) and resume the execution when I get the result back from service.

The challenge I'm facing is that ContinuationPending (extending RuntimeException) isn't serializable (because Context is not).

Question: Is there any other way to store the state of script and get it back from its serialized form?

Javascript:

function invokeFooService(arg) {
    return foo.bar.Helper.invokeFooServiceAsync(arg);
}

function main() {
    // Main JS function
    ..
    var response = invokeFooService(arg);
    if (..) {
        ..
    }
}

Java:

package foo.bar;

public class Helper {

    public static final void invokeFooServiceAsync(String arg) {
        Context cx = getContext();
        ContinuationPending pending = cx.captureContinuation();
        // At this point the script is paused
        // Serialize the state of script
        invokeFooService(arg, key);
    }

    public static final void returnResponse(FooResponse response, String key) {
        // De serialize the state of script
        ContinuationPending pending = ..
        Context cx = getContext();
        cx.resumeContinuation(pending.getContinuation(), getScope(), response);
        // Script is resumed
    }
}

Solution

  • I found the solution finally. The key is to use ScriptableOutputStream (to serialize) and ScriptableInputStream (to deserialize) Continuation and Scope.

    Below is working code.

    Javascript:

    function invokeFooService(arg) {
        return foo.bar.Helper.invokeFooServiceAsync(arg);
    }
    
    function main() {
        // Main JS function
        ..
        var response = invokeFooService(arg);
        if (..) {
            ..
        }
    }
    

    Java:

    package foo.bar;
    
    public class Helper {
    
        public static final void invokeFooServiceAsync(String arg) {
            Context cx = getContext();
            ContinuationPending pending = cx.captureContinuation();
            // Script is paused here
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ScriptableOutputStream sos = new ScriptableOutputStream(baos, getScope());
            sos.writeObject(pending.getContinuation());
            sos.writeObject(getScope());
            String servicePayload = Base64.encodeBase64String(baos.toByteArray());
            invokeFooServiceForReal(arg, servicePayload); // This method invokes the async service
        }
    
        public static final void returnFooServiceResponse(FooResponse response, String servicePayload) {
            // De serialize the state of script
            byte[] continuationAndScope = Base64.decodeBase64(servicePayload);
            ScriptableInputStream sis = new ScriptableInputStream(new ByteArrayInputStream(continuationAndScope), getScope());
            Scriptable continuation = (Scriptable) sis.readObject();
            Scriptable scope = (Scriptable) sis.readObject();
            getContext().resumeContinuation(continuation, scope, response);
            // Script resumed
        }
    }