java scripting API - how to stop the evaluation
Asked Answered
G

7

11

i have writen a servlet that recives a java script code and process it and returns the answer. for that i have used the java scripting API

in the code below if script = "print('Hello, World')"; the code will end properly print "hello world". but if script = "while(true);" the script will loop endlessly.

import javax.script.*;
public class EvalScript {
    public static void main(String[] args) throws Exception {
        // create a script engine manager
        ScriptEngineManager factory = new ScriptEngineManager();
        // create a JavaScript engine
        ScriptEngine engine = factory.getEngineByName("JavaScript");
        // evaluate JavaScript code from String
        engine.eval(script);
    }
}

my question is how do i kill the eval process in case it takes too long (lets say 15 sec)?

thanks

Gareth answered 21/10, 2009 at 14:35 Comment(0)
A
6

Here's some code showing the Future implementation and the Thread.stop() one. This is an interesting problem, and it points out the need for a hook in a ScriptEngine to be able to halt whatever script it's running for whatever reason. I wonder whether this would break the assumptions in most implementations since they assume eval() will be executed in a single-threaded (blocking) environment?

Anyway, the results of executing the code below:

// exec with Thread.stop()
$ java ExecJavascript 
Java: Starting thread...
JS: Before infinite loop...
Java: ...thread started
Java: Thread alive after timeout, stopping...
Java: ...thread stopped
(program exits)

// exec with Future.cancel()
$ java ExecJavascript 1
Java: Submitting script eval to thread pool...
Java: ...submitted.
JS: Before infinite loop...
Java: Timeout! trying to future.cancel()...
Java: ...future.cancel() executed
(program hangs)

Here's the full program:

import java.util.concurrent.*;
import javax.script.*;

public class ExecJavascript
{
private static final int TIMEOUT_SEC = 5;
public static void main( final String ... args ) throws Exception 
{
    final ScriptEngine engine = new ScriptEngineManager()
        .getEngineByName("JavaScript");
    final String script = 
        "var out = java.lang.System.out;\n" +
        "out.println( 'JS: Before infinite loop...' );\n" +
        "while( true ) {}\n" +
        "out.println( 'JS: After infinite loop...' );\n";
    if ( args.length == 0 ) {
        execWithThread( engine, script );
    }
    else {
        execWithFuture( engine, script );
    }
}

private static void execWithThread( 
    final ScriptEngine engine, final String script )
{
    final Runnable r = new Runnable() {
        public void run() {
            try {
                engine.eval( script );
            }
            catch ( ScriptException e ) {
                System.out.println( 
                    "Java: Caught exception from eval(): " + e.getMessage() );
            }
        }
    };
    System.out.println( "Java: Starting thread..." );
    final Thread t = new Thread( r );
    t.start();
    System.out.println( "Java: ...thread started" );
    try {
        Thread.currentThread().sleep( TIMEOUT_SEC * 1000 );
        if ( t.isAlive() ) {
            System.out.println( "Java: Thread alive after timeout, stopping..." );
            t.stop();
            System.out.println( "Java: ...thread stopped" );
        }
        else {
            System.out.println( "Java: Thread not alive after timeout." );
        }
    }
    catch ( InterruptedException e ) {
        System.out.println( "Interrupted while waiting for timeout to elapse." );
    }
}

private static void execWithFuture( final ScriptEngine engine, final String script )
    throws Exception
{
    final Callable<Object> c = new Callable<Object>() {
        public Object call() throws Exception {
            return engine.eval( script );
        }
    };
    System.out.println( "Java: Submitting script eval to thread pool..." );
    final Future<Object> f = Executors.newCachedThreadPool().submit( c );
    System.out.println( "Java: ...submitted." );
    try {
        final Object result = f.get( TIMEOUT_SEC, TimeUnit.SECONDS );
    }
    catch ( InterruptedException e ) {
        System.out.println( "Java: Interrupted while waiting for script..." );
    }
    catch ( ExecutionException e ) {
        System.out.println( "Java: Script threw exception: " + e.getMessage() );
    }
    catch ( TimeoutException e ) {
        System.out.println( "Java: Timeout! trying to future.cancel()..." );
        f.cancel( true );
        System.out.println( "Java: ...future.cancel() executed" );
    }
} 
}
Avunculate answered 21/10, 2009 at 15:6 Comment(6)
This resumes control after 15 seconds with a timeout exception, but leaves the background thread running, so the question of how to interrupt it remains.Spoon
Duh! Thanks for the comment, added the Future cancellation.Avunculate
hi, thanks for the elegant solution, but even after the Future cancellation the background thread is still running consuming the CPU resources. so the question still remains..Gareth
After Future's cancellation it's true the background thread keeps running, but I've noticed after a while it gets killed eventually. Is this expected?Iridectomy
OK, it works perfectly if you call executorService.shutdown() in a finally block after handling all those exceptions. Cheers.Iridectomy
@TiagoFernandez executorService.shutdown() doesn't solve the problem. From docs.oracle.com/javase/7/docs/api/java/util/concurrent/…, "There are no guarantees beyond best-effort attempts to stop processing actively executing tasks. For example, typical implementations will cancel via Thread.interrupt(), so any task that fails to respond to interrupts may never terminate." My threads don't terminate, even after calling shutdown() or shutdownNow().Aixenprovence
S
3

