java脚本API - 如何停止评估

时间:2009-10-21 14:35:59

标签: java api scripting

我已经编写了一个servlet,它可以重现java脚本代码并对其进行处理并返回答案。因为我使用了java脚本API

如果脚本=“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过程?

感谢

7 个答案:

答案 0 :(得分:3)

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

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

编辑:

正如sfussenegger指出的那样,中断对脚本引擎不起作用,因为它从不睡眠或进入任何等待状态以中断。 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();
    }
}

答案 1 :(得分:3)

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:0)

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

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

避免使用java.lang.Thread不推荐使用的stop方法的另一种方法是在javascript中引发异常:

throw "_EXIT";

not 在脚本中捕获它,但让它进入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

答案 6 :(得分:-1)

我知道这是一个较老的线程,但我有一个更直接的解决方案来停止JavaScript eval:调用&#34;退出&#34; 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();

脚本会立即停止。