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}