Implement basic abs path require, propagate async errors back to lua threads

This commit is contained in:
Filip Tibell 2023-08-19 16:06:12 -05:00
parent bcef44e286
commit dcb989fd92
9 changed files with 95 additions and 47 deletions

View file

@ -2,12 +2,19 @@ use mlua::prelude::*;
use super::context::*;
pub(super) async fn require<'lua>(
_lua: &'lua Lua,
_ctx: RequireContext,
pub(super) async fn require<'lua, 'ctx>(
lua: &'lua Lua,
ctx: &'ctx RequireContext,
path: &str,
) -> LuaResult<LuaValue<'lua>> {
Err(LuaError::runtime(format!(
"TODO: Support require for absolute paths (tried to require '{path}')"
)))
) -> LuaResult<LuaMultiValue<'lua>>
where
'lua: 'ctx,
{
if ctx.is_cached(path)? {
ctx.get_from_cache(lua, path)
} else if ctx.is_pending(path)? {
ctx.wait_for_cache(lua, path).await
} else {
ctx.load(lua, path).await
}
}

View file

@ -2,12 +2,15 @@ use mlua::prelude::*;
use super::context::*;
pub(super) async fn require<'lua>(
pub(super) async fn require<'lua, 'ctx>(
_lua: &'lua Lua,
_ctx: RequireContext,
_ctx: &'ctx RequireContext,
alias: &str,
name: &str,
) -> LuaResult<LuaValue<'lua>> {
) -> LuaResult<LuaMultiValue<'lua>>
where
'lua: 'ctx,
{
Err(LuaError::runtime(format!(
"TODO: Support require for built-in libraries (tried to require '{name}' with alias '{alias}')"
)))

View file

@ -2,11 +2,14 @@ use mlua::prelude::*;
use super::context::*;
pub(super) async fn require<'lua>(
pub(super) async fn require<'lua, 'ctx>(
_lua: &'lua Lua,
_ctx: RequireContext,
_ctx: &'ctx RequireContext,
name: &str,
) -> LuaResult<LuaValue<'lua>> {
) -> LuaResult<LuaMultiValue<'lua>>
where
'lua: 'ctx,
{
Err(LuaError::runtime(format!(
"TODO: Support require for built-in libraries (tried to require '{name}')"
)))

View file

@ -23,17 +23,15 @@ impl RequireContext {
context should be created per [`Lua`] struct, creating more
than one context may lead to undefined require-behavior.
*/
pub fn create(lua: &Lua) {
let this = Self {
pub fn new() -> Self {
Self {
// TODO: Set to false by default, load some kind of config
// or env var to check if we should be using absolute paths
use_absolute_paths: true,
working_directory: env::current_dir().expect("Failed to get current working directory"),
cache_results: Arc::new(AsyncMutex::new(HashMap::new())),
cache_pending: Arc::new(AsyncMutex::new(HashMap::new())),
};
lua.set_named_registry_value(REGISTRY_KEY, this)
.expect("Failed to insert RequireContext into registry");
}
}
/**
@ -102,9 +100,9 @@ impl RequireContext {
path will first be transformed into an absolute path.
*/
pub fn get_from_cache<'lua>(
&'lua self,
&self,
lua: &'lua Lua,
path: impl AsRef<str> + 'lua,
path: impl AsRef<str>,
) -> LuaResult<LuaMultiValue<'lua>> {
let path = self.abs_path(path);
@ -136,9 +134,9 @@ impl RequireContext {
path will first be transformed into an absolute path.
*/
pub async fn wait_for_cache<'lua>(
&'lua self,
&self,
lua: &'lua Lua,
path: impl AsRef<str> + 'lua,
path: impl AsRef<str>,
) -> LuaResult<LuaMultiValue<'lua>> {
let path = self.abs_path(path);
let sched = lua
@ -166,9 +164,9 @@ impl RequireContext {
path will first be transformed into an absolute path.
*/
pub async fn load<'lua>(
&'lua self,
&self,
lua: &'lua Lua,
path: impl AsRef<str> + 'lua,
path: impl AsRef<str>,
) -> LuaResult<LuaMultiValue<'lua>> {
let path = self.abs_path(path);
let sched = lua

View file

@ -11,31 +11,34 @@ mod builtin;
mod relative;
pub fn create(lua: &'static Lua) -> LuaResult<impl IntoLua<'_>> {
RequireContext::create(lua);
lua.set_app_data(RequireContext::new());
lua.create_async_function(|lua, path: LuaString| async move {
let context = RequireContext::from(lua);
let path = path
.to_str()
.into_lua_err()
.context("Failed to parse require path as string")?
.to_string();
if let Some(builtin_name) = path
let context = lua
.app_data_ref()
.expect("Failed to get RequireContext from app data");
let res = if let Some(builtin_name) = path
.strip_prefix("@lune/")
.map(|name| name.to_ascii_lowercase())
{
builtin::require(lua, context, &builtin_name).await
builtin::require(lua, &context, &builtin_name).await
} else if let Some(aliased_path) = path.strip_prefix('@') {
let (alias, name) = aliased_path.split_once('/').ok_or(LuaError::runtime(
"Require with custom alias must contain '/' delimiter",
))?;
alias::require(lua, context, alias, name).await
alias::require(lua, &context, alias, name).await
} else if context.use_absolute_paths() {
absolute::require(lua, context, &path).await
absolute::require(lua, &context, &path).await
} else {
relative::require(lua, context, &path).await
}
relative::require(lua, &context, &path).await
};
res.clone()
})
}

View file

@ -2,11 +2,14 @@ use mlua::prelude::*;
use super::context::*;
pub(super) async fn require<'lua>(
pub(super) async fn require<'lua, 'ctx>(
_lua: &'lua Lua,
_ctx: RequireContext,
_ctx: &'ctx RequireContext,
path: &str,
) -> LuaResult<LuaValue<'lua>> {
) -> LuaResult<LuaMultiValue<'lua>>
where
'lua: 'ctx,
{
Err(LuaError::runtime(format!(
"TODO: Support require for absolute paths (tried to require '{path}')"
)))

View file

@ -37,13 +37,20 @@ where
{
let thread = thread.into_owned_lua_thread(self.lua)?;
self.schedule_future(async move {
// TODO: Throw any error back to lua instead of panicking here
let rets = fut.await.expect("Failed to receive result");
let rets = rets
.into_lua_multi(self.lua)
.expect("Failed to create return multi value");
self.push_back(thread, rets)
.expect("Failed to schedule future thread");
match fut.await.and_then(|rets| rets.into_lua_multi(self.lua)) {
Err(e) => {
self.state.set_lua_error(e);
// NOTE: We push the thread to the front of the scheduler
// to ensure that it runs first to be able to catch the
// stored error from within the scheduler lua interrupt
self.push_front(thread, ())
.expect("Failed to schedule future thread");
}
Ok(v) => {
self.push_back(thread, v)
.expect("Failed to schedule future thread");
}
}
});
Ok(())

View file

@ -44,13 +44,23 @@ pub(crate) struct Scheduler<'lua, 'fut> {
impl<'lua, 'fut> Scheduler<'lua, 'fut> {
pub fn new(lua: &'lua Lua) -> Self {
Self {
let this = Self {
lua,
state: Arc::new(SchedulerState::new()),
threads: Arc::new(RefCell::new(VecDeque::new())),
thread_senders: Arc::new(RefCell::new(HashMap::new())),
futures: Arc::new(AsyncMutex::new(FuturesUnordered::new())),
}
};
// HACK: Propagate errors given to the scheduler back to their lua threads
// FUTURE: Do profiling and anything else we need inside of this interrupt
let state = this.state.clone();
lua.set_interrupt(move |_| match state.get_lua_error() {
Some(e) => Err(e),
None => Ok(LuaVmState::Continue),
});
this
}
#[doc(hidden)]

View file

@ -1,4 +1,9 @@
use std::sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering};
use std::{
cell::RefCell,
sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering},
};
use mlua::Error as LuaError;
#[derive(Debug, Default)]
pub struct SchedulerState {
@ -6,6 +11,7 @@ pub struct SchedulerState {
exit_code: AtomicU8,
num_resumptions: AtomicUsize,
num_errors: AtomicUsize,
lua_error: RefCell<Option<LuaError>>,
}
impl SchedulerState {
@ -41,4 +47,12 @@ impl SchedulerState {
self.exit_state.store(true, Ordering::SeqCst);
self.exit_code.store(code.into(), Ordering::SeqCst);
}
pub fn get_lua_error(&self) -> Option<LuaError> {
self.lua_error.take()
}
pub fn set_lua_error(&self, e: LuaError) {
self.lua_error.replace(Some(e));
}
}