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)]
28#[non_exhaustive]
29pub enum Runtime {
30    #[cfg(feature = "tokio_1")]
31    #[cfg_attr(docsrs, doc(cfg(feature = "tokio_1")))]
32    /// [`tokio` 1.0](tokio_1) runtime.
33    Tokio1,
34
35    #[cfg(feature = "async-std_1")]
36    #[cfg_attr(docsrs, doc(cfg(feature = "async-std_1")))]
37    #[deprecated(
38        note = "Support for `async-std` is deprecated and will be removed in a future version. Consider using `tokio_1` or `smol_2` instead."
39    )]
40    /// [`async-std` 1.0](async_std_1) runtime.
41    AsyncStd1,
42
43    #[cfg(feature = "smol_2")]
44    #[cfg_attr(docsrs, doc(cfg(feature = "smol_2")))]
45    /// `smol` 2.0 runtime.
46    Smol2,
47}
48
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)]
56pub async fn timeout<F>(runtime: Runtime, duration: Duration, future: F) -> Option<F::Output>
57where
58    F: Future,
59{
60    match runtime {
61        #[cfg(feature = "tokio_1")]
62        Runtime::Tokio1 => tokio_1::time::timeout(duration, future).await.ok(),
63        #[cfg(feature = "async-std_1")]
64        #[allow(deprecated)]
65        Runtime::AsyncStd1 => async_std_1::future::timeout(duration, future).await.ok(),
66        #[cfg(feature = "smol_2")]
67        Runtime::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)]
85pub async fn spawn_blocking<F, R>(runtime: Runtime, f: F) -> Result<R, SpawnBlockingError>
86where
87    F: FnOnce() -> R + Send + 'static,
88    R: Send + 'static,
89{
90    match runtime {
91        #[cfg(feature = "tokio_1")]
92        Runtime::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        Runtime::AsyncStd1 => Ok(async_std_1::task::spawn_blocking(f).await),
102        #[cfg(feature = "smol_2")]
103        Runtime::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 [`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)]
118pub fn spawn_blocking_background<F>(runtime: Runtime, f: F) -> Result<(), SpawnBlockingError>
119where
120    F: FnOnce() + Send + 'static,
121{
122    match runtime {
123        #[cfg(feature = "tokio_1")]
124        Runtime::Tokio1 => {
125            drop(tokio_1::task::spawn_blocking(f));
126            Ok(())
127        }
128        #[cfg(feature = "async-std_1")]
129        #[allow(deprecated)]
130        Runtime::AsyncStd1 => {
131            drop(async_std_1::task::spawn_blocking(f));
132            Ok(())
133        }
134        #[cfg(feature = "smol_2")]
135        Runtime::Smol2 => {
136            drop(smol_2_blocking::unblock(f));
137            Ok(())
138        }
139        #[allow(unreachable_patterns)]
140        _ => unreachable!(),
141    }
142}
143
144/// Error of spawning a task on a thread where blocking is acceptable.
145#[derive(Debug)]
146pub enum SpawnBlockingError {
147    /// Spawned task has panicked.
148    Panic(Box<dyn Any + Send + 'static>),
149
150    /// Spawned task has been cancelled.
151    Cancelled,
152}
153
154impl fmt::Display for SpawnBlockingError {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        match self {
157            Self::Panic(p) => write!(f, "SpawnBlockingError: Panic: {:?}", p),
158            Self::Cancelled => write!(f, "SpawnBlockingError: Cancelled"),
159        }
160    }
161}
162
163impl std::error::Error for SpawnBlockingError {}
164
165#[cfg(all(test, feature = "tokio_1"))]
166mod tests_with_tokio_1 {
167    use super::*;
168
169    #[tokio_1::test(crate = "tokio_1")]
170    async fn test_spawning_blocking() {
171        assert!(spawn_blocking(Runtime::Tokio1, || 42).await.is_ok());
172    }
173
174    #[tokio_1::test(crate = "tokio_1")]
175    async fn test_spawning_blocking_can_panic() {
176        assert!(matches!(
177            spawn_blocking(Runtime::Tokio1, || {
178                panic!("42");
179            })
180            .await,
181            Err(SpawnBlockingError::Panic(_))
182        ));
183    }
184}