在下面的代码中,我有一个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");
}
}
答案 0 :(得分:5)
所以这已经过时了,但我刚刚写了这篇文章并认为分享可能很有价值。默认情况下,你无法阻止执行Nashorn脚本,.cancel()
Thread.stop()
Thread.interrupt()
什么也不做,但是如果你愿意付出一些努力并且可以重写一些字节码,它是可以实现的。详细说明:
答案 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();
}
}
由于我们正在启动一个线程池,所以不妨使它成为一个预定的线程池,以便我们可以将它用于脚本任务和超时。这样我们就可以避免使用Timer
和TimerTask
,而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次,所以无论你设置限制为什么。