Stopping the Rhino Engine in middle of execution
Asked Answered
H

4

7

Does the Rhino engine have an api that can stop the execution of a script fie in the middle. For example, I have a script file in which there is an infinte loop. How can I stop the execution in the middle?

Of course, I can stop the jvm which started the Rhino engine to excecute the script. But I don't want to kill the entire jvm session for that reason as i have started the script programatically and the Rhino Engine also running in the same JVM as my application.

Hutcherson answered 20/4, 2012 at 12:13 Comment(0)
H
7

Stopping the execution of running JavaScript can be done by using following way.

1) Create a dummy Debugger and attach it to the context created initially.

mContext = Context.enter();
ObservingDebugger observingDebugger = new ObservingDebugger();
mContext.setDebugger(observingDebugger, new Integer(0));
mContext.setGeneratingDebug(true);
mContext.setOptimizationLevel(-1);

The ObservingDebugger code looks as follows.

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.debug.DebugFrame;
import org.mozilla.javascript.debug.DebuggableScript;
import org.mozilla.javascript.debug.Debugger;

public class ObservingDebugger implements Debugger 
{
boolean isDisconnected = false;

private DebugFrame debugFrame = null;

public boolean isDisconnected() {
    return isDisconnected;
}

public void setDisconnected(boolean isDisconnected) {
    this.isDisconnected = isDisconnected;
    if(debugFrame != null){
       ((ObservingDebugFrame)debugFrame).setDisconnected(isDisconnected);
    }
}

public ObservingDebugger() {

}

public DebugFrame getFrame(Context cx, DebuggableScript fnOrScript)
{
    if(debugFrame == null){
        debugFrame = new ObservingDebugFrame(isDisconnected);
    }
    return debugFrame;      
}

@Override
public void handleCompilationDone(Context arg0, DebuggableScript arg1, String arg2) {   } }
// internal ObservingDebugFrame class
class ObservingDebugFrame implements DebugFrame
   {
boolean isDisconnected = false;

public boolean isDisconnected() {
    return isDisconnected;
}

public void setDisconnected(boolean isDisconnected) {
    this.isDisconnected = isDisconnected;
}

ObservingDebugFrame(boolean isDisconnected)
{
    this.isDisconnected = isDisconnected;
}

public void onEnter(Context cx, Scriptable activation,
        Scriptable thisObj, Object[] args)
{ }

public void onLineChange(Context cx, int lineNumber) 
{
    if(isDisconnected){
        throw new RuntimeException("Script Execution terminaed");
    }
}

public void onExceptionThrown(Context cx, Throwable ex)
{ }

public void onExit(Context cx, boolean byThrow,
        Object resultOrException)
{ }

@Override
public void onDebuggerStatement(Context arg0) { } }

ObservingDebugger class will manage the boolean variable "isDisconnected" and when user clicks on the stop button (wants to stop the execution) then this variable is set to true. Once the variable is set to true as follows the Rhino Execution will immediately terminates.

observingDebugger.setDisconnected(true);
Hutcherson answered 26/4, 2012 at 10:48 Comment(0)
C
4

For anyone else looking for a solution, the javadoc for ContextFactory details how to stop a script running more than 10 seconds:

https://github.com/mozilla/rhino/blob/master/src/org/mozilla/javascript/ContextFactory.java

Cicero answered 23/4, 2013 at 21:22 Comment(3)
The custom ContextFactory approach works brilliantly. If you have issues reading the javadoc from the java there's a HTML version here: jarvana.com/jarvana/view/org/mozilla/rhino/1.7R3/…Slip
I am wondering about the performance impact of this and the debugger method. Any ideas?Unguinous
I would imagine the debugger method has some impact on performance since the context is generating debug information. The ContextFactory solution simply fires after a certain number of instructions have been executed.Cicero
H
1

I execute scripts in a new thread using an ExecutorService and timed-out future.get

    ExecutorService executor = Executors.newSingleThreadExecutor();

    Future<?> future = executor.submit(threadEvaluation);

    try {
        System.out.println("Started..");
        future.get(100, TimeUnit.MILLISECONDS);
        System.out.println("Finished!");
    } catch (TimeoutException e) {
        future.cancel(true);
        System.out.println("Terminated!");
    }

Notice that this approach will not stop the thread executing the script! In order to do so, as the thread executing your script will be notified to be interrupted, you can create a custom ContextFactory that monitors periodically this situation:

public class InterruptableContextFactory extends ContextFactory {

    public static boolean initialized = false;

    public static void init() {
        if (!initialized) {
            ContextFactory.initGlobal(new InterruptableContextFactory());
            initialized = true;
        }
    }

    @Override
    protected void observeInstructionCount(Context cx, int instructionCount) {
        System.out.println(instructionCount + " javascript instructions!");
        if (Thread.currentThread().isInterrupted()) {
            throw new Error("script execution aborted");
        }
    }

    @Override
    protected Context makeContext() {
        Context cx = super.makeContext();
        //set a number of instructions here
        cx.setInstructionObserverThreshold(10000);
        return cx;
    }
}

Before creating any Context object, you need to configure your application to use this ContextFactory as default, just invoke

InterruptableContextFactory.init()

Inside your Callable's call method, you can capture the Error:

    try {
        cx.setOptimizationLevel(9);
        cx.setInstructionObserverThreshold(10000);
        ScriptableObject scope = cx.initStandardObjects();

        // your code here

    } catch (Error e) {
        System.out.println("execution was aborted: " + e.getMessage());
    } finally {
        Context.exit();
    }
Housebound answered 13/11, 2015 at 17:24 Comment(0)
F
-2

The Rhino engine doesn't appear to have any mechanism for doing this (bad Rhino!) and it's hard to tell whether it creates threads internally, so the only solution you've got is to create a ThreadGroup, load and execute the Rhino engine and its script from inside a thread in that group, and when you want to kill it off, use ThreadGroup.stop(). Yes, it's deprecated but there's really no other way to do it given that there's no cooperation on the part of the Rhino library.

Fulguration answered 20/4, 2012 at 12:33 Comment(2)
@ Donal: This might probably not work because once i create new thread and start loading and executing the Rhino Engine from that once the evaluateReader() method on the Context is called the thread execution completes and Rhino engine internally manages the execution.Hutcherson
That's why I suggested put it in its own thread group; any other threads created by Rhino will still be in that thread group (or a child thread group) so it will still be possible to kill them all.Fulguration

© 2022 - 2024 — McMap. All rights reserved.