java脚本API-如何停止评估

java脚本API-如何停止评估,java,api,scripting,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) t

我编写了一个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秒),我如何终止评估过程


谢谢

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

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

编辑:

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

下面是一些显示未来实现和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" );
    }
} 
}
import java.util.concurrent.*;
导入javax.script.*;
公共类可执行JavaScript
{
私有静态最终整数超时_SEC=5;
公共静态void main(最终字符串…args)引发异常
{
final ScriptEngine=new ScriptEngineManager()
.getEngineByName(“JavaScript”);
最终字符串脚本=
“var out=java.lang.System.out;\n”+
“out.println('JS:Before infinite loop…');\n”+
“while(true){}\n”+
“out.println('JS:After infinite loop…');\n”;
如果(args.length==0){
execWithThread(引擎、脚本);
}
否则{
execWithFuture(引擎、脚本);
}
}
私有静态无效execWithThread(
最终脚本引擎(最终字符串脚本)
{
最终可运行r=新可运行(){
公开募捐{
试一试{
engine.eval(脚本);
}
捕获(脚本异常){
System.out.println(
“Java:从eval()捕获异常:”+e.getMessage());
}
}
};
println(“Java:开始线程…”);
最终螺纹t=新螺纹(r);
t、 start();
println(“Java:…线程已启动”);
试一试{
Thread.currentThread().sleep(超时秒*1000);
if(t.isAlive()){
println(“Java:Thread-alive-after-timeout,stopping…”);
t、 停止();
System.out.println(“Java:…线程停止”);
}
否则{
println(“Java:线程在超时后不活动。”);
}
}
捕捉(中断异常e){
System.out.println(“在等待超时时间过去时中断”);
}
}
私有静态void execWithFuture(最终脚本引擎、最终字符串脚本)
抛出异常
{
最终可调用c=新可调用(){
公共对象调用()引发异常{
返回引擎.eval(脚本);
}
};
println(“Java:将脚本评估提交到线程池…”);
最终未来f=Executors.newCachedThreadPool().submit(c);
System.out.println(“Java:…已提交”);
试一试{
最终对象结果=f.get(超时秒,TimeUnit.SECONDS);
}
捕捉(中断异常e){
println(“Java:在等待脚本时被中断…”);
}
捕获(执行例外){
println(“Java:Script抛出异常:”+e.getMessage());
}
捕获(超时异常e){
System.out.println(“Java:Timeout!正在尝试future.cancel()…”;
f、 取消(真);
System.out.println(“Java:…future.cancel()已执行”);
}
} 
}

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


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

Context.observeInstructionCount仅在解释模式下调用,因此这是一个显著的性能影响。我当然希望Rhino团队能想出一个更好的方法。

我知道这是一个较老的线程,但我有一个更直接的停止JavaScript评估的解决方案:调用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();
throw "_EXIT";
try{
   eval(script);
}catch(ScriptException ex) {
    if(ex.getMessage().startsWith("_EXIT")) {
        // do nothing but exit nashorn
    } else {
        // handle the exception
    }
}