Lua / Java / LuaJ – 处理或中断无限循环和线程

我正在使用LuaJ在Java中运行用户创建的Lua脚本。 但是,运行永不返回的Lua脚本会导致Java线程冻结。 这也使线程不可中断。 我运行Lua脚本:

JsePlatform.standardGlobals().loadFile("badscript.lua").call(); 

badscript.lua包含while true do end

我希望能够自动终止陷入不屈的循环的脚本,并允许用户在运行时手动终止他们的Lua脚本。 我已经阅读了有关debug.sethook和pcall的内容 ,但我不确定我是如何正确地将它们用于我的目的。 我也听说沙盒是一个更好的选择,虽然这有点超出我的范围。

这个问题也可能仅扩展到Java线程。 我没有找到关于在一段while (true);中断Java线程的任何确切信息while (true);

在线Lua演示是非常有前途的,但似乎“坏”脚本的检测和终止是在CGI脚本而不是Lua中完成的。 我是否可以使用Java调用CGI脚本,后者又调用Lua脚本? 不过,我不确定是否允许用户手动终止他们的脚本。 我丢失了Lua演示源代码的链接,但我手头有它。 这是神奇的路线:

 tee -a $LOG | (ulimit -t 1 ; $LUA demo.lua 2>&1 | head -c 8k) 

有人能指出我正确的方向吗?

一些来源:

  • 嵌入式Lua – 超时流氓脚本(例如无限循环) – 任何人的例子?
  • 防止Lua无限循环
  • 嵌入式Lua – 超时流氓脚本(例如无限循环) – 任何人的例子?
  • 当一些循环执行长任务时如何中断线程?
  • 在Java中指定的时间限制后杀死线程

我在同一个问题上苦苦挣扎,经过一些挖掘调试库的实现后,我创建了一个类似于David Lewis提出的解决方案,但是通过提供我自己的DebugLibrary来实现:

 package org.luaj.vm2.lib; import org.luaj.vm2.LuaValue; import org.luaj.vm2.Varargs; public class CustomDebugLib extends DebugLib { public boolean interrupted = false; @Override public void onInstruction(int pc, Varargs v, int top) { if (interrupted) { throw new ScriptInterruptException(); } super.onInstruction(pc, v, top); } public static class ScriptInterruptException extends RuntimeException {} } 

只需从新线程内部执行脚本,并将interrupted设置为true即可停止执行。 该exception将被封装为抛出时引发LuaError的原因。

有问题,但这对回答你的问题还有很长的路要走。

以下概念validation演示了任意用户代码的沙盒和限制的基本级别。 它运行约250条精心设计的“用户输入”指令,然后丢弃协程。 您可以使用类似于本答案中的一种机制来查询Java并有条件地在钩子函数内生成,而不是每次都屈服。

SandboxTest.java:

 public static void main(String[] args) { Globals globals = JsePlatform.debugGlobals(); LuaValue chunk = globals.loadfile("res/test.lua"); chunk.call(); } 

RES / test.lua:

 function sandbox(fn) -- read script and set the environment f = loadfile(fn, "t") debug.setupvalue(f, 1, {print = print}) -- create a coroutine and have it yield every 50 instructions local co = coroutine.create(f) debug.sethook(co, coroutine.yield, "", 50) -- demonstrate stepped execution, 5 'ticks' for i = 1, 5 do print("tick") coroutine.resume(co) end end sandbox("res/badfile.lua") 

RES / badfile.lua:

 while 1 do print("", "badfile") end 

不幸的是,虽然控制流程按预期工作,但“废弃”协程应该收集垃圾的方式不能正常工作。 Java中相应的LuaThread在等待循环中永远挂起,使进程保持活动状态。 详情如下:

我怎么能放弃LuaJ coroutine LuaThread?

我之前从未使用过Luaj,但是你能不能放一条线

 JsePlatform.standardGlobals().loadFile("badscript.lua").call(); 

进入自己的新线程,然后可以从主线程终止?

这将要求您制作某种主管线程(类)并将任何已启动的脚本传递给它以监督并最终终止,如果它们不自行终止。

编辑:我没有找到任何方法来安全地终止LuaJ的线程而不修改LuaJ本身。 以下是我想出来的,虽然它与LuaJ不兼容。 但是,可以很容易地修改它以在纯Lua中完成它的工作。 我可能会切换到Java的Python绑定,因为LuaJ线程是如此有问题。

—我想出了以下内容,但它与LuaJ无关—

这是一个可能的解决方案。 我在debug.sethook中注册了一个钩子,它在“count”事件上被触发(这些事件甚至会在一段while true do end发生while true do end )。 我还传递了一个我创建的自定义“ScriptState”Java对象,它包含一个布尔标志,指示脚本是否应该终止。 在Lua钩子中查询Java对象, 如果设置了标志,则会抛出错误来关闭脚本 (编辑:抛出错误实际上并不终止脚本) 。 终止标志也可以从Lua脚本内部设置。

如果你想自动终止不屈不挠的无限循环,它就足以直接实现一个计时器系统,它记录上次调用ScriptState的时间,然后在没有API调用的情况下经过足够的时间后自动终止脚本(编辑:这只能工作)如果线程可以中断) 。 如果要杀死无限循环但不中断某些阻塞操作,可以调整ScriptState对象以包含允许您暂时暂停自动终止的其他状态信息等。

这是我的interpreter.lua ,它可用于调用另一个脚本并在必要时中断它。 它调用Java方法,因此它不会在没有LuaJ(或其他一些Lua-Java库)的情况下运行,除非它被修改(编辑:再次,它可以很容易地修改为在纯Lua中工作)

 function hook_line(e) if jthread:getDone() then -- I saw someone else use error(), but an infinite loop still seems to evade it. -- os.exit() seems to take care of it well. os.exit() end end function inithook() -- the hook will run every 100 million instructions. -- the time it takes for 100 million instructions to occur -- is based on computer speed and the calling environment debug.sethook(hook_line, "", 1e8) local ret = dofile(jLuaScript) debug.sethook() return ret end args = { ... } if jthread == nil then error("jthread object is nil. Please set it in the Java environment.",2) elseif jLuaScript == nil then error("jLuaScript not set. Please set it in the Java environment.",2) else local x,y = xpcall(inithook, debug.traceback) end 

这是存储标志的ScriptState类和用于演示的main()

 public class ScriptState { private AtomicBoolean isDone = new AtomicBoolean(true); public boolean getDone() { return isDone.get(); } public void setDone(boolean v) { isDone.set(v); } public static void main(String[] args) { Thread t = new Thread() { public void run() { System.out.println("J: Lua script started."); ScriptState s = new ScriptState(); Globals g = JsePlatform.debugGlobals(); g.set("jLuaScript", "res/main.lua"); g.set("jthread", CoerceJavaToLua.coerce(s)); try { g.loadFile("res/_interpreter.lua").call(); } catch (Exception e) { System.err.println("There was a Lua error!"); e.printStackTrace(); } } }; t.start(); try { t.join(); } catch (Exception e) { System.err.println("Error waiting for thread"); } System.out.println("J: End main"); } } 

res/main.lua包含要运行的目标Lua代码。 使用环境变量或参数照常将其他信息传递给脚本。 如果要在Lua中使用debug库,请记住使用JsePlatform.debugGlobals()而不是JsePlatform.standardGlobals()

编辑:我刚刚注意到os.exit()不仅终止了Lua脚本,还终止了调用进程 它似乎相当于System.exit() error()将抛出错误但不会导致Lua脚本终止。 我正试图为此找到解决方案。