Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrong garbage collection local variable #510

Closed
rise0chen opened this issue Jan 17, 2025 · 4 comments
Closed

Wrong garbage collection local variable #510

rise0chen opened this issue Jan 17, 2025 · 4 comments

Comments

@rise0chen
Copy link
Contributor

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.

@khvzak
Copy link
Member

khvzak commented Jan 17, 2025

I'm not getting any error, the program runs without printing anything

@rise0chen
Copy link
Contributor Author

Because of I use this version #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(())
}

@khvzak
Copy link
Member

khvzak commented Jan 19, 2025

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.

@rise0chen
Copy link
Contributor Author

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.

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 #489

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants