中断运行nashorn脚本的java线程

时间:2014-07-20 21:43:01

标签: java javascript multithreading nashorn

在下面的代码中,我有一个javascript运行在一个单独的线程中。该脚本是一个无限循环,因此它需要以某种方式终止。怎么样?

脚本开始运行后调用.cancel()不起作用。但是如果我在线程初始化之后调用.cancel(),它将终止它(注释掉的行)。

package testscriptterminate;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import java.util.Timer;
import java.util.TimerTask;

public class TestScriptTerminate extends TimerTask{

    private ExecutorService threads;
    private Future runScript;

    private Timer t;

    public TestScriptTerminate(){
        t = new Timer();
        t.schedule(this, 6000); //let the script run for a while before attempt to cancel

        threads = Executors.newFixedThreadPool(1);
        runScript = threads.submit(new ScriptExec());

        //runScript.cancel(true); //will cancel here, before the script had a change to run, but useless, i want to cancel at any time on demand
    }

    @Override
    public void run(){
        //after script has fully initialized and ran for a while - attempt to cancel.
        //DOESN'T WORK, thread still active
        System.out.println("Canceling now...");
        runScript.cancel(true);
    }

    public static void main(String[] args) {
        new TestScriptTerminate();
    }


}

class ScriptExec implements Runnable{

    private ScriptEngine js;
    private ScriptEngineManager scriptManager;

    public ScriptExec(){
        init();
    }

    @Override
    public void run() {
        try {
            js.eval("while(true){}");
        } catch (ScriptException ex) {
            System.out.println(ex.toString());
        }
    }

    private void init(){
        scriptManager = new ScriptEngineManager();
        js = scriptManager.getEngineByName("nashorn");
    }
}

4 个答案:

答案 0 :(得分:5)

所以这已经过时了,但我刚刚写了这篇文章并认为分享可能很有价值。默认情况下,你无法阻止执行Nashorn脚本,.cancel() Thread.stop() Thread.interrupt()什么也不做,但是如果你愿意付出一些努力并且可以重写一些字节码,它是可以实现的。详细说明:

http://blog.getsandbox.com/2018/01/15/nashorn-interupt/

答案 1 :(得分:3)

JavaScript(在Nashorn下)和Java一样,在紧密循环中不会响应中断。脚本需要轮询中断并自动终止循环,或者它可以调用检查中断的内容并让InterruptedException传播。

你可能认为Nashorn“只是在运行一个脚本”并且它应该立即被中断。这不适用,因为它不适用于Java:异步中断可能会损害应用程序数据结构的损坏,并且基本上没有办法避免它或从中恢复。

异步中断带来的问题与长期弃用的Thread.stop方法相同。本文档对此进行了解释,该文档是评论中链接的文档的更新版本。

Java Thread Primitive Deprecation

另请参阅Goetz,实践中的Java并发,第7章,取消和关闭

检查中断的最简单方法是致电Thread.interrupted()。您可以通过JavaScript轻松调用它。这是重写示例程序,在五秒后取消正在运行的脚本:

public class TestScriptTerminate {

    ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);

    void script() {
        ScriptEngineManager scriptManager = new ScriptEngineManager();
        ScriptEngine js = scriptManager.getEngineByName("nashorn");
        try {
            System.out.println("Script starting.");
            js.eval("while (true) { if (java.lang.Thread.interrupted()) break; }");
            System.out.println("Script finished.");
        } catch (ScriptException ex) {
            ex.printStackTrace();
        }
    }

    void init() throws Exception {
        Future<?> scriptTask = pool.submit(this::script);
        pool.schedule(() -> {
            System.out.println("Canceling now...");
            scriptTask.cancel(true);
        }, 5, TimeUnit.SECONDS);
        pool.shutdown();
    }

    public static void main(String[] args) throws Exception {
        new TestScriptTerminate().init();
    }
}

由于我们正在启动一个线程池,所以不妨使它成为一个预定的线程池,以便我们可以将它用于脚本任务和超时。这样我们就可以避免使用TimerTimerTask,而ScheduledExecutorService大部分会被InterruptedException取代。

