Implement traceback helper to use in scheduler

This commit is contained in:
Filip Tibell 2023-02-16 16:19:58 +01:00
parent 02e4f87a5a
commit 04a47babdd
No known key found for this signature in database
8 changed files with 183 additions and 16 deletions

View file

@ -98,8 +98,7 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable<'static>> {
// We want the task scheduler to be transparent, // We want the task scheduler to be transparent,
// but it does not return real lua threads, so // but it does not return real lua threads, so
// we need to override some globals to fake it // we need to override some globals to fake it
let globals = lua.globals(); let type_original: LuaFunction = lua.named_registry_value("type")?;
let type_original: LuaFunction = globals.get("type")?;
let type_proxy = lua.create_function(move |_, value: LuaValue| { let type_proxy = lua.create_function(move |_, value: LuaValue| {
if let LuaValue::UserData(u) = &value { if let LuaValue::UserData(u) = &value {
if u.is::<TaskReference>() { if u.is::<TaskReference>() {
@ -108,7 +107,7 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable<'static>> {
} }
type_original.call(value) type_original.call(value)
})?; })?;
let typeof_original: LuaFunction = globals.get("typeof")?; let typeof_original: LuaFunction = lua.named_registry_value("typeof")?;
let typeof_proxy = lua.create_function(move |_, value: LuaValue| { let typeof_proxy = lua.create_function(move |_, value: LuaValue| {
if let LuaValue::UserData(u) = &value { if let LuaValue::UserData(u) = &value {
if u.is::<TaskReference>() { if u.is::<TaskReference>() {
@ -117,6 +116,7 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable<'static>> {
} }
typeof_original.call(value) typeof_original.call(value)
})?; })?;
let globals = lua.globals();
globals.set("type", type_proxy)?; globals.set("type", type_proxy)?;
globals.set("typeof", typeof_proxy)?; globals.set("typeof", typeof_proxy)?;
// Functions in the built-in coroutine library also need to be // Functions in the built-in coroutine library also need to be

View file

@ -14,6 +14,7 @@ mod tests;
use crate::utils::formatting::pretty_format_luau_error; use crate::utils::formatting::pretty_format_luau_error;
pub use globals::LuneGlobal; pub use globals::LuneGlobal;
pub use lua::create_lune_lua;
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct Lune { pub struct Lune {
@ -88,18 +89,8 @@ impl Lune {
script_name: &str, script_name: &str,
script_contents: &str, script_contents: &str,
) -> Result<ExitCode, LuaError> { ) -> Result<ExitCode, LuaError> {
let lua = Lua::new().into_static(); // Create our special lune-flavored Lua object with extra registry values
// Store original lua global functions in the registry so we can use let lua = create_lune_lua().expect("Failed to create Lua object");
// them later without passing them around and dealing with lifetimes
lua.set_named_registry_value("require", lua.globals().get::<_, LuaFunction>("require")?)?;
lua.set_named_registry_value("print", lua.globals().get::<_, LuaFunction>("print")?)?;
lua.set_named_registry_value("error", lua.globals().get::<_, LuaFunction>("error")?)?;
let coroutine: LuaTable = lua.globals().get("coroutine")?;
lua.set_named_registry_value("co.thread", coroutine.get::<_, LuaFunction>("running")?)?;
lua.set_named_registry_value("co.yield", coroutine.get::<_, LuaFunction>("yield")?)?;
lua.set_named_registry_value("co.close", coroutine.get::<_, LuaFunction>("close")?)?;
let debug: LuaTable = lua.globals().raw_get("debug")?;
lua.set_named_registry_value("dbg.info", debug.get::<_, LuaFunction>("info")?)?;
// Create our task scheduler and schedule the main thread on it // Create our task scheduler and schedule the main thread on it
let sched = TaskScheduler::new(lua)?.into_static(); let sched = TaskScheduler::new(lua)?.into_static();
lua.set_app_data(sched); lua.set_app_data(sched);

View file

@ -0,0 +1,93 @@
use mlua::prelude::*;
/*
- Level 0 is the call to info
- Level 1 is the load call in create() below where we load this into a function
- Level 2 is the call to the scheduler, probably, but we can't know for sure so we start at 2
*/
const TRACE_IMPL_LUA: &str = r#"
local lines = {}
for level = 2, 2^8 do
local source, line, name = info(level, "sln")
if source then
if line then
if name and #name > 0 then
push(lines, format(" Script '%s', Line %d - function %s", source, line, name))
else
push(lines, format(" Script '%s', Line %d", source, line))
end
elseif name and #name > 0 then
push(lines, format(" Script '%s' - function %s", source, name))
else
push(lines, format(" Script '%s'", source))
end
elseif name then
push(lines, format("[Lune] - function %s", source, name))
else
break
end
end
if #lines > 0 then
push(lines, 1, "Stack Begin")
push(lines, "Stack End")
return concat(lines, "\n")
else
return nil
end
"#;
/**
Creates a [`mlua::Lua`] object with certain globals stored in the Lua registry.
These globals can then be modified safely after constructing Lua using this function.
---
* `"require"` -> `require`
---
* `"print"` -> `print`
* `"error"` -> `error`
---
* `"type"` -> `type`
* `"typeof"` -> `typeof`
---
* `"co.thread"` -> `coroutine.running`
* `"co.yield"` -> `coroutine.yield`
* `"co.close"` -> `coroutine.close`
---
* `"dbg.info"` -> `debug.info`
* `"dbg.trace"` -> `debug.traceback`
---
*/
pub fn create() -> LuaResult<&'static Lua> {
let lua = Lua::new().into_static();
let globals = &lua.globals();
let debug: LuaTable = globals.raw_get("debug")?;
let table: LuaTable = globals.raw_get("table")?;
let string: LuaTable = globals.raw_get("string")?;
let coroutine: LuaTable = globals.get("coroutine")?;
// Store original lua global functions in the registry so we can use
// them later without passing them around and dealing with lifetimes
lua.set_named_registry_value("require", globals.get::<_, LuaFunction>("require")?)?;
lua.set_named_registry_value("print", globals.get::<_, LuaFunction>("print")?)?;
lua.set_named_registry_value("error", globals.get::<_, LuaFunction>("error")?)?;
lua.set_named_registry_value("type", globals.get::<_, LuaFunction>("type")?)?;
lua.set_named_registry_value("typeof", globals.get::<_, LuaFunction>("typeof")?)?;
lua.set_named_registry_value("co.thread", coroutine.get::<_, LuaFunction>("running")?)?;
lua.set_named_registry_value("co.yield", coroutine.get::<_, LuaFunction>("yield")?)?;
lua.set_named_registry_value("co.close", coroutine.get::<_, LuaFunction>("close")?)?;
lua.set_named_registry_value("dbg.info", debug.get::<_, LuaFunction>("info")?)?;
// Create a trace function that can be called to obtain a full stack trace from
// lua, this is not possible to do from rust when using our manual scheduler
let trace_env = lua.create_table_with_capacity(0, 1)?;
trace_env.set("info", debug.get::<_, LuaFunction>("info")?)?;
trace_env.set("push", table.get::<_, LuaFunction>("insert")?)?;
trace_env.set("concat", table.get::<_, LuaFunction>("concat")?)?;
trace_env.set("format", string.get::<_, LuaFunction>("format")?)?;
let trace_fn = lua
.load(TRACE_IMPL_LUA)
.set_environment(trace_env)?
.into_function()?;
lua.set_named_registry_value("dbg.trace", trace_fn)?;
// All done
Ok(lua)
}

View file

@ -1,3 +1,7 @@
mod create;
pub mod net; pub mod net;
pub mod stdio; pub mod stdio;
pub mod task; pub mod task;
pub use create::create as create_lune_lua;

View file

@ -94,9 +94,12 @@ impl TaskSchedulerState {
Returns `true` if the task scheduler is done, Returns `true` if the task scheduler is done,
meaning it has no lua threads left to run, and meaning it has no lua threads left to run, and
no spawned tasks are running in the background. no spawned tasks are running in the background.
Also returns `true` if a task has requested to exit the process.
*/ */
pub fn is_done(&self) -> bool { pub fn is_done(&self) -> bool {
self.num_blocking == 0 && self.num_futures == 0 && self.num_background == 0 self.exit_code.is_some()
|| (self.num_blocking == 0 && self.num_futures == 0 && self.num_background == 0)
} }
} }

View file

@ -39,6 +39,26 @@ end)
task.wait() task.wait()
assert(flag3 == 3, "Defer should run after spawned threads") assert(flag3 == 3, "Defer should run after spawned threads")
-- Delay should be able to be nested
local flag4: boolean = false
task.delay(0.05, function()
local function nested3()
task.delay(0.05, function()
flag4 = true
end)
end
local function nested2()
task.delay(0.05, nested3)
end
local function nested1()
task.delay(0.05, nested2)
end
nested1()
end)
task.wait(0.25)
assert(flag4, "Defer should work with nesting")
-- Varargs should get passed correctly -- Varargs should get passed correctly
local fcheck = require("./fcheck") local fcheck = require("./fcheck")

View file

@ -26,6 +26,34 @@ assert(flag, "Delay should work with yielding (1)")
task.wait(0.1) task.wait(0.1)
assert(not flag2, "Delay should work with yielding (2)") assert(not flag2, "Delay should work with yielding (2)")
-- Defer should be able to be nested
local flag4: boolean = false
task.defer(function()
local function nested3()
task.defer(function()
task.wait(0.05)
flag4 = true
end)
end
local function nested2()
task.defer(function()
task.wait(0.05)
nested3()
end)
end
local function nested1()
task.defer(function()
task.wait(0.05)
nested2()
end)
end
task.wait(0.05)
nested1()
end)
task.wait(0.25)
assert(flag4, "Defer should work with nesting")
-- Varargs should get passed correctly -- Varargs should get passed correctly
local fcheck = require("./fcheck") local fcheck = require("./fcheck")

View file

@ -31,6 +31,34 @@ end)
task.spawn(thread2) task.spawn(thread2)
assert(flag3, "Spawn should run threads made from coroutine.create") assert(flag3, "Spawn should run threads made from coroutine.create")
-- Spawn should be able to be nested
local flag4: boolean = false
task.spawn(function()
local function nested3()
task.spawn(function()
task.wait(0.05)
flag4 = true
end)
end
local function nested2()
task.spawn(function()
task.wait(0.05)
nested3()
end)
end
local function nested1()
task.spawn(function()
task.wait(0.05)
nested2()
end)
end
task.wait(0.05)
nested1()
end)
task.wait(0.25)
assert(flag4, "Spawn should work with nesting")
-- Varargs should get passed correctly -- Varargs should get passed correctly
local fcheck = require("./fcheck") local fcheck = require("./fcheck")