Skip to main content

zbus/abstractions/
executor.rs

1#[cfg(feature = "async-io")]
2use async_executor::Executor as AsyncExecutor;
3#[cfg(feature = "async-io")]
4use async_task::Task as AsyncTask;
5#[cfg(feature = "tokio")]
6use std::io::Error;
7#[cfg(not(feature = "async-io"))]
8use std::marker::PhantomData;
9#[cfg(feature = "async-io")]
10use std::sync::Arc;
11use std::{
12    future::Future,
13    io::Result,
14    pin::Pin,
15    task::{Context, Poll},
16};
17#[cfg(feature = "tokio")]
18use tokio::task::JoinHandle;
19
20/// A wrapper around the underlying runtime/executor.
21///
22/// This is used to run asynchronous tasks internally and allows integration with various runtimes.
23/// See [`crate::Connection::executor`] for an example of integration with external runtimes.
24///
25/// **Note:** You can (and should) completely ignore this type when the `tokio` backend is in use.
26#[derive(Debug, Clone)]
27pub struct Executor<'a> {
28    #[cfg(feature = "async-io")]
29    async_io: Option<Arc<AsyncExecutor<'a>>>,
30    #[cfg(not(feature = "async-io"))]
31    phantom: PhantomData<&'a ()>,
32}
33
34impl Executor<'_> {
35    /// Spawns a task onto the executor.
36    #[doc(hidden)]
37    pub fn spawn<T: Send + 'static>(
38        &self,
39        future: impl Future<Output = T> + Send + 'static,
40        #[allow(unused)] name: &str,
41    ) -> Task<T> {
42        #[cfg(feature = "async-io")]
43        if let Some(executor) = &self.async_io {
44            return Task::from_async_io(executor.spawn(future));
45        }
46
47        #[cfg(feature = "tokio")]
48        return Task::from_tokio(tokio_spawn(future, name));
49
50        #[cfg(all(feature = "async-io", not(feature = "tokio")))]
51        unreachable!("async-io executor is always `Some` when tokio is disabled")
52    }
53
54    /// Return `true` if there are no unfinished tasks.
55    ///
56    /// With the `tokio` backend in use, this always returns `true`.
57    pub fn is_empty(&self) -> bool {
58        #[cfg(feature = "async-io")]
59        if let Some(executor) = &self.async_io {
60            return executor.is_empty();
61        }
62
63        true
64    }
65
66    /// Runs a single task.
67    ///
68    /// With the `tokio` backend in use, it's a noop and never returns.
69    pub async fn tick(&self) {
70        #[cfg(feature = "async-io")]
71        if let Some(executor) = &self.async_io {
72            executor.tick().await;
73            // Skip the `tokio` branch below (only present when both backends are compiled in).
74            #[cfg(feature = "tokio")]
75            return;
76        }
77
78        #[cfg(feature = "tokio")]
79        std::future::pending::<()>().await;
80    }
81
82    /// Create a new `Executor`.
83    pub(crate) fn new() -> Self {
84        Self {
85            #[cfg(feature = "async-io")]
86            async_io: (!super::use_tokio()).then(|| Arc::new(AsyncExecutor::new())),
87            #[cfg(not(feature = "async-io"))]
88            phantom: PhantomData,
89        }
90    }
91
92    /// Whether this executor needs an external driver thread (only the `async-io` backend does).
93    #[cfg(feature = "async-io")]
94    pub(crate) fn needs_internal_driver(&self) -> bool {
95        self.async_io.is_some()
96    }
97
98    /// Runs the executor until the given future completes.
99    ///
100    /// With the `tokio` backend in use, it just awaits on the `future`.
101    pub(crate) async fn run<T>(&self, future: impl Future<Output = T>) -> T {
102        #[cfg(feature = "async-io")]
103        if let Some(executor) = &self.async_io {
104            return executor.run(future).await;
105        }
106
107        future.await
108    }
109}
110
111#[cfg(feature = "tokio")]
112fn tokio_spawn<T: Send + 'static>(
113    future: impl Future<Output = T> + Send + 'static,
114    #[allow(unused)] name: &str,
115) -> JoinHandle<T> {
116    #[cfg(tokio_unstable)]
117    {
118        tokio::task::Builder::new()
119            .name(name)
120            .spawn(future)
121            // SAFETY: Looking at the code, this call always returns an `Ok`.
122            .unwrap()
123    }
124    #[cfg(not(tokio_unstable))]
125    {
126        tokio::task::spawn(future)
127    }
128}
129
130/// A wrapper around the task API of the underlying runtime/executor.
131///
132/// This follows the semantics of `async_task::Task` on drop:
133///
134/// * it will be cancelled, rather than detached. For detaching, use the `detach` method.
135/// * errors from the task cancellation will will be ignored. If you need to know about task errors,
136///   convert the task to a `FallibleTask` using the `fallible` method.
137#[doc(hidden)]
138#[derive(Debug)]
139pub struct Task<T> {
140    #[cfg(feature = "async-io")]
141    async_io: Option<AsyncTask<T>>,
142    #[cfg(feature = "tokio")]
143    tokio: Option<JoinHandle<T>>,
144}
145
146impl<T> Task<T> {
147    #[cfg(feature = "async-io")]
148    fn from_async_io(task: AsyncTask<T>) -> Self {
149        Self {
150            async_io: Some(task),
151            #[cfg(feature = "tokio")]
152            tokio: None,
153        }
154    }
155
156    #[cfg(feature = "tokio")]
157    fn from_tokio(handle: JoinHandle<T>) -> Self {
158        Self {
159            #[cfg(feature = "async-io")]
160            async_io: None,
161            tokio: Some(handle),
162        }
163    }
164
165    /// Detaches the task to let it keep running in the background.
166    #[allow(unused_mut)]
167    pub fn detach(mut self) {
168        #[cfg(feature = "async-io")]
169        if let Some(task) = self.async_io.take() {
170            task.detach();
171        }
172
173        #[cfg(feature = "tokio")]
174        if let Some(handle) = self.tokio.take() {
175            // Dropping a tokio `JoinHandle` detaches it.
176            drop(handle);
177        }
178    }
179}
180
181impl<T> Task<T>
182where
183    T: Send + 'static,
184{
185    /// Launch the given blocking function in a task.
186    ///
187    /// `blocking::unblock` needs no runtime, so async-io's pool is used unless a tokio runtime is
188    /// active.
189    #[allow(unused)]
190    pub(crate) fn spawn_blocking<F>(f: F, #[allow(unused)] name: &str) -> Self
191    where
192        F: FnOnce() -> T + Send + 'static,
193    {
194        super::select_runtime! {
195            tokio: Self::from_tokio(tokio_spawn_blocking(f, name)),
196            async_io: Self::from_async_io(blocking::unblock(f)),
197        }
198    }
199}
200
201#[cfg(feature = "tokio")]
202fn tokio_spawn_blocking<F, T>(f: F, #[allow(unused)] name: &str) -> JoinHandle<T>
203where
204    F: FnOnce() -> T + Send + 'static,
205    T: Send + 'static,
206{
207    #[cfg(tokio_unstable)]
208    {
209        tokio::task::Builder::new()
210            .name(name)
211            .spawn_blocking(f)
212            // SAFETY: Looking at the code, this call always returns an `Ok`.
213            .unwrap()
214    }
215    #[cfg(not(tokio_unstable))]
216    {
217        tokio::task::spawn_blocking(f)
218    }
219}
220
221impl<T> Drop for Task<T> {
222    fn drop(&mut self) {
223        #[cfg(feature = "tokio")]
224        if let Some(join_handle) = self.tokio.take() {
225            join_handle.abort();
226        }
227    }
228}
229
230impl<T> Future for Task<T> {
231    type Output = Result<T>;
232
233    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
234        let this = self.get_mut();
235
236        #[cfg(feature = "async-io")]
237        if let Some(task) = &mut this.async_io {
238            return Pin::new(task).poll(cx).map(Ok);
239        }
240
241        #[cfg(feature = "tokio")]
242        if let Some(handle) = &mut this.tokio {
243            return Pin::new(handle).poll(cx).map(|r| match r {
244                Ok(v) => Ok(v),
245                Err(e) => {
246                    if e.is_cancelled() {
247                        Err(Error::other("tokio::task cancelled"))
248                    } else {
249                        panic!("tokio::task::JoinHandle error: {e}")
250                    }
251                }
252            });
253        }
254
255        unreachable!("Task always has exactly one backend")
256    }
257}