diff --git a/src/lune/builtins/net/mod.rs b/src/lune/builtins/net/mod.rs index 105a005..a18b3c0 100644 --- a/src/lune/builtins/net/mod.rs +++ b/src/lune/builtins/net/mod.rs @@ -168,7 +168,9 @@ where Ok(bound) => bound, }; // Start up our web server - sched.schedule_future_background(async move { + // TODO: Spawn a scheduler background task here, + // and communicate using an mpsc channel instead + sched.schedule_future_background_local(async move { bound .http1_only(true) // Web sockets can only use http1 .http1_keepalive(true) // Web sockets must be kept alive diff --git a/src/lune/builtins/net/server.rs b/src/lune/builtins/net/server.rs index ef3d48f..0cb8ac6 100644 --- a/src/lune/builtins/net/server.rs +++ b/src/lune/builtins/net/server.rs @@ -53,7 +53,7 @@ impl Service> for NetServiceInner { let sched = lua .app_data_ref::<&Scheduler>() .expect("Lua struct is missing scheduler"); - sched.schedule_future_background(async move { + sched.schedule_future_background_local(async move { // Create our new full websocket object, then // schedule our handler to get called asap let res = async move { diff --git a/src/lune/builtins/process/mod.rs b/src/lune/builtins/process/mod.rs index 09e7291..ac4070a 100644 --- a/src/lune/builtins/process/mod.rs +++ b/src/lune/builtins/process/mod.rs @@ -7,7 +7,6 @@ use std::{ use dunce::canonicalize; use mlua::prelude::*; use os_str_bytes::RawOsString; -use tokio::task; use crate::lune::{scheduler::Scheduler, util::TableBuilder}; @@ -164,12 +163,24 @@ async fn process_spawn( lua: &Lua, (program, args, options): (String, Option>, ProcessSpawnOptions), ) -> LuaResult { - // Spawn the new process in the background, - // letting the tokio runtime place it on a - // different thread if necessary, and wait - let (status, stdout, stderr) = task::spawn(spawn_command(program, args, options)) + /* + Spawn the new process in the background, letting the tokio + runtime place it on a different thread if possible / necessary + + Note that we have to use our scheduler here, we can't + use anything like tokio::task::spawn because our lua + scheduler will not drive those futures to completion + */ + let sched = lua + .app_data_ref::<&Scheduler>() + .expect("Lua struct is missing scheduler"); + + let fut = spawn_command(program, args, options); + let recv = sched.schedule_future_background(fut); + + let (status, stdout, stderr) = recv .await - .expect("Spawned process should not be cancellable")?; + .expect("Failed to receive result of spawned process")?; // NOTE: If an exit code was not given by the child process, // we default to 1 if it yielded any error output, otherwise 0 diff --git a/src/lune/scheduler/impl_async.rs b/src/lune/scheduler/impl_async.rs index 1a2a9c9..f97b7ca 100644 --- a/src/lune/scheduler/impl_async.rs +++ b/src/lune/scheduler/impl_async.rs @@ -1,5 +1,9 @@ use futures_util::Future; use mlua::prelude::*; +use tokio::{ + sync::oneshot::{self, Receiver}, + task, +}; use super::{IntoLuaThread, Scheduler}; @@ -29,18 +33,60 @@ where /** Schedules a plain future to run in the background. - Note that this will keep the scheduler alive even - if the future does not spawn any new lua threads. + This will spawn the future both on the scheduler and + potentially on a different thread using [`task::spawn`], + meaning the provided future must implement [`Send`]. + + Returns a [`Receiver`] which may be `await`-ed + to retrieve the result of the spawned future. + + This [`Receiver`] may be safely ignored if the result of the + spawned future is not needed, the future will run either way. */ - pub fn schedule_future_background(&self, fut: F) + pub fn schedule_future_background(&self, fut: F) -> Receiver where - F: Future + 'static, + F: Future + Send + 'static, + FR: Send + 'static, { + let (tx, rx) = oneshot::channel(); + + let handle = task::spawn(async move { + let res = fut.await; + tx.send(res).ok(); + }); + let futs = self .futures_background .try_lock() .expect("Failed to lock futures queue for background tasks"); - futs.push(Box::pin(fut)) + futs.push(Box::pin(async move { + handle.await.ok(); + })); + + rx + } + + /** + Equivalent to [`schedule_future_background`], except the + future is only spawned on the scheduler, on the main thread. + */ + pub fn schedule_future_background_local(&self, fut: F) -> Receiver + where + F: Future + 'static, + FR: 'static, + { + let (tx, rx) = oneshot::channel(); + + let futs = self + .futures_background + .try_lock() + .expect("Failed to lock futures queue for background tasks"); + futs.push(Box::pin(async move { + let res = fut.await; + tx.send(res).ok(); + })); + + rx } /**