java scripting API – 如何停止评估

我编写了一个servlet,它收集了一个java脚本代码并处理它并返回答案。 因为我使用了java脚本API

在下面的代码中,如果script =“print(’Hello,World’)”; 代码将正确打印“hello world”。 但如果script =“while(true);” 脚本将无休止地循环。

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); } } 

我的问题是,如果它需要太长时间(比如15秒),我如何杀死eval过程?

谢谢

在单独的线程中运行评估,并在15秒后使用Thread.interrupt()中断它。 这将停止eval并抛出InterruptedException,您可以捕获并返回失败状态。

更好的解决方案是为脚本引擎提供某种异步接口,但据我所知,这不存在。

编辑:

正如sfussenegger指出的那样,中断对脚本引擎不起作用,因为它从不hibernate或进入任何等待状态以中断。 我可以在ScriptContext或Bindings对象中找到任何定期回调,这些回调可以用作检查中断的钩子。 但是有一种方法可以工作:Thread.stop()。 由于多种原因,它已被弃用并且本质上不安全,但为了完整性,我将在此发布我的测试代码以及​​Chris Winters实现以进行比较。 Chris的版本将超时,但后台线程保持运行,interrupt()不执行任何操作,stop()终止线程并恢复对主线程的控制:

 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(); } } 

这里有一些代码显示了Future实现和Thread.stop()。 这是一个有趣的问题,它指出在ScriptEngine中需要一个钩子,以便能够停止它出于任何原因运行的任何脚本。 我想知道这是否会打破大多数实现中的假设,因为他们假设eval()将在单线程(阻塞)环境中执行?

无论如何,执行以下代码的结果:

 // 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) 

这是完整的程序:

 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 c = new Callable() { public Object call() throws Exception { return engine.eval( script ); } }; System.out.println( "Java: Submitting script eval to thread pool..." ); final Future 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" ); } } } 

如果你不愿意使用Thread.stop()(你真的应该这样做),似乎没有办法用javax.script API来实现你的需求。

如果直接使用Rhino引擎并且实际性能不是很重要,则可以在Context.observeInstructionCount中实现一个钩子来中断或过早终止脚本执行。 在达到使用setInstructionObserverThreshold设置的阈值(指令计数)后,将为每个执行的JavaScript指令调用该挂钩。 您需要自己测量执行时间,因为您只提供了执行的指令数量,这可能会对性能产生相关影响。 我不确定,但是如果脚本引擎以解释模式运行而不是编译JavaScript代码,则也可以仅调用钩子。

Context.observeInstructionCount仅在解释模式下调用,因此性能受到重大影响。 我当然希望Rhino团队能够找到更好的方法。

Nashorn脚本编译为.class“文件”并即时加载。 因此,脚本评估类似于加载已编译的Java .class并运行相同的。 除非您明确编程中断,否则无法停止脚本评估。 没有“脚本解释器”可以“轮询”中断状态。 您必须显式调用Thread.sleep或其他从另一个线程中断的Java API。

我知道这是一个较旧的线程,但我有一个更直接的解决方案来停止JavaScript eval:调用Nashorn提供的“退出”function。

在我用来运行脚本引擎的类中,我包括:

 private Invocable invocable_ = null; private final ExecutorService pool_ = Executors.newFixedThreadPool(1); public boolean runScript(String fileName) { pool_.submit(new Callable() { 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; } 

现在,致电:

 myAwesomeClass.shutdownNow(); 

该脚本将立即停止。