java脚本API - 如何停止评估

spe*_*0ne 6 java api scripting

我写了一个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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

谢谢

Chr*_*ers 5

这是一些显示 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)
Run Code Online (Sandbox Code Playgroud)

这是完整的程序:

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" );
    }
} 
}
Run Code Online (Sandbox Code Playgroud)

  • 这会在 15 秒后恢复控制并出现超时异常,但后台线程仍在运行,因此如何中断它的问题仍然存在。 (2认同)
  • 您好,感谢您提供了优雅的解决方案,但即使在 Future 取消之后,后台线程仍在运行,消耗 CPU 资源。所以问题仍然存在.. (2认同)

Ale*_*ing 3

在单独的线程中运行评估,并在 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • -1 如果胎面不等待,则不会出现异常。因此,它不会以无限循环的方式工作。该代码必须检查“Thread.currentThread().interrupted()”以使您的建议起作用。 (2认同)