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 线程的明确信息。
在线的 Lua demo 是非常有前途的,但似乎“坏”脚本的检测和终止是在 CGI 脚本中完成的,而不是 Lua。我能否使用 Java 调用一个 CGI 脚本,然后再调用 Lua 脚本?我不确定这会允许用户手动终止他们的脚本。我丢失了 Lua demo 源代码的链接,但我手头有它。这是魔法般的一行:
tee -a $LOG | (ulimit -t 1 ; $LUA demo.lua 2>&1 | head -c 8k)
有人能指引我正确的方向吗?
一些来源:
- Embedded Lua - timing out rogue scripts (e.g. infinite loop) - an example anyone?
- Prevent Lua infinite loop
- Embedded Lua - timing out rogue scripts (e.g. infinite loop) - an example anyone?
- How to interrupt the Thread when it is inside some loop doing long task?
- Killing thread after some specified time limit in Java
有些问题,但这对回答你的问题有一定的帮助。
以下的概念证明演示了基本的沙箱和限流任意用户代码的功能。它运行了大约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)
-- 读取脚本并设置环境变量
f = loadfile(fn, "t")
debug.setupvalue(f, 1, {print = print})
-- 创建一个协程,并让它每50个指令引出产生作用
local co = coroutine.create(f)
debug.sethook(co, coroutine.yield, "", 50)
-- 演示分步执行,5个“tick”
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将永远挂在一个等待循环中,保持进程的存活。详见这里:
我曾经也遇到了同样的问题,在阅读过 debug 库的实现后,我创建了一个类似于 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 的原因。
感谢 @Seldon 建议使用自定义 DebugLib。我通过仅在每个指令之前检查预定义的时间量来实现了一个简化版本。当然,这不是非常精确,因为在类创建和脚本执行之间存在一些时间。不需要单独的线程。
class DebugLibWithTimeout(
timeout: Duration,
) : DebugLib() {
private val timeoutOn = Instant.now() + timeout
override fun onInstruction(pc: Int, v: Varargs, top: Int) {
val timeoutElapsed = Instant.now() > timeoutOn
if (timeoutElapsed)
throw Exception("Timeout")
super.onInstruction(pc, v, top)
}
}
重要提示:如果你使用 load 函数在 Lua 代码中调用未受信任的脚本,并向其传递一个独立的环境,则此方法无法使用。onInstruction() 似乎只会在函数环境引用 _G 时被调用。我通过剥离 _G 中的一切,然后将白名单项目添加回去来解决了这个问题。
-- 白名单项目
local sandbox_globals = {
print = print
}
local original_globals = {}
for key, value in pairs(_G) do
original_globals[key] = value
end
local sandbox_env = _G
-- 从 _G 中去除一切
for key, _ in pairs(sandbox_env) do
sandbox_env[key] = nil
end
-- 将白名单项目添加回去。
-- 现在无法使用全局 pairs 函数。
for key, value in original_globals.pairs(sandbox_globals) do
sandbox_env[key] = value
end
local function run_user_script(script)
local script_function, message = original_globals.load(script, nil, 't', sandbox_env)
if not script_function then
return false, message
end
return pcall(script_function)
end
- 如何将两个不同的lua文件合成一个 东西有点长 大佬请耐心看完 我是小白研究几天了都没搞定
- 如何在roblox studio中1:1导入真实世界的地形?
- 求解,lua_resume的第二次调用继续执行协程问题。
- 【上海普陀区】内向猫网络招募【Skynet游戏框架Lua后端程序员】
- SF爱好求教:如何用lua实现游戏内调用数据库函数实现账号密码注册?
- Lua实现网站后台开发
- LUA错误显式返回,社区常见的规约是怎么样的
- lua5.3下载库失败
- 请问如何实现文本框内容和某个网页搜索框内容连接,并把网页输出来的结果反馈到另外一个文本框上
- lua lanes多线程使用
- 一个kv数据库
- openresty 有没有比较轻量的 docker 镜像
- 想问一下,有大佬用过luacurl吗
- 在Lua执行过程中使用Load函数出现问题
- 为什么 neovim 里没有显示一些特殊字符?
- Lua比较两个表的值(不考虑键的顺序)
- 有个lua简单的项目,外包,有意者加微信 liuheng600456详谈,最好在成都
- 如何在 Visual Studio 2022 中运行 Lua 代码?
- addEventListener 返回 nil Lua
- Lua中获取用户配置主目录的跨平台方法
我以前从未使用过 Luaj,但是你能将你的一行代码
JsePlatform.standardGlobals().loadFile("badscript.lua").call();放进一个新的线程中,然后你可以从主线程中终止它吗?
这将需要你创建某种监督线程(类),并将任何启动的脚本传递给它进行监督,最终在它们自己不关闭时终止它们。