处理和中断时的通常惯例是恢复中断位或让init()传播。 (一个人应该从不忽略一个中断。)由于断开循环可以认为已经完成了中断的处理,两者都不是必需的,只是让脚本正常退出就足够了。

这次重写还将构造函数中的大量工作转移到{{1}}方法中。这可以防止实例从构造函数中泄漏到其他线程。原始示例代码中没有明显的危险 - 事实上,几乎从来没有 - 但是避免从构造函数中泄漏实例总是很好的做法。

答案 2 :(得分:2)

不幸的是,它不适用于简单的无限循环:while (true) { }。我试过Thread.cancel();不会导致线程退出。我想在IntelliJ插件中运行脚本是万无一失的,用户可能会犯错误导致无限循环,挂起插件。

在大多数情况下,我发现唯一可以工作的是Thread.stop()。即使这样也不适用于这样的脚本:

while(true) { 
   try { 
       java.lang.Thread.sleep(100); 
   } catch (e) {  
   } 
}

javascript捕获java.lang.ThreadDeath异常并继续运行。我发现即使一个接一个地发出几个Thread.stop(),上面的样本也不可能中断。我为什么要用几个?希望其中一个将在其异常处理代码中捕获该线程并中止它。如果catch块中有一些东西像var i = "" + e;一样简单,那就足以导致第二个Thread.stop()结束它。

因此,故事的寓意是在Nashorn结束一个失控的剧本并没有失败的安全方法,但是在大多数情况下都有一些可行的方法。

我的实现发出Thread.interrupt(),然后礼貌地等待2秒钟让线程终止,如果失败则会发出Thread.stop()两次。如果这不起作用,那么也没有别的。

希望它可以帮助某人消除数小时的实验,找到一种更可靠的方法来阻止nashorn失控脚本,而不是希望运行脚本的合作能够尊重Thread.cancel()

答案 3 :(得分:1)

我遇到类似的问题,我让用户编写自己的脚本。 但在我允许执行脚本之前,我解析脚本。 如果我发现以下任何一种情况     (System.sleep.Exit,Thread.sleep,goto)等 我甚至没有启动脚本,我给用户一个错误。

然后我搜索所有     (for,loops,while,doWhile),我注入了一个方法     checkForLoop()紧跟在循环标识符之后。 我注入了checkForLoop();进入允许用户提交的脚本。

    while(users code)
    {
    }  
becomes 
    while ( checkForLoop() && users code )
    {
    }

这种方式在循环的每次迭代之前都会调用我的方法。 我可以算一下我被叫多少次或检查内部计时器。 我可以从checkForLoop();

内部停止循环或计时器

老实说,无论如何,我认为这是一个很大的安全问题,只是盲目地让用户编写脚本并执行它。 您需要构建一个将代码注入其代码循环的系统。 哪个不那么难。

您可以将100多种安全机制应用于用户提交的代码,没有规则表明您需要按原样运行代码。

我编辑了这个答案,其中包含一个非常简单的例子。

//第1步 将用户提交的JS代码放入名为userJSCode;

的Java String中

第2步 //在代码开头注入代码。

String safeWhile ="var sCount=0; var sMax=10;
function safeWhileCheck(){ sCount++;
if ( return ( sCount > sMax )}";

userJSCode = safeWhile + userJSCode;

//第3步:在代码中注入自定义

String injectSsafeWHile = "while( safeWhileCheck() && ";
userJSCode = userJSCode.replace("while(", injectSsafeWHile);

//第4步:执行自定义JS代码

nashhorn.execute(injectSsafeWHile);

//这是用户提交的错误代码,注意循环中没有增量,它会永远持续下去。

var i=0;
while ( i <1000 )
console.log("I am number " + i);

使用上述步骤我们最终

var sCount=0;var sMax=10;
function safeWhileCheck(){
sCount++; 
return ( sCount > sMax )};

var i=0;
while ( safeWhileCheck() && i <1000 )
console.log("I am number " + i)"

这里while循环最多只执行10次,所以无论你设置限制为什么。