Interpreting JavaScript in Java with Rhino: pausing/resuming scripts
Asked Answered
C

3

16

I'm using the javax.script.* package of the JDK. Specifically, I'm using the JavaScript engine, which, from what I've read, seems to be based on a Mozilla-developed JavaScript-in-Java interpreter called Rhino.

What I'm hoping to accomplish is to basically have my JavaScript able to "pause" itself at a certain point in the code (say, halfway through a function call) and only resume itself later when Java allows it to do so.

To illustrate what I mean, imagine this JavaScript code:

function myJSFunction() {
    print("Hello ");
    mysteriousPauseFunction(); // this is the part I'm wondering about.  basically, the script should break here and resume later at Java's discretion...
    // upon reaching this comment, we know now that Java has told JavaScript that it's okay to resume, so the next line will now be executed...
    print("world");
}

If the "pausing"/"breaking" part involves binding a Java function and passing it a reference to the current ScriptEngine or whatever, that's cool with me. I'm thinking that's what this will probably involve: pausing the JavaScript from within Java.

I did some googling and found that the keyword here appears to be "continuations." From what I can tell, Rhino only supports continuations in interpreted mode (versus compiled mode), which I see is accomplished by setting the "context" to -2. Since the built-in JDK ScriptEngine doesn't seem to mention anything about contexts (or maybe I'm missing it), does this mean I have to download and use Mozilla's Rhino library directly instead?

And are Rhino continuations what I need to accomplish this? I've found a useful tutorial on Rhino continuations, but after reading through it, I'm not 100% sure if this is going to be able to accomplish what I described above. If this is what I'm looking for, then my follow-up question is about the "serialization" mentioned: does this mean that when I resume my script, all variables will have been unset unless I serialize them?

Update: It looks like this IS possible with Rhino. Here's what I have so far in my JavaScript; after the code, I'll explain what it does...

var end = new Continuation();

function myJSFunction()
{
    print("Hello ");
    var kont = new Continuation();
    storePause(script, kont); // script is previously bound by Java into the JavaScript.  it is a reference to the script itself.
    end();
    print("world");

}

My "storePause()" function is a Java function which I have written, and it is bound to the JavaScript, but right now, it doesn't do anything. My next goal will be to flesh out its code such that it stores the continuation and script information as Java objects, so that Java can resume the script later.

Right now, what it's doing is pausing/"breaking" the script after "Hello " is printed but before "world" is printed, so this proves to me that it is possible to pause a script this way.

So, all that I should have left to figure out at this point is how to resume a continuation. Note that the above works using the JDK scripting engine by default (I haven't needed to worry about interpreted mode vs compiled mode at this point -- it seems to default to interpreted mode), but it looks like the process of resuming a script will require Mozilla's Rhino library.

Cyrillus answered 23/12, 2011 at 13:13 Comment(0)
C
5

Alright, it took me many hours of digging through documentation, tutorials, and examples, and also posting on here and on the Rhino Google Group, but I've managed to compile a working solution. Since there seems to be no complete example, I'll post my findings here for anyone who stumbles across this in the future.

Actually, my findings are probably too long to post here, so I decided to write up a tutorial on my blog:

Tutorial: Continuations in Mozilla Rhino (a JavaScript interpreter for Java)

Hope that helps someone. As far as I know, this is the only complete Rhino tutorial that shows how to do all of the following: initialize Rhino, load a script from a JavaScript (*.js) file, automatically bind all of the functions in a particular Java class (e.g. ScriptFunctions) as global functions in JavaScript, and finally call a JavaScript function and handle continuations for that call.

Basically, the problem was that I needed to first download the Mozilla Rhino source code (because the version packed in with the JDK is outdated and doesn't support continuations), rewrite all of my code to use the official Rhino package's syntax (it is very different from JDK's ScriptingEngine syntax), write a Java function that throws a ContinuationPending exception and bind it to JavaScript so JavaScript can call it (because throwing a ContinuationPending directly from JavaScript results in a JavaScriptException being thrown, not a ContinuationPending being thrown, and even trying to call getCause() on that JavaScriptException results in null), and then in my Java code that calls my JavaScript function ("myJSFunction" in my original example), have try/catch blocks to check for a ContinuationPending (which is an exception), and then use that ContinuationPending later to resume the script.

Phew. It was tough, but it's all worth it now.

Cyrillus answered 26/12, 2011 at 13:57 Comment(2)
The blog site is not responding.Francophile
@JesseGlick thanks for the heads-up. Looks like I need to move my site to a new web server or something. In the meantime, here's an archive.org version: web.archive.org/web/20130730231256/http://www.joshforde.com/…Cyrillus
A
0

You didn't explain why you were doing this, but I was emulating a program that interacts with an end user, like this:

print('Hello!'); 
a=Number(input('enter a number')); 
b=Number(input('and another number')); 
print('the sum of '+a+' plus '+b+' is '+(a+b))

I've got it working just by creating a print and an input function in javascript that checks for program state.

you can see a demo here.

it's all written in javascript so you can look at the source code with any browser.

Hope it helps

Adiaphorous answered 30/9, 2013 at 0:41 Comment(0)
G
-1

You could use wait/notify:

public final class Pause {
  private final Object lock = new Object();

  public void await() throws InterruptedException {
    synchronized (lock) {
      lock.wait();
    }
  }

  public void resumeAll() {
    synchronized (lock) {
      lock.notifyAll();
    }
  }
}

Usage:

final Pause pause = new Pause();

class Resumer implements Runnable {
  @Override public void run() {
    try {
      Thread.sleep(5000);
      pause.resumeAll();
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
    }
  }
}
new Thread(new Resumer()).start();

SimpleBindings bindings = new SimpleBindings();
bindings.put("pause", pause);
String script = "print('Hello, ');\n"
              + "pause.await();\n"
              + "println('ECMAScript!');\n";
new ScriptEngineManager().getEngineByName("ECMAScript")
                         .eval(script, bindings);

This is a relatively simplistic solution as you don't mention any other constraints. wait() causes the thread to block, which would not be acceptable in all environments. There is also no easy way to identify what threads are waiting on the Pause instance if you want to run scripts concurrently.

Note: the InterruptedException on await() should be handled either by the caller or by doing something more sensible in await().

Ganesa answered 24/12, 2011 at 10:34 Comment(1)
Thanks, but what I'm looking to utilize is Rhino's actual "continuations" functionality. After days of research and debugging, I've finally found how to accomplish this, so I'll post an answer for anyone looking for this information in the future.Cyrillus

© 2022 - 2024 — McMap. All rights reserved.