Run the evaluation in a separate thread and interrupt it after 15s using Thread.interrupt(). This will stop the eval and throw an InterruptedException, which you can catch and return a failure status.

A better solution would be to have some sort of asynch interface to the scripting engine, but as far as I could see this does not exist.

EDIT:

As sfussenegger pointed out, interrupting does not work with the script engine, since it never sleeps or enters any wait state to get interrupted. Niether could I find any periodical callback in the ScriptContext or Bindings objects which could be used as a hook to check for interruptions. There is one method which does work, though : Thread.stop(). It is deprecated and inherently unsafe for a number of reasons, but for completeness I will post my test code here along with Chris Winters implementation for comparison. Chris's version will timeout but leave the background thread running, the interrupt() does nothing and the stop() kills the thread and resumes control to the main thread:

import javax.script.*;
import java.util.concurrent.*;

class ScriptRunner implements Runnable {

    private String script;
    public ScriptRunner(String script) {
            this.script = script;
    }

    public ScriptRunner() {
            this("while(true);");
    }

    public void run() {
            try {
            // create a script engine manager
            ScriptEngineManager factory = new ScriptEngineManager();
            // create a JavaScript engine
            ScriptEngine engine = factory.getEngineByName("JavaScript");
            // evaluate JavaScript code from String
            System.out.println("running script :'" + script + "'");
            engine.eval(script);
            System.out.println("stopped running script");
            } catch(ScriptException se) {
                    System.out.println("caught exception");
                    throw new RuntimeException(se);
            }
            System.out.println("exiting run");
    }
}

public class Inter {

    public void run() {
            try {
             Executors.newCachedThreadPool().submit(new ScriptRunner()).get(15, TimeUnit.SECONDS);
            } catch(Exception e) {
                    throw new RuntimeException(e);
            }
    }

    public void run2() {
            try {
            Thread t = new Thread(new ScriptRunner());
            t.start();
            Thread.sleep(1000);
            System.out.println("interrupting");
            t.interrupt();
            Thread.sleep(5000);
            System.out.println("stopping");
            t.stop();
            } catch(InterruptedException ie) {
                    throw new RuntimeException(ie);
            }
    }

