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
This commit is contained in:
François Laignel 2021-12-15 12:48:06 +01:00 committed by Sebastian Dröge
parent 21d41ca244
commit 5e4fc8b138
3 changed files with 31 additions and 11 deletions

View file

@ -209,10 +209,10 @@ impl Context {
/// This will block current thread and would panic if run
/// from the [`Context`].
#[track_caller]
pub fn enter<F, O>(&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 {

View file

@ -391,14 +391,17 @@ impl Handle {
///
/// This will block current thread and would panic if run
/// from the [`Scheduler`].
pub fn enter<F, O>(&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);
}
}

View file

@ -186,10 +186,16 @@ impl TaskQueue {
(task_id, task)
}
pub fn add_sync<F, O>(&self, f: F) -> async_task::Task<O>
/// 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<F, O>(&self, f: F) -> async_task::Task<O>
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));