【问题标题】:java scripting API - how to stop the evaluationjava scripting API - 如何停止评估
【发布时间】:2010-12-08 17:52:42
【问题描述】:

我编写了一个 servlet,它接收 java 脚本代码并对其进行处理并返回答案。为此,我使用了 java 脚本 API

在下面的代码中 if 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);
    }
}

我的问题是如何终止 eval 进程以防它花费太长时间(比如说 15 秒)?

谢谢

【问题讨论】:

    标签: java api scripting


    【解决方案1】:

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

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

    编辑:

    正如 sfussenegger 所指出的,中断不适用于脚本引擎,因为它从不休眠或进入任何等待状态以被中断。我是否可以在 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();
        }
    }
    

    【讨论】:

    • -1 如果胎面不等待,则不会出现异常。因此,它不适用于无限循环。代码必须检查 Thread.currentThread().interrupted() 才能使您的建议生效。
    • 啊,错过了那个。那只剩下我相信的已弃用的 stop() 了?
    • Thread.stop() 如果线程正在处理一些本机调用,可能会导致应用程序崩溃(在更大的环境中一直发生在我身上)。
    【解决方案2】:

    这里有一些代码显示 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<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" );
        }
    } 
    }
    

    【讨论】:

    • 这会在 15 秒后恢复控制,但会出现超时异常,但会使后台线程继续运行,因此如何中断它的问题仍然存在。
    • 呃!感谢您的评论,添加了未来的取消。
    • 嗨,感谢您提供优雅的解决方案,但即使在 Future 取消后,后台线程仍在运行消耗 CPU 资源。所以问题仍然存在..
    • Future 取消后,后台线程确实继续运行,但我注意到一段时间后它最终会被杀死。这是预期的吗?
    • 好的,如果您在处理完所有这些异常后在 finally 块中调用 executorService.shutdown() ,它会完美运行。干杯。
    【解决方案3】:

    如果您不愿意使用 Thread.stop()(您确实应该这样做),似乎无法使用 javax.script API 来满足您的要求。

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

    【讨论】:

      【解决方案4】:

      Context.observeInstructionCount 仅在解释模式下调用,因此对性能有很大影响。我当然希望犀牛团队想出更好的方法。

      【讨论】:

        【解决方案5】:

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

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

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

        现在,打电话:

        myAwesomeClass.shutdownNow();
        

        脚本将立即停止。

        【讨论】:

        • 投反对票。 “exit”调用不会退出脚本引擎,而是退出整个 JVM。
        • 我不同意;在我的项目中并非如此。脚本引擎停止,我的程序的其余部分继续在 JVM 上运行。
        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

          一个旧线程,但是我在尝试解决此问题时首先遇到的一个,所以我想在这里回答它而不是找到一个更新的线程。

          另一种避免使用 java.lang.Thread 已弃用的 stop 方法的方法是在 javascript 中抛出异常:

          throw "_EXIT";
          

          并且在脚本中捕获它,但让它落入java并在(仍在运行的)java代码中捕获它:

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

          我可以确认 nashorn exit 方法退出 java,而不仅仅是引擎,并且我已经编写了自己的 (js) 退出函数,它只是抛出 _EXIT 异常。

          P

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-08-06
            • 2010-10-04
            • 1970-01-01
            • 1970-01-01
            • 2020-08-26
            • 2012-03-04
            相关资源
            最近更新 更多