    public static void main(String[] args) {
            new Inter().run();
    }
}
Spoon answered 21/10, 2009 at 14:43 Comment(3)
-1 If the tread isn't waiting, there won't be an Exception. Hence, it won't work with an endless loop. The code would have to check Thread.currentThread().interrupted() for your suggestions to work.Artist
Argh, missed that one. That leaves only the deprecated stop() i believe?Spoon
Thread.stop() might cause application to crash (Happens with me consistently in a larger enviornment) if that thread is processing some native calls.Occupation
M
3

If you are unwilling to use Thread.stop() (and you really should be), there seem to be no way to achieve your requirement with the javax.script API.

If you use the Rhino engine directly and actual performance is not too important, you can implement a hook in Context.observeInstructionCount to interrupt or prematurely terminate script execution. The hook is invoked for each executed JavaScript instruction after you reach the threshold (instruction count) set with setInstructionObserverThreshold. You need to measure execution time yourself, since you are only provided the number of instructions executed and this may have a relevant performance impact. I'm not sure, but the hook may also only be invoked if the script engine runs in interpreted mode and not if the JavaScript code is compiled.

Mccarty answered 21/10, 2009 at 15:56 Comment(0)
I
1

Context.observeInstructionCount is only called in interpreted mode, so that's a significant performance hit. I sure hope the Rhino team comes up with a better way.

Incomputable answered 30/3, 2013 at 2:43 Comment(0)
S
-1

I know this is an older thread, but I have a more direct solution for stopping the JavaScript eval: Invoke the "exit" function that Nashorn provides.

Inside the class I use to run the script engine, I include:

private Invocable invocable_ = null;
private final ExecutorService pool_ = Executors.newFixedThreadPool(1);  

public boolean runScript(String fileName)
    {
    pool_.submit(new Callable<Boolean>() 
        {       
        ScriptEngine engine 
            = new ScriptEngineManager().getEngineByName("nashorn");
        public Boolean call() throws Exception
            {
            try
                {
                invocable_ = (Invocable)engine;
                engine.eval(
                        new InputStreamReader(
                                new FileInputStream(fileName), 
                                Charset.forName("UTF-8")) );
                return true;
                }
            catch (ScriptException ex)
                {
                ...
                return false;
                } 
            catch (FileNotFoundException ex)
                {
                ...
                return false;
                }                   
            }
        });

    return true;
    }

public void
shutdownNow()
    {

    try
        {
        invocable_.invokeFunction("exit");
        } 
    catch (ScriptException ex)
        {
        ...
        } 
    catch (NoSuchMethodException ex)
        {
        ...
        }

    pool_.shutdownNow();
    invocable_ = null;
    }

Now, call:

myAwesomeClass.shutdownNow();

The script will stop immediately.

Surfactant answered 30/5, 2015 at 19:10 Comment(2)
Downvoting. The "exit" call doesn't exit the script engine but the whole JVM.Sheol
I disagree; that wasn't the case in my project. The script engine stopped, the rest of my program continued running on the JVM.Surfactant
O
-1

Nashorn scripts are compiled to .class "files" & loaded on the fly. So, script evaluation is similar to loading a compiled Java .class and running the same. Unless you program explicitly for interruption, you can't stop script evaluation. There is no "script interpreter" that "polls" for interrupt status. You've to explicitly call Thread.sleep or other Java API that be interrupted from another thread.

Overeager answered 20/1, 2016 at 3:1 Comment(0)
B
-1

An old thread but the one I came across first when trying to solve this issue so I would like to answer it here rather than find a more recent one.

Another way that avoids using java.lang.Thread's deprecated stop method is to throw an exception in the javascript:

throw "_EXIT";

and not catch it in the script but let it fall through to java and catch it in (the still running) java code:

try{
   eval(script);
}catch(ScriptException ex) {
    if(ex.getMessage().startsWith("_EXIT")) {
        // do nothing but exit nashorn
    } else {
        // handle the exception
    }
}

I can confirm the nashorn exit method exits java, not just the engine, and I have written my own (js) exit function that simply throws the _EXIT exception.

P

Burton answered 29/3, 2020 at 11:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.