4

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

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

badscript.lua包含while true do end.

我希望能够自动终止卡在不屈不挠的循环中的脚本,并允许用户在运行时手动终止他们的 Lua 脚本。我读过关于debug.sethookpcall的内容,但我不确定如何正确使用它们来达到我的目的。我还听说沙盒是一个更好的选择,尽管这有点超出我的能力范围。

这个问题也可以单独扩展到 Java 线程。我还没有找到任何关于中断卡在while (true);.

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

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

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

一些资料来源:

4

4 回答 4

6

我在同样的问题上苦苦挣扎,在对调试库的实现进行了一些挖掘之后,我创建了一个类似于 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 即可停止执行。异常将被封装为引发LuaError时的原因。

于 2015-04-03T19:30:04.913 回答
2

存在问题,但这对回答您的问题大有帮助。

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

沙盒测试.java:

public static void main(String[] args) {
    Globals globals = JsePlatform.debugGlobals();

    LuaValue chunk = globals.loadfile("res/test.lua");

    chunk.call();
}

水库/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")

资源/坏文件.lua:

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

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

如何放弃 LuaJ 协程 LuaThread?

于 2014-07-05T10:36:00.730 回答
0

我以前从未使用过 Luaj,但你能不能不要把你的一行

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

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

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

于 2013-07-05T22:28:35.077 回答
0

编辑:我还没有找到任何方法来安全地终止 LuaJ 的线程而不修改 LuaJ 本身。以下是我想出的,尽管它不适用于 LuaJ。但是,可以很容易地对其进行修改以在纯 Lua 中完成其工作。我可能会切换到 Java 的 Python 绑定,因为 LuaJ 线程非常有问题。

--- 我想出了以下方法,但它不适用于 LuaJ ---

是一个可能的解决方案。我使用 debug.sethook 注册了一个钩子,该钩子在“计数”事件上触发(这些事件甚至发生在 a 中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()要演示的 a:

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 中使用库,请记住使用JsePlatform.debugGlobals()而不是。JsePlatform.standardGlobals()debug

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

于 2013-07-07T22:12:29.863 回答