Skip to main content

deadpool_runtime/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![deny(
4    nonstandard_style,
5    rust_2018_idioms,
6    rustdoc::broken_intra_doc_links,
7    rustdoc::private_intra_doc_links
8)]
9#![forbid(non_ascii_idents, unsafe_code)]
10#![warn(
11    deprecated_in_future,
12    missing_copy_implementations,
13    missing_debug_implementations,
14    missing_docs,
15    unreachable_pub,
16    unused_import_braces,
17    unused_labels,
18    unused_lifetimes,
19    unused_qualifications,
20    unused_results
21)]
22#![allow(clippy::uninlined_format_args)]
23
24use std::{any::Any, fmt, future::Future, time::Duration};
25
26/// Enumeration for picking a runtime implementation.
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
28pub enum Runtime {
29    #[cfg(feature = "tokio_1")]
30    #[cfg_attr(docsrs, doc(cfg(feature = "tokio_1")))]
31    /// [`tokio` 1.0](tokio_1) runtime.
32    Tokio1,
33
34    #[cfg(feature = "async-std_1")]
35    #[cfg_attr(docsrs, doc(cfg(feature = "async-std_1")))]
36    #[deprecated(
37        note = "Support for `async-std` is deprecated and will be removed in a future version. Consider using `tokio_1` or `smol_2` instead."
38    )]
39    /// [`async-std` 1.0](async_std_1) runtime.
40    AsyncStd1,
41
42    #[cfg(feature = "smol_2")]
43    #[cfg_attr(docsrs, doc(cfg(feature = "smol_2")))]
44    /// [`smol` 2.0](smol_2) runtime.
45    Smol2,
46}
47
48impl Runtime {
49    /// Requires a [`Future`] to complete before the specified `duration` has
50    /// elapsed.
51    ///
52    /// If the `future` completes before the `duration` has elapsed, then the
53    /// completed value is returned. Otherwise, an error is returned and
54    /// the `future` is canceled.
55    #[allow(unused_variables)]
56    pub async fn timeout<F>(&self, duration: Duration, future: F) -> Option<F::Output>
57    where
58        F: Future,
59    {
60        match self {
61            #[cfg(feature = "tokio_1")]
62            Self::Tokio1 => tokio_1::time::timeout(duration, future).await.ok(),
63            #[cfg(feature = "async-std_1")]
64            #[allow(deprecated)]
65            Self::AsyncStd1 => async_std_1::future::timeout(duration, future).await.ok(),
66            #[cfg(feature = "smol_2")]
67            Self::Smol2 => {
68                smol_2_futures_lite::future::or(async { Some(future.await) }, async {
69                    let _ = smol_2_async_io::Timer::after(duration).await;
70                    None
71                })
72                .await
73            }
74            #[allow(unreachable_patterns)]
75            _ => unreachable!(),
76        }
77    }
78
79    /// Runs the given closure on a thread where blocking is acceptable.
80    ///
81    /// # Errors
82    ///
83    /// See [`SpawnBlockingError`] for details.
84    #[allow(unused_variables)]
85    pub async fn spawn_blocking<F, R>(&self, f: F) -> Result<R, SpawnBlockingError>
86    where
87        F: FnOnce() -> R + Send + 'static,
88        R: Send + 'static,
89    {
90        match self {
91            #[cfg(feature = "tokio_1")]
92            Self::Tokio1 => tokio_1::task::spawn_blocking(f).await.map_err(|e| {
93                if e.is_cancelled() {
94                    SpawnBlockingError::Cancelled
95                } else {
96                    SpawnBlockingError::Panic(e.into_panic())
97                }
98            }),
99            #[cfg(feature = "async-std_1")]
100            #[allow(deprecated)]
101            Self::AsyncStd1 => Ok(async_std_1::task::spawn_blocking(f).await),
102            #[cfg(feature = "smol_2")]
103            Self::Smol2 => Ok(smol_2_blocking::unblock(f).await),
104            #[allow(unreachable_patterns)]
105            _ => unreachable!(),
106        }
107    }
108
109    /// Runs the given closure on a thread where blocking is acceptable.
110    ///
111    /// It works similar to [`Runtime::spawn_blocking()`] but doesn't return a
112    /// [`Future`] and is meant to be used for background tasks.
113    ///
114    /// # Errors
115    ///
116    /// See [`SpawnBlockingError`] for details.
117    #[allow(unused_variables)]
118    pub fn spawn_blocking_background<F>(&self, f: F) -> Result<(), SpawnBlockingError>
119    where
120        F: FnOnce() + Send + 'static,
121    {
122        match self {
123            #[cfg(feature = "tokio_1")]
124            Self::Tokio1 => {
125                drop(tokio_1::task::spawn_blocking(f));
126                Ok(())
127            }
128            #[cfg(feature = "async-std_1")]
129            #[allow(deprecated)]
130            Self::AsyncStd1 => {
131                drop(async_std_1::task::spawn_blocking(f));
132                Ok(())
133            }
134            #[cfg(feature = "smol_2")]
135            Self::Smol2 => {
136                drop(smol_2_blocking::unblock(f));
137                Ok(())
138            }
139            #[allow(unreachable_patterns)]
140            _ => unreachable!(),
141        }
142    }
143}
144
145/// Error of spawning a task on a thread where blocking is acceptable.
146#[derive(Debug)]
147pub enum SpawnBlockingError {
148    /// Spawned task has panicked.
149    Panic(Box<dyn Any + Send + 'static>),
150
151    /// Spawned task has been cancelled.
152    Cancelled,
153}
154
155impl fmt::Display for SpawnBlockingError {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        match self {
158            Self::Panic(p) => write!(f, "SpawnBlockingError: Panic: {:?}", p),
159            Self::Cancelled => write!(f, "SpawnBlockingError: Cancelled"),
160        }
161    }
162}
163
164impl std::error::Error for SpawnBlockingError {}
165
166#[cfg(all(test, feature = "tokio_1"))]
167mod tests_with_tokio_1 {
168    use super::{Runtime, SpawnBlockingError};
169
170    #[tokio_1::test(crate = "tokio_1")]
171    async fn test_spawning_blocking() {
172        assert!(Runtime::Tokio1.spawn_blocking(|| 42).await.is_ok());
173    }
174
175    #[tokio_1::test(crate = "tokio_1")]
176    async fn test_spawning_blocking_can_panic() {
177        assert!(matches!(
178            Runtime::Tokio1
179                .spawn_blocking(|| {
180                    panic!("42");
181                })
182                .await,
183            Err(SpawnBlockingError::Panic(_))
184        ));
185    }
186}