pub trait SomeLocalExecutor<'future>: Debug {
type ExecutorNotifier: ExecutorNotified;
// Required methods
fn spawn_local<F, Notifier: ObserverNotified<F::Output>>(
&mut self,
task: Task<F, Notifier>,
) -> impl Observer<Value = F::Output>
where Self: Sized,
F: Future + 'future,
<F as Future>::Output: Unpin + 'static;
fn spawn_local_async<F, Notifier: ObserverNotified<F::Output>>(
&mut self,
task: Task<F, Notifier>,
) -> impl Future<Output = impl Observer<Value = F::Output>>
where Self: Sized,
F: Future + 'future,
<F as Future>::Output: 'static + Unpin;
fn spawn_local_objsafe(
&mut self,
task: ObjSafeLocalTask,
) -> BoxedLocalObserver;
fn spawn_local_objsafe_async<'s>(
&'s mut self,
task: ObjSafeLocalTask,
) -> BoxedLocalObserverFuture<'s>;
fn executor_notifier(&mut self) -> Option<Self::ExecutorNotifier>;
}Expand description
A trait for executors that can spawn tasks onto the local thread.
This type can spawn futures that are !Send.
§About the lifetime parameter
The lifetime parameter defines the lifetime of the executor itself, which is really the longest lifetime of any future it may be executing.
To understand this, it is helpful to consider 3 cases.
§Multithreaded executors
Multithreaded executors along the lines of SomeExecutor generally require their futures to be 'static.
This is vaguely intuitive in the “it is nice to be able to move the future to another thread” sense,
but in full detail it is less intuitive than it seems.
If a future refers to data on the local stack frame, then the future may dangle if:
- The user returns upstack before the future completes, this could maybe be resolved with clever lifetimes?
- The thread panics before the future completes, this is a hard problem.
- The “thread” is really an async context, which is cancelled before the future completes, this is a hard problem.
- The thread is terminated by the OS for some reason, this is a hard problem.
For at least reasons 2-4, SomeExecutor implicitly requires 'static futures.
§Local executors, globally-scoped
Now let us consider a local executor with global scope (such as a main thread executor). These executors disptach onto the local thread but exist for a long time, such as the lifetime of the program. These types of executors have requirements not so dissimilar from multithreaded executors:
- The user returns upstack before the future completes. This is probably fine, if we poison the executor in some way (includingstatically), although care must be taken to ensure that the future’s memory does not escape. For example, in a DMA-type operation where the OS is writing to a buffer independently, that buffer must not be located on the stack.
- If the thread panics before the future completes, that’s probably fine as well since the executor is inherently poisoned by the panic. See the DMA-style caveat above.
- If the async context is cancelled before the future completes, that’s a big problem. It is a hard one to solve because it’s not obvious how to poison the executor deterministically.
- If the thread is terminated by the OS, the executor is poisoned so that’s ok.
Due to reason 3, local executors with global scope generally require 'static futures.
§Local executors, locally scoped
Alternatively we may spin up an executor e.g. on a stack frame, for a specific task. In that case:
- Returning upstack inherently poisons the executor, with sensible lifetime design/analysis.
- Panicking poisons the executor.
- Cancellation posions the executor since the executor is on the same stackframe
- The thread is terminated by the OS, the executor is poisoned.
In this case, the executor can support non-'static futures.
§Overall
In summary, for executors with global scope, `‘static’ should be chosen as the lifetime parameter.
For executors with local scope, this trait can be implemented for any lifetime.
When in doubt, the 'static lifetime can be chosen and upgraded later.
Required Associated Types§
Sourcetype ExecutorNotifier: ExecutorNotified
type ExecutorNotifier: ExecutorNotified
The notifier handle that can wake or signal work for this executor.
Required Methods§
Sourcefn spawn_local<F, Notifier: ObserverNotified<F::Output>>(
&mut self,
task: Task<F, Notifier>,
) -> impl Observer<Value = F::Output>
fn spawn_local<F, Notifier: ObserverNotified<F::Output>>( &mut self, task: Task<F, Notifier>, ) -> impl Observer<Value = F::Output>
Sourcefn spawn_local_async<F, Notifier: ObserverNotified<F::Output>>(
&mut self,
task: Task<F, Notifier>,
) -> impl Future<Output = impl Observer<Value = F::Output>>
fn spawn_local_async<F, Notifier: ObserverNotified<F::Output>>( &mut self, task: Task<F, Notifier>, ) -> impl Future<Output = impl Observer<Value = F::Output>>
Spawns a future onto the runtime.
Like Self::spawn_local, but some implementors may have a fast path for the async context.
Sourcefn spawn_local_objsafe(&mut self, task: ObjSafeLocalTask) -> BoxedLocalObserver
fn spawn_local_objsafe(&mut self, task: ObjSafeLocalTask) -> BoxedLocalObserver
Spawns a future onto the runtime.
§Note
This differs from SomeExecutor::spawn in that we take a boxed future, since we can’t have generic fn. Implementations probably pin this with Box::into_pin.
Sourcefn spawn_local_objsafe_async<'s>(
&'s mut self,
task: ObjSafeLocalTask,
) -> BoxedLocalObserverFuture<'s>
fn spawn_local_objsafe_async<'s>( &'s mut self, task: ObjSafeLocalTask, ) -> BoxedLocalObserverFuture<'s>
Object-safe async variant of Self::spawn_local_objsafe.
Sourcefn executor_notifier(&mut self) -> Option<Self::ExecutorNotifier>
fn executor_notifier(&mut self) -> Option<Self::ExecutorNotifier>
Returns an executor notifier when notification is supported.