From 5e4fc8b1384615f366323dfa203ada76f28052f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Laignel?= Date: Wed, 15 Dec 2021 12:48:06 +0100 Subject: [PATCH] ts/executor: relax the static bound on `enter` The function `enter` is executed in a blocking way from the caller's point of view. This means that we can guaranty that the provided function and its output will outlive the underlying Scheduler Task execution. This requires an unsafe call to `async_task::spawn_unchecked`. See: https://docs.rs/async-task/latest/async_task/fn.spawn_unchecked.html --- .../src/runtime/executor/context.rs | 6 +++--- .../src/runtime/executor/scheduler.rs | 20 +++++++++++++++---- .../threadshare/src/runtime/executor/task.rs | 16 +++++++++++---- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/generic/threadshare/src/runtime/executor/context.rs b/generic/threadshare/src/runtime/executor/context.rs index 7520197d..040d7391 100644 --- a/generic/threadshare/src/runtime/executor/context.rs +++ b/generic/threadshare/src/runtime/executor/context.rs @@ -209,10 +209,10 @@ impl Context { /// This will block current thread and would panic if run /// from the [`Context`]. #[track_caller] - pub fn enter(&self, f: F) -> O + pub fn enter<'a, F, O>(&'a self, f: F) -> O where - F: FnOnce() -> O + Send + 'static, - O: Send + 'static, + F: FnOnce() -> O + Send + 'a, + O: Send + 'a, { if let Some(cur) = Context::current().as_ref() { if cur == self { diff --git a/generic/threadshare/src/runtime/executor/scheduler.rs b/generic/threadshare/src/runtime/executor/scheduler.rs index c036f4c4..7a84ae65 100644 --- a/generic/threadshare/src/runtime/executor/scheduler.rs +++ b/generic/threadshare/src/runtime/executor/scheduler.rs @@ -391,14 +391,17 @@ impl Handle { /// /// This will block current thread and would panic if run /// from the [`Scheduler`]. - pub fn enter(&self, f: F) -> O + pub fn enter<'a, F, O>(&'a self, f: F) -> O where - F: FnOnce() -> O + Send + 'static, - O: Send + 'static, + F: FnOnce() -> O + Send + 'a, + O: Send + 'a, { assert!(!self.0.scheduler.is_current()); - let task = self.0.scheduler.tasks.add_sync(f); + // Safety: bounding `self` to `'a` and blocking on the task + // ensures that the lifetime bounds satisfy the safety + // requirements for `TaskQueue::add_sync`. + let task = unsafe { self.0.scheduler.tasks.add_sync(f) }; self.0.scheduler.wake_up(); futures::executor::block_on(task) } @@ -502,4 +505,13 @@ mod tests { assert_eq!(res, 42); } + + #[test] + fn enter_non_static() { + let handle = Scheduler::start("enter_non_static", Duration::from_millis(2)); + + let mut flag = false; + handle.enter(|| flag = true); + assert!(flag); + } } diff --git a/generic/threadshare/src/runtime/executor/task.rs b/generic/threadshare/src/runtime/executor/task.rs index 9839f22e..d29ef791 100644 --- a/generic/threadshare/src/runtime/executor/task.rs +++ b/generic/threadshare/src/runtime/executor/task.rs @@ -186,10 +186,16 @@ impl TaskQueue { (task_id, task) } - pub fn add_sync(&self, f: F) -> async_task::Task + /// Adds a task to be blocked on immediately. + /// + /// # Safety + /// + /// The function and its output must outlive the execution + /// of the resulting task and the retrieval of the result. + pub unsafe fn add_sync(&self, f: F) -> async_task::Task where - F: FnOnce() -> O + Send + 'static, - O: Send + 'static, + F: FnOnce() -> O + Send, + O: Send, { let tasks_clone = Arc::clone(&self.tasks); let mut tasks = self.tasks.lock().unwrap(); @@ -219,7 +225,9 @@ impl TaskQueue { }; let runnables = Arc::clone(&self.runnables); - let (runnable, task) = async_task::spawn(task_fut, move |runnable| { + // This is the unsafe call for which the lifetime must hold + // until the the Future is Ready and its Output retrieved. + let (runnable, task) = async_task::spawn_unchecked(task_fut, move |runnable| { runnables.push(runnable).unwrap(); }); tasks.insert(Task::new(task_id));