Skip to main content

agnostic_lite/
spawner.rs

1use core::future::Future;
2
3cfg_time!(
4  use core::time::Duration;
5);
6
7use crate::Yielder;
8
9#[cfg(feature = "smol")]
10macro_rules! join_handle {
11  ($handle:ty) => {
12    pin_project_lite::pin_project! {
13      /// An owned permission to join on a task (await its termination).
14      pub struct JoinHandle<T> {
15        #[pin]
16        handle: $handle,
17      }
18    }
19
20    impl<T> From<$handle> for JoinHandle<T> {
21      fn from(handle: $handle) -> Self {
22        Self { handle }
23      }
24    }
25
26    impl<T> core::future::Future for JoinHandle<T> {
27      type Output = core::result::Result<T, $crate::spawner::handle::JoinError>;
28
29      fn poll(
30        self: core::pin::Pin<&mut Self>,
31        cx: &mut core::task::Context<'_>,
32      ) -> core::task::Poll<Self::Output> {
33        let this = self.project();
34
35        match this.handle.poll(cx) {
36          core::task::Poll::Ready(v) => core::task::Poll::Ready(Ok(v)),
37          core::task::Poll::Pending => core::task::Poll::Pending,
38        }
39      }
40    }
41  };
42}
43
44#[cfg(any(feature = "smol", feature = "wasm", feature = "embassy"))]
45pub(crate) mod handle {
46  /// Task failed to execute to completion.
47  ///
48  /// This error will never be returned for `smol` runtime,
49  /// having it here is just for compatibility with other runtimes.
50  #[derive(Debug, Clone, PartialEq, Eq)]
51  pub struct JoinError(());
52
53  impl JoinError {
54    /// Create a new `JoinError`.
55    #[inline]
56    #[cfg(any(feature = "wasm", feature = "embassy"))]
57    pub(crate) const fn new() -> Self {
58      Self(())
59    }
60  }
61
62  impl core::fmt::Display for JoinError {
63    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
64      write!(f, "task failed to execute to completion")
65    }
66  }
67
68  impl core::error::Error for JoinError {}
69
70  #[cfg(feature = "std")]
71  impl From<JoinError> for std::io::Error {
72    fn from(_: JoinError) -> Self {
73      std::io::Error::other("join error")
74    }
75  }
76}
77
78/// Joinhanlde trait
79pub trait JoinHandle<O>: Future<Output = Result<O, Self::JoinError>> + Unpin {
80  /// The error type for the join handle
81  #[cfg(feature = "std")]
82  type JoinError: core::error::Error + Into<std::io::Error> + Send + Sync + 'static;
83
84  /// The error type for the join handle
85  #[cfg(not(feature = "std"))]
86  type JoinError: core::error::Error + Send + Sync + 'static;
87
88  /// Aborts the task related to this handle.
89  fn abort(self);
90
91  /// Detaches the task to let it keep running in the background.
92  fn detach(self)
93  where
94    Self: Sized,
95  {
96    drop(self)
97  }
98}
99
100/// Joinhanlde trait
101pub trait LocalJoinHandle<O>: Future<Output = Result<O, Self::JoinError>> + Unpin {
102  /// The error type for the join handle
103  #[cfg(feature = "std")]
104  type JoinError: core::error::Error + Into<std::io::Error> + 'static;
105  /// The error type for the join handle
106  #[cfg(not(feature = "std"))]
107  type JoinError: core::error::Error + 'static;
108
109  /// Detaches the task to let it keep running in the background.
110  fn detach(self)
111  where
112    Self: Sized,
113  {
114    drop(self)
115  }
116}
117
118/// A spawner trait for spawning futures.
119pub trait AsyncSpawner: Yielder + Copy + Send + Sync + 'static {
120  /// The handle returned by the spawner when a future is spawned.
121  type JoinHandle<O>: JoinHandle<O> + Send + Sync + 'static
122  where
123    O: Send + 'static;
124
125  /// Spawn a future.
126  fn spawn<F>(future: F) -> Self::JoinHandle<F::Output>
127  where
128    F::Output: Send + 'static,
129    F: Future + Send + 'static;
130
131  /// Spawn a future and detach it.
132  fn spawn_detach<F>(future: F)
133  where
134    F::Output: Send + 'static,
135    F: Future + Send + 'static,
136  {
137    core::mem::drop(Self::spawn(future));
138  }
139}
140
141/// A spawner trait for spawning futures.
142///
143/// # Local-executor context (contract note)
144///
145/// A local spawn needs a thread-local executor somebody drives, and not every
146/// runtime has an ambient one. An implementation **may panic** when the
147/// current thread has no local-executor context to target:
148///
149/// - `tokio`: panics outside a `LocalSet` or `LocalRuntime` (tokio's own
150///   documented `spawn_local` contract).
151/// - `smol`: **always panics** — smol has no ambient thread-local executor;
152///   drive a `smol::LocalExecutor` yourself and spawn onto it directly.
153/// - `embassy`: always panics — its global spawner is `Send`-only.
154pub trait AsyncLocalSpawner: Yielder + Copy + 'static {
155  /// The handle returned by the spawner when a future is spawned.
156  type JoinHandle<O>: LocalJoinHandle<O> + 'static
157  where
158    O: 'static;
159
160  /// Spawn a future.
161  fn spawn_local<F>(future: F) -> Self::JoinHandle<F::Output>
162  where
163    F::Output: 'static,
164    F: Future + 'static;
165
166  /// Spawn a future and detach it.
167  fn spawn_local_detach<F>(future: F)
168  where
169    F::Output: 'static,
170    F: Future + 'static,
171  {
172    core::mem::drop(Self::spawn_local(future));
173  }
174}
175
176/// A spawner trait for spawning blocking.
177pub trait AsyncBlockingSpawner: Yielder + Copy + 'static {
178  /// The join handle type for blocking tasks
179  type JoinHandle<R>: JoinHandle<R> + Send + 'static
180  where
181    R: Send + 'static;
182
183  /// Spawn a blocking function onto the runtime
184  fn spawn_blocking<F, R>(f: F) -> Self::JoinHandle<R>
185  where
186    F: FnOnce() -> R + Send + 'static,
187    R: Send + 'static;
188
189  /// Spawn a blocking function onto the runtime and detach it
190  fn spawn_blocking_detach<F, R>(f: F)
191  where
192    F: FnOnce() -> R + Send + 'static,
193    R: Send + 'static,
194  {
195    Self::spawn_blocking(f).detach();
196  }
197}
198
199/// Canceled
200#[derive(Debug, Clone, Copy)]
201#[cfg(all(
202  feature = "time",
203  any(
204    feature = "tokio",
205    feature = "smol",
206    feature = "wasm",
207    feature = "embassy"
208  )
209))]
210pub(crate) struct Canceled;
211
212#[cfg(all(
213  feature = "time",
214  any(
215    feature = "tokio",
216    feature = "smol",
217    feature = "wasm",
218    feature = "embassy"
219  )
220))]
221impl core::fmt::Display for Canceled {
222  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
223    write!(f, "after canceled")
224  }
225}
226
227#[cfg(all(
228  feature = "time",
229  any(
230    feature = "tokio",
231    feature = "smol",
232    feature = "wasm",
233    feature = "embassy"
234  )
235))]
236impl core::error::Error for Canceled {}
237
238/// Error of [`AfterHandle`]'s output
239#[cfg(feature = "time")]
240#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
241#[derive(Debug)]
242pub enum AfterHandleError<E> {
243  /// The after function was canceled
244  Canceled,
245  /// Task failed to execute to completion.
246  Join(E),
247}
248
249#[cfg(feature = "time")]
250impl<E: core::fmt::Display> core::fmt::Display for AfterHandleError<E> {
251  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
252    match self {
253      Self::Canceled => write!(f, "after function was canceled"),
254      Self::Join(e) => write!(f, "{e}"),
255    }
256  }
257}
258
259#[cfg(feature = "time")]
260impl<E: core::error::Error> core::error::Error for AfterHandleError<E> {}
261
262#[cfg(all(feature = "time", feature = "std"))]
263impl<E: core::error::Error + Send + Sync + 'static> From<AfterHandleError<E>> for std::io::Error {
264  fn from(value: AfterHandleError<E>) -> Self {
265    match value {
266      AfterHandleError::Canceled => std::io::Error::other("task canceled"),
267      AfterHandleError::Join(e) => std::io::Error::other(e),
268    }
269  }
270}
271
272#[cfg(all(
273  feature = "time",
274  any(
275    feature = "tokio",
276    feature = "smol",
277    feature = "wasm",
278    feature = "embassy"
279  )
280))]
281pub(crate) struct AfterHandleSignals {
282  finished: core::sync::atomic::AtomicBool,
283  expired: core::sync::atomic::AtomicBool,
284}
285
286#[cfg(all(
287  feature = "time",
288  any(
289    feature = "tokio",
290    feature = "smol",
291    feature = "wasm",
292    feature = "embassy"
293  )
294))]
295impl AfterHandleSignals {
296  #[inline]
297  pub(crate) const fn new() -> Self {
298    Self {
299      finished: core::sync::atomic::AtomicBool::new(false),
300      expired: core::sync::atomic::AtomicBool::new(false),
301    }
302  }
303
304  #[inline]
305  pub(crate) fn set_finished(&self) {
306    self
307      .finished
308      .store(true, core::sync::atomic::Ordering::Release);
309  }
310
311  #[inline]
312  pub(crate) fn set_expired(&self) {
313    self
314      .expired
315      .store(true, core::sync::atomic::Ordering::Release);
316  }
317
318  #[inline]
319  pub(crate) fn is_finished(&self) -> bool {
320    self.finished.load(core::sync::atomic::Ordering::Acquire)
321  }
322
323  #[inline]
324  pub(crate) fn is_expired(&self) -> bool {
325    self.expired.load(core::sync::atomic::Ordering::Acquire)
326  }
327}
328
329/// The handle returned by the [`AsyncAfterSpawner`] when a after future is spawned.
330///
331/// Drop the handle to detach the task.
332#[cfg(feature = "time")]
333#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
334pub trait AfterHandle<F: Send + 'static>:
335  Send + Sync + Future<Output = Result<F, Self::JoinError>> + 'static
336{
337  /// The join error type for the join handle
338  #[cfg(feature = "std")]
339  type JoinError: core::error::Error + Into<std::io::Error> + Send + Sync + 'static;
340  /// The join error type for the join handle
341  #[cfg(not(feature = "std"))]
342  type JoinError: core::error::Error + 'static;
343
344  /// Cancels the task related to this handle.
345  ///
346  /// Returns the task’s output if it was completed just before it got canceled, or `None` if it didn’t complete.
347  fn cancel(self) -> impl Future<Output = Option<Result<F, Self::JoinError>>> + Send;
348
349  /// Resets the delay of the task related to this handle.
350  fn reset(&self, duration: core::time::Duration);
351
352  /// Aborts the task related to this handle.
353  fn abort(self);
354
355  /// Detaches the task to let it keep running in the background.
356  fn detach(self)
357  where
358    Self: Sized,
359  {
360    drop(self)
361  }
362
363  /// Returns `true` if the timer has expired.
364  fn is_expired(&self) -> bool;
365
366  /// Returns `true` if the task has finished.
367  fn is_finished(&self) -> bool;
368}
369
370/// A spawner trait for spawning futures. Go's `time.AfterFunc` equivalent.
371#[cfg(feature = "time")]
372#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
373pub trait AsyncAfterSpawner: Copy + Send + Sync + 'static {
374  /// The instant type for the spawner
375  type Instant: crate::time::Instant;
376
377  /// The handle returned by the spawner when a future is spawned.
378  type JoinHandle<F>: AfterHandle<F>
379  where
380    F: Send + 'static;
381
382  /// Spawn a future onto the runtime and run the given future after the given duration
383  fn spawn_after<F>(duration: Duration, future: F) -> Self::JoinHandle<F::Output>
384  where
385    F::Output: Send + 'static,
386    F: Future + Send + 'static;
387
388  /// Spawn and detach a future onto the runtime and run the given future after the given duration
389  fn spawn_after_detach<F>(duration: Duration, future: F)
390  where
391    F::Output: Send + 'static,
392    F: Future + Send + 'static,
393  {
394    core::mem::drop(Self::spawn_after(duration, future));
395  }
396
397  /// Spawn a future onto the runtime and run the given future after reach the given instant
398  fn spawn_after_at<F>(instant: Self::Instant, future: F) -> Self::JoinHandle<F::Output>
399  where
400    F::Output: Send + 'static,
401    F: Future + Send + 'static;
402
403  /// Spawn and detach a future onto the runtime and run the given future after reach the given instant
404  fn spawn_after_at_detach<F>(instant: Self::Instant, future: F)
405  where
406    F::Output: Send + 'static,
407    F: Future + Send + 'static,
408  {
409    Self::spawn_after_at(instant, future).detach()
410  }
411}