Java scripting API - how to stop evaluation

I have a servlet file that returns java script code and processes it and returns a response. for this i used java scripting API

in the code below if script = "print ('Hello, World')"; the code will end up typing "hello world" correctly. 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 can I kill the eval process if it takes too long (say 15 seconds)?

thanks

+6
source share
6 answers

Run the evaluation in a separate thread and abort it after 15 seconds with Thread.interrupt (). This will stop eval and raise InterruptedException, which you can catch and return the failure status.

The best solution would be to have some kind of asynchronous interface for the scripting engine, but as far as I could see, this does not exist.

EDIT:

As sfussenegger noted, interruption does not work with the script engine, as it never sleeps or goes into any wait state to interrupt. Niether could find some sort of periodic callback in ScriptContext or Bindings that could be used as a hook for checking interrupts. There is one method that really works: Thread.stop (). It is outdated and inherently unsafe for a number of reasons, but for completeness I will post my test code here along with the implementation of Chris Winters for comparison. The Chris version will timeout, but leave the background thread running, interrupt () does nothing, and 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(); } } 
+3
source

Here is the code showing the implementation of Future and Thread.stop (). This is an interesting problem, and it indicates the need for a hook in ScriptEngine to be able to stop any script from working for any reason. I wonder if this will violate the assumptions in most implementations, since they assume that eval() will execute in a single-threaded (blocking) environment?

In any case, the results of the code below are:

 // 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" ); } } } 
+3
source

If you don't want to use Thread.stop () (and you really should be), there seems to be no way to achieve your requirement using the javax.script API.

If you use the Rhino engine directly, and the actual performance is not too important, you can implement a hook in Context.observeInstructionCount to interrupt or prematurely terminate the script. A hook is called for each executable JavaScript instruction after reaching the threshold (number of commands) set using setInstructionObserverThreshold. You must measure the execution time yourself, since you are only provided with the number of instructions executed, and this may have a corresponding effect on performance. I'm not sure, but hook can also be called only if the script engine is running in interpreted mode, and not when compiling JavaScript code.

+2
source

Context.observeInstructionCount is only called in interpretation mode, so there is significant performance. I'm sure the Rhino team will come up with a better way.

0
source

Nashorn scripts are compiled into .class “files” and loaded on the fly. Thus, evaluating the script is similar to loading a compiled Java.class and doing the same thing. If you are not programming explicitly for interruption, you cannot stop evaluating the script. There is no “interpreter script” that polls for interrupt status. You must explicitly call Thread.sleep or another Java API to be interrupted from another thread.

0
source

I know this is an old thread, but I have a more direct solution to stop JavaScript eval: the "exit" function that Nashorn provides is called.

Inside the class that 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.

-one
source

All Articles