在Rust中使用mlua终止Lua脚本/线程的正确方法
如何在mlua脚本执行中途停止对应的Rust线程
我正在用mlua和Rust构建脚本引擎,同一时间仅运行一个脚本。计划为每个脚本启动独立线程,线程无返回值,可无限运行直至用户手动停止。当前遇到的问题是:如何在脚本执行中途停止该线程?
示例无限循环Lua脚本:
while true do log("I'm stuck in an infinite loop!") end
当前Rust代码实现:
pub struct Script { name: String, path: PathBuf, lua: Arc<Mutex<Lua>>, thread: ThreadManager, should_stop: Arc<AtomicBool>, } impl Script { pub fn new(name: &str, path: PathBuf) -> Result<Self> { Ok(Self { name: name.to_string(), path, lua: Arc::new(Mutex::new(Lua::new())), thread: ThreadManager::new(), should_stop: Arc::new(AtomicBool::new(false)), }) } pub fn load(&mut self) -> Result<()> { let lua = self.lua.lock().unwrap(); let globals = lua.globals(); // register API functions here globals.set( "log", lua.create_function(|_, msg: String| { info!("{}", msg); Ok(()) })?, )?; info!("Loaded script: {}", self.name); Ok(()) } pub fn start(&mut self) -> Result<()> { let script_content = std::fs::read_to_string(&self.path); let script_content = match script_content { Ok(content) => content, Err(_) => { return Err(Error::RuntimeError( "failed to read script file".to_string(), )); } }; let lua_arc_clone = Arc::clone(&self.lua); let thread_state = self.thread.get_state(&self.name); if thread_state == ThreadState::Stopped { let is_started = self.thread.start(&self.name, move || { let lua = lua_arc_clone.lock().unwrap(); lua.load(&script_content).exec().unwrap(); }); if !is_started { warn!("failed to start thread for: {}", self.name); } } Ok(()) } pub fn stop(&mut self) -> Result<()> { let thread_state = self.thread.get_state(&self.name); if thread_state == ThreadState::Running { self.thread.stop(&self.name); info!("script stopped: {}", self.name); } Ok(()) } }
线程启动逻辑:
pub fn start<T>(&mut self, name: &str, t: T) -> bool where T: Fn() + Send + 'static, { let state = Arc::new(Mutex::new(ThreadState::Running)); let state_clone = Arc::clone(&state); thread::spawn(move || loop { match *state_clone.lock().unwrap() { ThreadState::Running => { info!("Running thread"); t(); } ThreadState::Paused => { info!("Paused thread"); thread::sleep(Duration::from_millis(10)); } ThreadState::Stopped => { info!("Stopped thread"); break; } } }); self.threads.insert(name.clone().to_string(), state); true }
此前尝试用信号实现,但Lua的执行会阻塞状态检查,无法生效。
解决方案
直接强制终止Rust线程会导致Lua虚拟机资源泄漏、锁持有等不安全问题,推荐利用mlua的协程钩子机制实现安全中断:
方案:使用mlua钩子定期检查停止信号
核心思路是让Lua虚拟机在执行过程中定期触发钩子,检查停止标记,若标记为true则抛出错误终止脚本执行。
1. 修改Script的load方法,添加钩子
pub fn load(&mut self) -> Result<()> { let lua = self.lua.lock().unwrap(); let globals = lua.globals(); // 注册log函数 globals.set( "log", lua.create_function(|_, msg: String| { info!("{}", msg); Ok(()) })?, )?; // 克隆停止标记供钩子使用 let should_stop_clone = Arc::clone(&self.should_stop); // 设置钩子:每执行100条Lua指令触发一次检查 lua.set_hook(mlua::HookTriggers::COUNT, 100, move |_| { if should_stop_clone.load(Ordering::SeqCst) { // 抛出错误终止Lua执行 Err(mlua::Error::ExternalError(Box::new("Script stopped by user"))) } else { Ok(()) } })?; info!("Loaded script: {}", self.name); Ok(()) }
2. 修改start方法,处理脚本中断错误
pub fn start(&mut self) -> Result<()> { let script_content = std::fs::read_to_string(&self.path)?; let lua_arc_clone = Arc::clone(&self.lua); let should_stop_clone = Arc::clone(&self.should_stop); // 启动前重置停止标记 should_stop_clone.store(false, Ordering::SeqCst); let thread_state = self.thread.get_state(&self.name); if thread_state == ThreadState::Stopped { let is_started = self.thread.start(&self.name, move || { let lua = lua_arc_clone.lock().unwrap(); // 执行脚本并捕获钩子抛出的中断错误 match lua.load(&script_content).exec() { Ok(_) => {}, Err(e) => { if e.to_string().contains("Script stopped by user") { info!("Script stopped via hook: {}", self.name); } else { error!("Script execution error: {}", e); } } } }); if !is_started { warn!("failed to start thread for: {}", self.name); } } Ok(()) }
3. 修改stop方法,设置停止标记
pub fn stop(&mut self) -> Result<()> { let thread_state = self.thread.get_state(&self.name); if thread_state == ThreadState::Running { // 设置停止标记,钩子会检测到并终止脚本 self.should_stop.store(true, Ordering::SeqCst); // 通知线程管理器切换状态 self.thread.stop(&self.name); info!("script stopped: {}", self.name); } Ok(()) }
注意事项
- 钩子触发频率可调整:
100代表每执行100条Lua指令触发一次,频率过高会影响性能,过低会导致停止响应延迟 - 禁止直接强制终止线程:会导致Lua虚拟机处于不一致状态,引发内存泄漏或死锁
- 确保所有自定义Lua绑定函数不长期阻塞,否则会延迟中断响应
内容的提问来源于stack exchange,提问作者requiem
相关产品推荐
相关产品推荐

