Interrupt java thread running nashorn script

六月ゝ 毕业季﹏ 提交于 2019-12-03 00:25:37

JavaScript (under Nashorn), like Java, will not respond to an interrupt in the middle of a tight loop. The script needs to poll for interruption and terminate the loop voluntarily, or it can call something that checks for interruption and let InterruptedException propagate.

You might think that Nashorn is "just running a script" and that it should be interrupted immediately. This doesn't apply, for the same reason that it doesn't apply in Java: asynchronous interruption risks corruption of the application's data structures, and there is essentially no way to avoid it or recover from it.

Asynchronous interruption brings in the same problems as the long-deprecated Thread.stop method. This is explained in this document, which is an updated version of the document linked in the comments.

Java Thread Primitive Deprecation

See also Goetz, Java Concurrency In Practice, Chapter 7, Cancellation and Shutdown.

The easiest way to check for interruption is to call Thread.interrupted(). You can call this quite easily from JavaScript. Here's a rewrite of the example program that cancels the running script after five seconds:

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

Since we're starting up a thread pool, might as well make it a scheduled thread pool so that we can use it for both the script task and the timeout. That way we can avoid Timer and TimerTask, which are mostly replaced by ScheduledExecutorService anyway.

The usual convention when handling and interrupt is either to restore the interrupt bit or to let an InterruptedException propagate. (One should never ignore an interrupt.) Since breaking out of the loop can be considered to have completed the handling of the interrupt, neither is necessary, and it seems sufficient simply to let the script exit normally.

This rewrite also moves a lot of work out of the constructor into an init() method. This prevents the instance from being leaked to other threads from within the constructor. There is no obvious danger from this in the original example code -- in fact, there almost never is -- but it's always good practice to avoid leaking the instance from the constructor.

So this is old, but i just wrote this up and thought it would probably be valuable to share. By default there is ~nothing you can do to stop a Nashorn script executing, .cancel() Thread.stop() Thread.interrupt() do nothing, but if you are willing to put in a bit of effort and are ok with rewriting some bytecode, it is achieveable. Details:

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

Unfortunately it does not work for simple infinite loops: while (true) { }. I tried Thread.cancel(); does not cause the thread to exit. I wanted something foolproof for running scripts in an IntelliJ plugin where a user can make a mistake an cause an infinite loop, hanging the plugin.

The only thing I found to work in most cases is Thread.stop(). Even that does not work for a script like this:

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

javascript catches the java.lang.ThreadDeath exception and keeps going. I found that the above sample is impossible to interrupt even with several Thread.stop() issued one after the other. Why would I use several? Hoping that one of them will catch the thread in its exception processing code and abort it. Which does work if there is something in the catch block to process as simple as var i = "" + e; that is enough to cause the second Thread.stop() to end it.

So the moral of the story is there is no fail safe way of ending a runaway script in Nashorn, but there is something that will work on most cases.

My implementation issues a Thread.interrupt(), then politely waits 2 seconds for the thread to terminate and if that fails then it issues Thread.stop() twice. If that does not work, then nothing else will either.

Hope it helps someone eliminate hours of experimentation to find a more reliable method to stop nashorn runaway scripts than hoping on the cooperation of the running script to respect Thread.cancel().

I have a similar problem where I let users write their own scripts. But before I allow the script to be executed, I parse the script. and if I find any of the following (System.sleep. Exit, Thread.sleep, goto) etc I don't even start the script, and I give user an error.

and then I do a search for all (for,loops, while, doWhile), and I inject a method.
checkForLoop() just after the loop identifier. I inject checkForLoop(); into allow user submitted script.

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

This way before every iteration of their loop, my method is called. and I can count how many times I was called or check internal timers. Than I can stop the loops or timers from inside checkForLoop();

Honestly I think its a big security issue anyway, just to blindly let users write script and just execute it. You need to build in a system that injects your code into their code loops. Which is not that hard.

There are 100s of safety mechanisms you can apply to users submitted code, there is no RULE that says you need to run their code as is.

I have edited this answer to include a very simple example.

//Step 1 put the users submitted JS code into a Java String called userJSCode;

Step 2 //inject code at the start of their code.

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

userJSCode = safeWhile + userJSCode;

//Step 3: inject the custom while code

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

//Step 4: execute custom JS code

nashhorn.execute(injectSsafeWHile);

//Here is users bad submitted code, note no i increment in the loop, it would go on for ever.

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

using the steps above we end up with

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)"

Here the while loop only executes a max of 10 times, so whatever you set the limit to.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!