yash_executor/spawner.rs
1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2024 WATANABE Yuki
3
4//! Implementation of `Spawner`
5
6use crate::forwarder::Receiver;
7use crate::{ExecutorState, Spawner};
8use alloc::boxed::Box;
9use core::fmt::Debug;
10use core::future::{Future, IntoFuture};
11use core::pin::Pin;
12
13/// Error returned when a task cannot be spawned
14///
15/// This error is returned from [`Spawner`]'s methods when the executor has been
16/// dropped and the task cannot be spawned. The error contains the task that
17/// could not be spawned, allowing the caller to reuse the task.
18///
19/// `SpawnError` implements `Debug` for all `F` regardless of whether `F` does.
20/// This allows the use of `unwrap` and `expect` on `Result<_, SpawnError<F>>`.
21#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
22pub struct SpawnError<F>(pub F);
23
24impl<F> Debug for SpawnError<F> {
25 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
26 "SpawnError(_)".fmt(f)
27 }
28}
29// TODO Specialize Debug for F when F: Debug
30
31impl<'a> Spawner<'a> {
32 /// Creates a dummy `Spawner` that is not associated with any executor and
33 /// thus cannot spawn tasks.
34 #[must_use]
35 pub fn dead() -> Self {
36 Self {
37 state: Default::default(),
38 }
39 }
40
41 /// Adds the given future to the executor's task queue so that it will be
42 /// polled when the executor is run.
43 ///
44 /// The added task is not polled immediately. It will be polled when the
45 /// executor runs tasks.
46 ///
47 /// If the executor has been dropped, this method will return the future
48 /// wrapped in a `SpawnError`. The caller can then reuse the future with
49 /// another executor or handle the error in some other way.
50 ///
51 /// # Safety
52 ///
53 /// It may be surprising that this method is unsafe. The reason is that the
54 /// `Waker` available in the `Context` passed to the future's `poll` method
55 /// is thread-unsafe despite `Waker` being `Send` and `Sync`. The `Waker` is
56 /// not protected by a lock or atomic operation, and it is your sole
57 /// responsibility to ensure that the `Waker` is not passed to or accessed
58 /// from other threads.
59 #[allow(clippy::type_complexity)]
60 pub unsafe fn spawn_pinned(
61 &self,
62 future: Pin<Box<dyn Future<Output = ()> + 'a>>,
63 ) -> Result<(), SpawnError<Pin<Box<dyn Future<Output = ()> + 'a>>>> {
64 if let Some(state) = self.state.upgrade() {
65 ExecutorState::enqueue(&state, future);
66 Ok(())
67 } else {
68 Err(SpawnError(future))
69 }
70 }
71
72 /// Adds the given future to the executor's task queue so that it will be
73 /// polled when the executor is run.
74 ///
75 /// This method is an extended version of [`spawn_pinned`] that can take a
76 /// non-pinned future and may return a non-unit output. The result of the
77 /// future will be sent to the returned receiver.
78 ///
79 /// The added task is not polled immediately. It will be polled when the
80 /// executor runs tasks.
81 ///
82 /// If the executor has been dropped, this method will return the future
83 /// wrapped in a `SpawnError`. The caller can then reuse the future with
84 /// another executor or handle the error in some other way.
85 ///
86 /// # Safety
87 ///
88 /// See [`spawn_pinned`] for safety considerations.
89 ///
90 /// [`spawn_pinned`]: Self::spawn_pinned
91 pub unsafe fn spawn<F, T>(&self, future: F) -> Result<Receiver<T>, SpawnError<F>>
92 where
93 F: IntoFuture<Output = T> + 'a,
94 T: 'a,
95 {
96 if let Some(state) = self.state.upgrade() {
97 Ok(ExecutorState::enqueue_forwarding(&state, future))
98 } else {
99 Err(SpawnError(future))
100 }
101 }
102}