Implement thread return value broadcasting in scheduler

This commit is contained in:
Filip Tibell 2023-08-16 22:00:15 -05:00
parent 6757e1a1a8
commit 4fa76aa27f
4 changed files with 161 additions and 41 deletions

View file

@ -1,49 +1,72 @@
use std::process::ExitCode; use std::{process::ExitCode, sync::Arc};
use mlua::prelude::*; use mlua::prelude::*;
use tokio::task::LocalSet;
use super::SchedulerImpl; use super::SchedulerImpl;
impl SchedulerImpl { impl SchedulerImpl {
/** /**
Runs all lua threads to completion, gathering any results they produce. Runs all lua threads to completion.
*/
fn run_threads(&self) -> Vec<LuaResult<()>> {
let mut results = Vec::new();
while let Some((thread, args)) = self Returns `true` if any thread was resumed, `false` otherwise.
*/
fn run_lua_threads(&self) -> bool {
if self.state.has_exit_code() {
return false;
}
let mut resumed_any = false;
while let Some((thread, args, sender)) = self
.pop_thread() .pop_thread()
.expect("Failed to pop thread from scheduler") .expect("Failed to pop thread from scheduler")
{ {
let res = thread.resume(args); let res = thread.resume::<_, LuaMultiValue>(args);
self.state.add_resumption(); self.state.add_resumption();
resumed_any = true;
if let Err(e) = &res { if let Err(err) = &res {
self.state.add_error(); self.state.add_error();
eprintln!("{e}"); // TODO: Pretty print the lua error here eprint!("{err}"); // TODO: Pretty print the lua error here
} }
results.push(res); if sender.receiver_count() > 0 {
sender
.send(res.map(|v| {
Arc::new(
self.lua
.create_registry_value(v.into_vec())
.expect("Failed to store return values in registry"),
)
}))
.expect("Failed to broadcast return values of thread");
}
if self.state.has_exit_code() { if self.state.has_exit_code() {
break; break;
} }
} }
results resumed_any
} }
/** /**
Runs the scheduler to completion, both normal lua threads and futures. Runs the scheduler to completion in a [`LocalSet`],
both normal lua threads and futures, prioritizing
lua threads over completion of any pending futures.
This will emit lua output and errors to stdout and stderr. Will emit lua output and errors to stdout and stderr.
*/ */
pub async fn run_to_completion(&self) -> ExitCode { pub async fn run_to_completion(&self) -> ExitCode {
let fut = async move {
loop { loop {
// 1. Run lua threads until exit or there are none left // 1. Run lua threads until exit or there are none left,
let results = self.run_threads(); // if any thread was resumed it may have spawned futures
let resumed_lua = self.run_lua_threads();
// 2. If we got a manual exit code from lua we should not continue // 2. If we got a manual exit code from lua we should
// not try to wait for any pending futures to complete
if self.state.has_exit_code() { if self.state.has_exit_code() {
break; break;
} }
@ -51,14 +74,17 @@ impl SchedulerImpl {
// 3. Wait for the next future to complete, this may // 3. Wait for the next future to complete, this may
// add more lua threads to run in the next iteration // add more lua threads to run in the next iteration
// TODO: Implement this // TODO: Implement futures resumption
// 4. If did not resume any lua threads, and we have no futures // 4. If we did not resume any lua threads, and we have no futures
// queued either, we have run the scheduler until completion // queued either, we have now run the scheduler until completion
if results.is_empty() { if !resumed_lua {
break; break;
} }
} }
};
LocalSet::new().run_until(fut).await;
if let Some(code) = self.state.exit_code() { if let Some(code) = self.state.exit_code() {
ExitCode::from(code) ExitCode::from(code)

View file

@ -1,6 +1,10 @@
use mlua::prelude::*; use mlua::prelude::*;
use super::{thread::SchedulerThread, traits::IntoLuaThread, SchedulerImpl}; use super::{
thread::{SchedulerThread, SchedulerThreadId, SchedulerThreadSender},
traits::IntoLuaThread,
SchedulerImpl,
};
impl<'lua> SchedulerImpl { impl<'lua> SchedulerImpl {
/** /**
@ -10,7 +14,7 @@ impl<'lua> SchedulerImpl {
*/ */
pub(super) fn pop_thread( pub(super) fn pop_thread(
&'lua self, &'lua self,
) -> LuaResult<Option<(LuaThread<'lua>, LuaMultiValue<'lua>)>> { ) -> LuaResult<Option<(LuaThread<'lua>, LuaMultiValue<'lua>, SchedulerThreadSender)>> {
match self match self
.threads .threads
.try_borrow_mut() .try_borrow_mut()
@ -19,8 +23,14 @@ impl<'lua> SchedulerImpl {
.pop_front() .pop_front()
{ {
Some(thread) => { Some(thread) => {
let thread_id = &thread.id();
let (thread, args) = thread.into_inner(&self.lua); let (thread, args) = thread.into_inner(&self.lua);
Ok(Some((thread, args))) let sender = self
.thread_senders
.borrow_mut()
.remove(thread_id)
.expect("Missing thread sender");
Ok(Some((thread, args, sender)))
} }
None => Ok(None), None => Ok(None),
} }
@ -34,17 +44,23 @@ impl<'lua> SchedulerImpl {
&'lua self, &'lua self,
thread: impl IntoLuaThread<'lua>, thread: impl IntoLuaThread<'lua>,
args: impl IntoLuaMulti<'lua>, args: impl IntoLuaMulti<'lua>,
) -> LuaResult<()> { ) -> LuaResult<SchedulerThreadId> {
let thread = thread.into_lua_thread(&self.lua)?; let thread = thread.into_lua_thread(&self.lua)?;
let args = args.into_lua_multi(&self.lua)?; let args = args.into_lua_multi(&self.lua)?;
let thread = SchedulerThread::new(&self.lua, thread, args)?;
let thread_id = thread.id();
self.threads self.threads
.try_borrow_mut() .try_borrow_mut()
.into_lua_err() .into_lua_err()
.context("Failed to borrow threads vec")? .context("Failed to borrow threads vec")?
.push_front(SchedulerThread::new(&self.lua, thread, args)?); .push_front(thread);
self.thread_senders
.borrow_mut()
.insert(thread_id, SchedulerThreadSender::new(1));
Ok(()) Ok(thread_id)
} }
/** /**
@ -55,16 +71,48 @@ impl<'lua> SchedulerImpl {
&'lua self, &'lua self,
thread: impl IntoLuaThread<'lua>, thread: impl IntoLuaThread<'lua>,
args: impl IntoLuaMulti<'lua>, args: impl IntoLuaMulti<'lua>,
) -> LuaResult<()> { ) -> LuaResult<SchedulerThreadId> {
let thread = thread.into_lua_thread(&self.lua)?; let thread = thread.into_lua_thread(&self.lua)?;
let args = args.into_lua_multi(&self.lua)?; let args = args.into_lua_multi(&self.lua)?;
let thread = SchedulerThread::new(&self.lua, thread, args)?;
let thread_id = thread.id();
self.threads self.threads
.try_borrow_mut() .try_borrow_mut()
.into_lua_err() .into_lua_err()
.context("Failed to borrow threads vec")? .context("Failed to borrow threads vec")?
.push_back(SchedulerThread::new(&self.lua, thread, args)?); .push_back(thread);
self.thread_senders
.borrow_mut()
.insert(thread_id, SchedulerThreadSender::new(1));
Ok(()) Ok(thread_id)
}
/**
Waits for the given thread to finish running, and returns its result.
*/
pub async fn wait_for_thread(
&'lua self,
thread_id: SchedulerThreadId,
) -> LuaResult<LuaMultiValue<'lua>> {
let mut recv = {
let senders = self.thread_senders.borrow();
let sender = senders
.get(&thread_id)
.expect("Tried to wait for thread that is not queued");
sender.subscribe()
};
match recv.recv().await.expect("Failed to receive thread result") {
Err(e) => Err(e),
Ok(k) => {
let vals = self
.lua
.registry_value::<Vec<LuaValue>>(&k)
.expect("Received invalid registry key for thread");
Ok(LuaMultiValue::from_vec(vals))
}
}
} }
} }

View file

@ -1,4 +1,9 @@
use std::{cell::RefCell, collections::VecDeque, ops::Deref, sync::Arc}; use std::{
cell::RefCell,
collections::{HashMap, VecDeque},
ops::Deref,
sync::Arc,
};
use mlua::prelude::*; use mlua::prelude::*;
@ -9,7 +14,10 @@ mod traits;
mod impl_runner; mod impl_runner;
mod impl_threads; mod impl_threads;
use self::{state::SchedulerState, thread::SchedulerThread}; use self::{
state::SchedulerState,
thread::{SchedulerThread, SchedulerThreadId, SchedulerThreadSender},
};
/** /**
Scheduler for Lua threads. Scheduler for Lua threads.
@ -57,6 +65,7 @@ pub struct SchedulerImpl {
lua: Arc<Lua>, lua: Arc<Lua>,
state: SchedulerState, state: SchedulerState,
threads: RefCell<VecDeque<SchedulerThread>>, threads: RefCell<VecDeque<SchedulerThread>>,
thread_senders: RefCell<HashMap<SchedulerThreadId, SchedulerThreadSender>>,
} }
impl SchedulerImpl { impl SchedulerImpl {
@ -65,6 +74,7 @@ impl SchedulerImpl {
lua, lua,
state: SchedulerState::new(), state: SchedulerState::new(),
threads: RefCell::new(VecDeque::new()), threads: RefCell::new(VecDeque::new()),
thread_senders: RefCell::new(HashMap::new()),
} }
} }
} }

View file

@ -1,10 +1,38 @@
use std::sync::Arc;
use mlua::prelude::*; use mlua::prelude::*;
use rand::Rng;
use tokio::sync::broadcast::Sender;
/**
Type alias for a broadcast [`Sender`], which will
broadcast the result and return values of a lua thread.
The return values are stored in the lua registry as a
`Vec<LuaValue<'_>>`, and the registry key pointing to
those values will be sent using the broadcast sender.
*/
pub type SchedulerThreadSender = Sender<LuaResult<Arc<LuaRegistryKey>>>;
/**
Unique, randomly generated id for a scheduler thread.
*/
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct SchedulerThreadId(u128);
impl SchedulerThreadId {
fn gen() -> Self {
// FUTURE: Use a faster rng here?
Self(rand::thread_rng().gen())
}
}
/** /**
Container for registry keys that point to a thread and thread arguments. Container for registry keys that point to a thread and thread arguments.
*/ */
#[derive(Debug)] #[derive(Debug)]
pub(super) struct SchedulerThread { pub(super) struct SchedulerThread {
scheduler_id: SchedulerThreadId,
key_thread: LuaRegistryKey, key_thread: LuaRegistryKey,
key_args: LuaRegistryKey, key_args: LuaRegistryKey,
} }
@ -30,6 +58,7 @@ impl SchedulerThread {
.context("Failed to store value in registry")?; .context("Failed to store value in registry")?;
Ok(Self { Ok(Self {
scheduler_id: SchedulerThreadId::gen(),
key_thread, key_thread,
key_args, key_args,
}) })
@ -55,4 +84,11 @@ impl SchedulerThread {
(thread, args) (thread, args)
} }
/**
Retrieves the unique, randomly generated id for this scheduler thread.
*/
pub(super) fn id(&self) -> SchedulerThreadId {
self.scheduler_id
}
} }