Skip to main content

async_rt/
task.rs

1use crate::error::TimeoutError;
2use crate::global::BuiltinExecutor;
3use crate::{
4    AbortableJoinHandle, CommunicationTask, Executor, ExecutorBlockOn, ExecutorBlocking,
5    ExecutorTimeout, JoinHandle, Scope, ScopeExecutor, UnboundedCommunicationTask,
6};
7use futures::channel::mpsc::{Receiver, UnboundedReceiver};
8use parking_lot::{Condvar, Mutex};
9use std::sync::LazyLock;
10
11struct ExecutorState {
12    executor: BuiltinExecutor,
13    active: usize,
14}
15
16struct ExecutorLock {
17    state: Mutex<ExecutorState>,
18    available: Condvar,
19}
20
21static EXECUTOR: LazyLock<ExecutorLock> = LazyLock::new(|| ExecutorLock {
22    state: Mutex::new(ExecutorState {
23        executor: BuiltinExecutor::default(),
24        active: 0,
25    }),
26    available: Condvar::new(),
27});
28
29pub(crate) fn executor() -> BuiltinExecutor {
30    EXECUTOR.state.lock().executor
31}
32
33pub(crate) struct ExecutorGuard {
34    _private: (),
35}
36
37impl Drop for ExecutorGuard {
38    fn drop(&mut self) {
39        let mut state = EXECUTOR.state.lock();
40        state.active -= 1;
41        if state.active == 0 {
42            EXECUTOR.available.notify_all();
43        }
44    }
45}
46
47pub(crate) fn set_executor(executor: BuiltinExecutor) -> ExecutorGuard {
48    let mut state = EXECUTOR.state.lock();
49    while state.active != 0 && (state.executor != executor || executor.is_exclusive()) {
50        EXECUTOR.available.wait(&mut state);
51    }
52    if state.active == 0 {
53        state.executor = executor;
54    }
55    state.active += 1;
56    ExecutorGuard { _private: () }
57}
58
59/// Returns an optional runtime name of the executor.
60pub fn runtime_type() -> Option<&'static str> {
61    executor().runtime_type()
62}
63
64/// Spawns a new asynchronous task in the background, returning a Future [`JoinHandle`] for it.
65pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
66where
67    F: Future + Send + 'static,
68    F::Output: Send + 'static,
69{
70    executor().spawn(future)
71}
72
73pub fn spawn_blocking<F, T>(future: F) -> JoinHandle<T>
74where
75    F: FnOnce() -> T + Send + 'static,
76    T: Send + 'static,
77{
78    executor().spawn_blocking(future)
79}
80
81/// Spawns a new asynchronous task in the background, returning an abortable handle that will cancel the task
82/// once the handle is dropped.
83///
84/// Note: This function is used if the task is expected to run until the handle is dropped. It is recommended to use
85/// [`spawn`] or [`dispatch`] otherwise.
86pub fn spawn_abortable<F>(future: F) -> AbortableJoinHandle<F::Output>
87where
88    F: Future + Send + 'static,
89    F::Output: Send + 'static,
90{
91    executor().spawn_abortable(future)
92}
93
94/// Spawns a new asynchronous task that must complete within `duration`.
95///
96/// If it does not, the future is dropped and the task completes with [`TimeoutError`].
97pub fn spawn_timeout<F>(
98    duration: std::time::Duration,
99    future: F,
100) -> JoinHandle<Result<F::Output, TimeoutError>>
101where
102    F: Future + Send + 'static,
103    F::Output: Send + 'static,
104{
105    executor().spawn_timeout(duration, future)
106}
107
108/// Spawns a task after waiting for a duration before the task is polled.
109pub fn spawn_delay<F>(duration: std::time::Duration, future: F) -> JoinHandle<F::Output>
110where
111    F: Future + Send + 'static,
112    F::Output: Send + 'static,
113{
114    executor().spawn_delay(duration, future)
115}
116
117/// Spawns a new asynchronous task, returning an abortable handle, that must complete within
118/// `duration`.
119///
120/// If it does not, the future is dropped and the task completes with [`TimeoutError`].
121pub fn spawn_abortable_timeout<F>(
122    duration: std::time::Duration,
123    future: F,
124) -> AbortableJoinHandle<Result<F::Output, TimeoutError>>
125where
126    F: Future + Send + 'static,
127    F::Output: Send + 'static,
128{
129    executor().spawn_abortable_timeout(duration, future)
130}
131
132/// Spawns a task after waiting for a duration before the task is polled.
133pub fn spawn_abortable_delay<F>(
134    duration: std::time::Duration,
135    future: F,
136) -> AbortableJoinHandle<F::Output>
137where
138    F: Future + Send + 'static,
139    F::Output: Send + 'static,
140{
141    executor().spawn_abortable_delay(duration, future)
142}
143
144/// Spawns a new asynchronous task in the background without a handle.
145/// Basically the same as [`spawn`].
146pub fn dispatch<F>(future: F)
147where
148    F: Future + Send + 'static,
149    F::Output: Send + 'static,
150{
151    executor().dispatch(future);
152}
153
154/// Spawns a new asynchronous task that accepts messages to the task.
155/// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
156/// (in other words, all handles are dropped), the task would be aborted.
157pub fn spawn_coroutine<T, F, Fut>(f: F) -> CommunicationTask<T>
158where
159    F: FnMut(T) -> Fut + Send + 'static,
160    Fut: Future<Output = ()> + Send + 'static,
161    T: Send + 'static,
162{
163    executor().spawn_coroutine(f)
164}
165
166/// Spawns a new asynchronous task that accepts messages to the task with a set buffer.
167/// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
168/// (in other words, all handles are dropped), the task would be aborted.
169pub fn spawn_coroutine_with_buffer<T, F, Fut>(buffer: usize, f: F) -> CommunicationTask<T>
170where
171    F: FnMut(T) -> Fut + Send + 'static,
172    Fut: Future<Output = ()> + Send + 'static,
173    T: Send + 'static,
174{
175    executor().spawn_coroutine_with_buffer(buffer, f)
176}
177
178/// Spawns a new asynchronous task that accepts unbounded messages to the task.
179/// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
180/// (in other words, all handles are dropped), the task would be aborted.
181pub fn spawn_unbounded_coroutine<T, F, Fut>(f: F) -> UnboundedCommunicationTask<T>
182where
183    F: FnMut(T) -> Fut + Send + 'static,
184    Fut: Future<Output = ()> + Send + 'static,
185    T: Send + 'static,
186{
187    executor().spawn_unbounded_coroutine(f)
188}
189
190/// Spawns a new asynchronous task with provided context that accepts messages to the task.
191/// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
192/// (in other words, all handles are dropped), the task would be aborted.
193///
194/// # Note
195/// If state must be borrowed across awaits,
196/// use [`spawn_coroutine_with_receiver_and_context`].
197pub fn spawn_coroutine_with_context<T, C, F, Fut>(context: C, f: F) -> CommunicationTask<T>
198where
199    F: FnMut(&mut C, T) -> Fut + Send + 'static,
200    Fut: Future<Output = ()> + Send + 'static,
201    C: Send + 'static,
202    T: Send + 'static,
203{
204    executor().spawn_coroutine_with_context(context, f)
205}
206
207/// Spawns a new asynchronous task with provided context that accepts messages to the task with a set buffer.
208/// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
209/// (in other words, all handles are dropped), the task would be aborted.
210pub fn spawn_coroutine_with_buffer_and_context<T, C, F, Fut>(
211    context: C,
212    buffer: usize,
213    f: F,
214) -> CommunicationTask<T>
215where
216    F: FnMut(&mut C, T) -> Fut + Send + 'static,
217    Fut: Future<Output = ()> + Send + 'static,
218    C: Send + 'static,
219    T: Send + 'static,
220{
221    executor().spawn_coroutine_with_buffer_and_context(context, buffer, f)
222}
223
224/// Spawns a new asynchronous task with provided context that accepts unbounded messages to the task.
225/// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
226/// (in other words, all handles are dropped), the task would be aborted.
227pub fn spawn_unbounded_coroutine_with_context<T, C, F, Fut>(
228    context: C,
229    f: F,
230) -> UnboundedCommunicationTask<T>
231where
232    F: FnMut(&mut C, T) -> Fut + Send + 'static,
233    Fut: Future<Output = ()> + Send + 'static,
234    C: Send + 'static,
235    T: Send + 'static,
236{
237    executor().spawn_unbounded_coroutine_with_context(context, f)
238}
239
240/// Spawns a new asynchronous task that accepts messages to the task using [`channels`](futures::channel::mpsc).
241/// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
242/// (in other words, all handles are dropped), the task would be aborted.
243pub fn spawn_coroutine_with_receiver<T, F, Fut>(f: F) -> CommunicationTask<T>
244where
245    F: FnMut(Receiver<T>) -> Fut,
246    Fut: Future<Output = ()> + Send + 'static,
247{
248    executor().spawn_coroutine_with_receiver(f)
249}
250
251/// Spawns a new asynchronous task with a set channel buffer that accepts messages to the task using [`channels`](futures::channel::mpsc).
252/// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
253/// (in other words, all handles are dropped), the task would be aborted.
254pub fn spawn_coroutine_with_receiver_and_buffer<T, F, Fut>(
255    buffer: usize,
256    f: F,
257) -> CommunicationTask<T>
258where
259    F: FnMut(Receiver<T>) -> Fut,
260    Fut: Future<Output = ()> + Send + 'static,
261{
262    executor().spawn_coroutine_with_receiver_and_buffer(buffer, f)
263}
264
265/// Spawns a new asynchronous task with provided context that accepts messages to the task using [`channels`](futures::channel::mpsc).
266/// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
267/// (in other words, all handles are dropped), the task would be aborted.
268pub fn spawn_coroutine_with_receiver_and_context<T, F, C, Fut>(
269    context: C,
270    f: F,
271) -> CommunicationTask<T>
272where
273    F: FnMut(C, Receiver<T>) -> Fut,
274    Fut: Future<Output = ()> + Send + 'static,
275{
276    executor().spawn_coroutine_with_receiver_and_context(context, f)
277}
278
279/// Spawns a new asynchronous task with a set channel buffer and provided context that accepts messages to the task using [`channels`](futures::channel::mpsc).
280/// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
281/// (in other words, all handles are dropped), the task would be aborted.
282pub fn spawn_coroutine_with_receiver_buffer_and_context<T, F, C, Fut>(
283    context: C,
284    buffer: usize,
285    f: F,
286) -> CommunicationTask<T>
287where
288    F: FnMut(C, Receiver<T>) -> Fut,
289    Fut: Future<Output = ()> + Send + 'static,
290{
291    executor().spawn_coroutine_with_receiver_buffer_and_context(context, buffer, f)
292}
293
294/// Spawns a new asynchronous task that accepts messages to the task using [`channels`](futures::channel::mpsc).
295/// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
296/// (in other words, all handles are dropped), the task would be aborted.
297pub fn spawn_unbounded_coroutine_with_receiver<T, F, Fut>(f: F) -> UnboundedCommunicationTask<T>
298where
299    F: FnMut(UnboundedReceiver<T>) -> Fut,
300    Fut: Future<Output = ()> + Send + 'static,
301{
302    executor().spawn_unbounded_coroutine_with_receiver(f)
303}
304
305/// Spawns a new asynchronous task with provided context that accepts messages to the task using [`channels`](futures::channel::mpsc).
306/// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
307/// (in other words, all handles are dropped), the task would be aborted.
308pub fn spawn_unbounded_coroutine_with_receiver_and_context<T, F, C, Fut>(
309    context: C,
310    f: F,
311) -> UnboundedCommunicationTask<T>
312where
313    F: FnMut(C, UnboundedReceiver<T>) -> Fut,
314    Fut: Future<Output = ()> + Send + 'static,
315{
316    executor().spawn_unbounded_coroutine_with_receiver_and_context(context, f)
317}
318
319/// Create a structured-concurrency scope in which tasks may be spawned
320/// that borrow from the enclosing stack frame.
321///
322/// This is the async analogue of [`std::thread::scope`].
323pub fn scope<'env, F, T>(f: F) -> impl Future<Output = T>
324where
325    F: for<'scope> AsyncFnOnce(&'scope Scope<'scope, 'env>) -> T,
326{
327    crate::scoped::scope(f)
328}
329
330/// Run an async closure with a scoped [`Executor`] wrapper that
331/// forwards spawns to this executor, waits for all spawned tasks
332/// to finish when the closure returns, and aborts any outstanding
333/// tasks if the scope future itself is cancelled.
334pub fn executor_scope<F, T>(f: F) -> impl Future<Output = T>
335where
336    F: for<'scope> AsyncFnOnce(&ScopeExecutor<'scope, BuiltinExecutor>) -> T,
337{
338    async move {
339        let executor = executor();
340        executor.executor_scope(f).await
341    }
342}
343
344/// Blocks the current thread until the provided future has completed.
345///
346/// Note that calling this function within an executor context may cause a deadlock.
347pub fn block_on<F: Future>(f: F) -> F::Output {
348    executor().block_on(f)
349}
350
351#[cfg(not(all(feature = "tokio", not(target_arch = "wasm32"))))]
352#[derive(Default)]
353struct Yield {
354    yielded: bool,
355}
356
357#[cfg(not(all(feature = "tokio", not(target_arch = "wasm32"))))]
358impl core::future::Future for Yield {
359    type Output = ();
360
361    fn poll(
362        mut self: core::pin::Pin<&mut Self>,
363        cx: &mut core::task::Context<'_>,
364    ) -> core::task::Poll<()> {
365        if self.yielded {
366            return core::task::Poll::Ready(());
367        }
368        self.yielded = true;
369        cx.waker().wake_by_ref();
370        core::task::Poll::Pending
371    }
372}
373
374/// Yields execution back to the runtime
375pub fn yield_now() -> impl Future<Output = ()> {
376    #[cfg(all(feature = "tokio", not(target_arch = "wasm32")))]
377    {
378        tokio::task::yield_now()
379    }
380    #[cfg(not(all(feature = "tokio", not(target_arch = "wasm32"))))]
381    {
382        Yield::default()
383    }
384}
385
386/// Yields execution back to the runtime `amount` times.
387pub async fn yield_for(amount: usize) {
388    for _ in 0..amount {
389        yield_now().await;
390    }
391}