Wrong garbage collection local variable
use core::time::Duration;
use futures_util::future::select_all;
use mlua::prelude::*;
async fn sleep(_lua: Lua, ms: u64) -> LuaResult<()> {
tokio::time::sleep(Duration::from_millis(ms)).await;
Ok(())
}
async fn select(_lua: Lua, threads: LuaMultiValue) -> LuaResult<(LuaValue, i64)> {
let futs: Vec<LuaAsyncThread<_, LuaValue>> = threads
.into_iter()
.filter_map(|thread| {
if let LuaValue::Thread(thread) = thread {
Some(thread.into_async(()))
} else {
None
}
})
.collect();
if futs.is_empty() {
return Ok((LuaValue::Nil, -1));
}
let (val, index, _threads) = select_all(futs).await;
Ok((val?, index as i64 + 1))
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> LuaResult<()> {
let lua = Lua::new();
lua.globals().set("sleep", lua.create_async_function(sleep)?)?;
lua.globals()
.set("select_all", lua.create_async_function(select)?)?;
lua.set_hook(LuaHookTriggers::EVERY_LINE, move |_lua, debug| {
let source = debug.source().source.unwrap_or(std::borrow::Cow::Borrowed("~"));
let is_rust = source.len() < 2;
// println!("lua hook({}): {}", is_rust, source);
if is_rust {
Ok(LuaVmState::Continue)
} else {
Ok(LuaVmState::Yield)
}
});
let f = lua
.load(
r#"
function f_test()
while true
do
local d = {0,0,0,0,0,0}
d[1] = d[1] + 1
d[2] = d[2] + 2
d[3] = d[3] + 3
d[4] = d[4] + 4
d[5] = d[5] + 5
d[6] = d[6] + 6
sleep(5)
end
end
function f_gc()
while true
do
sleep(50)
collectgarbage("collect")
end
end
ret, index = select_all(coroutine.create(f_test), coroutine.create(f_gc))
print(ret, index)
"#,
)
.into_function()?;
let thread = lua.create_thread(f)?;
thread.into_async(()).await?;
Ok(())
}
cargo run --release --features=lua54,vendored,async --example b
Compiling mlua v0.10.2
Finished `release` profile [optimized] target(s) in 8.11s
Running `target/release/examples/b`
Error: CallbackError { traceback: "stack traceback:\n\t[C]: in local 'poll'\n\t[string \"?\"]:4: in function 'select_all'\n\t[string \"examples/b.rs:45:10\"]:25: in main chunk", cause: RuntimeError("[string \"examples/b.rs:45:10\"]:6: attempt to index a nil value (local 'd')\nstack traceback:\n\t[string \"examples/b.rs:45:10\"]:6: in function 'f_test'") }
It throw an error: attempt to index a nil value (local 'd')
I think this issue is related to set_hook.
I'm not getting any error, the program runs without printing anything
Because of I use this version https://github.com/mlua-rs/mlua/pull/498. It set a global hook.
I modify code that set hook for every thread manually:
use core::time::Duration;
use futures_util::future::select_all;
use mlua::prelude::*;
fn global_hook(_lua: &Lua, debug: mlua::Debug) -> LuaResult<LuaVmState> {
let source = debug
.source()
.source
.unwrap_or(std::borrow::Cow::Borrowed("~"));
let is_rust = source.len() < 2;
println!("lua hook({}): {}", is_rust, source);
if is_rust {
Ok(LuaVmState::Continue)
} else {
Ok(LuaVmState::Yield)
}
}
async fn sleep(_lua: Lua, ms: u64) -> LuaResult<()> {
tokio::time::sleep(Duration::from_millis(ms)).await;
Ok(())
}
async fn select(_lua: Lua, threads: LuaMultiValue) -> LuaResult<(LuaValue, i64)> {
let futs: Vec<LuaAsyncThread<_, LuaValue>> = threads
.into_iter()
.filter_map(|thread| {
if let LuaValue::Thread(thread) = thread {
thread.set_hook(LuaHookTriggers::EVERY_LINE, global_hook);
Some(thread.into_async(()))
} else {
None
}
})
.collect();
if futs.is_empty() {
return Ok((LuaValue::Nil, -1));
}
let (val, index, _threads) = select_all(futs).await;
Ok((val?, index as i64 + 1))
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> LuaResult<()> {
let lua = Lua::new();
lua.globals()
.set("sleep", lua.create_async_function(sleep)?)?;
lua.globals()
.set("select_all", lua.create_async_function(select)?)?;
let f = lua
.load(
r#"
function f_test()
while true
do
local d = {0,0,0,0,0,0}
d[1] = d[1] + 1
d[2] = d[2] + 2
d[3] = d[3] + 3
d[4] = d[4] + 4
d[5] = d[5] + 5
d[6] = d[6] + 6
sleep(5)
end
end
function f_gc()
while true
do
sleep(50)
collectgarbage("collect")
end
end
local co_test = coroutine.create(f_test)
debug.sethook(co_test, global_hook, "l", 1)
local co_gc = coroutine.create(f_gc)
debug.sethook(co_gc, global_hook, "l", 1)
ret, index = select_all(co_test, co_gc)
print(ret, index)
"#,
)
.into_function()?;
let thread = lua.create_thread(f)?;
thread.set_hook(LuaHookTriggers::EVERY_LINE, global_hook);
let _: () = thread.into_async(()).await?;
Ok(())
}
let lua = Lua::new();
must be
let lua = unsafe { Lua::unsafe_new_with(LuaStdLib::ALL, LuaOptions::default()) };
since debug module is not enabled by default.
thread.set_hook(LuaHookTriggers::EVERY_LINE, global_hook);
You cannot have more than one hook overall, attaching hook to a new thread will disable hook on previous thread. So this code makes little sense.
Apart from that the program does not crash.
let lua = Lua::new();must be
let lua = unsafe { Lua::unsafe_new_with(LuaStdLib::ALL, LuaOptions::default()) };sincedebugmodule is not enabled by default.
thread.set_hook(LuaHookTriggers::EVERY_LINE, global_hook);You cannot have more than one hook overall, attaching hook to a new thread will disable hook on previous thread. So this code makes little sense.
Apart from that the program does not crash.
Yes, you are right.
But, I want to set a global hook for every thread.
debug.sethook support more than one hook, but it can yield the current thread.
I close this issue, beacuse this bug is caused by multi hook https://github.com/mlua-rs/mlua/issues/489
use core::time::Duration;
use futures_util::future::select_all;
use mlua::prelude::*;
fn global_hook_ok(_lua: &Lua, debug: mlua::Debug) -> LuaResult<LuaVmState> {
let source = debug
.source()
.source
.unwrap_or(std::borrow::Cow::Borrowed("~"));
let is_rust = source.len() < 2;
println!("lua hook({}): {}", is_rust, source);
Ok(LuaVmState::Continue)
}
fn global_hook_not_resume(_lua: &Lua, debug: mlua::Debug) -> LuaResult<LuaVmState> {
let source = debug
.source()
.source
.unwrap_or(std::borrow::Cow::Borrowed("~"));
let is_rust = source.len() < 2;
println!("lua hook({}): {}", is_rust, source);
Ok(LuaVmState::Yield)
}
fn global_hook_error_gc(_lua: &Lua, debug: mlua::Debug) -> LuaResult<LuaVmState> {
let source = debug
.source()
.source
.unwrap_or(std::borrow::Cow::Borrowed("~"));
let is_rust = source.len() < 2;
println!("lua hook({}): {}", is_rust, source);
if is_rust {
Ok(LuaVmState::Continue)
} else {
Ok(LuaVmState::Yield)
}
}
async fn sleep(_lua: Lua, ms: u64) -> LuaResult<()> {
tokio::time::sleep(Duration::from_millis(ms)).await;
Ok(())
}
async fn select(_lua: Lua, threads: LuaMultiValue) -> LuaResult<(LuaValue, i64)> {
let futs: Vec<LuaAsyncThread<LuaValue>> = threads
.into_iter()
.filter_map(|thread| {
if let LuaValue::Thread(thread) = thread {
thread.into_async(()).ok()
} else {
None
}
})
.collect();
if futs.is_empty() {
return Ok((LuaValue::Nil, -1));
}
let (val, index, _threads) = select_all(futs).await;
Ok((val?, index as i64 + 1))
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> LuaResult<()> {
let lua = Lua::new();
lua.globals()
.set("sleep", lua.create_async_function(sleep)?)?;
lua.globals()
.set("select_all", lua.create_async_function(select)?)?;
//lua.set_global_hook(LuaHookTriggers::EVERY_LINE, global_hook_ok)?;
//lua.set_global_hook(LuaHookTriggers::EVERY_LINE, global_hook_not_resume)?;
lua.set_global_hook(LuaHookTriggers::EVERY_LINE, global_hook_error_gc)?;
let f = lua
.load(
r#"
function f_test()
while true
do
local d = {0,0,0,0,0,0}
d[1] = d[1] + 1
d[2] = d[2] + 2
d[3] = d[3] + 3
d[4] = d[4] + 4
d[5] = d[5] + 5
d[6] = d[6] + 6
sleep(5)
end
end
function f_gc()
while true
do
sleep(50)
collectgarbage("collect")
end
end
local co_test = coroutine.create(f_test)
local co_gc = coroutine.create(f_gc)
ret, index = select_all(co_test, co_gc)
print(ret, index)
"#,
)
.into_function()?;
let thread = lua.create_thread(f)?;
let _: () = thread.into_async(())?.await?;
Ok(())
}
Reopen this issue.
It is cased by global_hook and LuaVmState::Yield.
Maybe we should not use LuaVmState::Yield in global_hook.
On first sight, it looks like a bug in Lua itself. It should never collect locals. I need deep investigation.