Skip to main content

async_rt/
scoped.rs

1//! Structured concurrency scopes for async tasks.
2//!
3//! A [`Scope`] lets you spawn futures that borrow from the enclosing stack
4//! frame, analogous to [`std::thread::scope`]. Unlike thread scope, the
5//! tasks are driven cooperatively by the scope's own future, they make
6//! progress whenever the scope is polled so no runtime-level `spawn` is
7//! involved and no `unsafe` is needed to extend lifetimes.
8//!
9//! The [`scope`] function is the entry point. Inside the async closure,
10//! call [`Scope::spawn`] to enqueue a task; the returned [`ScopedJoinHandle`]
11//! resolves with the task's output. Any tasks still running when the user
12//! closure's future completes are drained before `scope` returns, so every
13//! borrow is released before the stack frame goes away.
14
15use crate::{
16    AbortableJoinHandle, CommunicationTask, CompletionGuard, Executor, ExecutorBlocking,
17    ExecutorTimeout, InnerJoinHandle, JoinHandle, TimeoutError, UnboundedCommunicationTask,
18    abortable_result, error::JoinError,
19};
20use core::future::{Future, poll_fn};
21use core::marker::PhantomData;
22use core::pin::Pin;
23use core::task::{Context, Poll};
24use futures::channel::mpsc::{Receiver, UnboundedReceiver};
25use futures::channel::oneshot;
26use futures::future::{AbortHandle, BoxFuture};
27use futures::stream::FuturesUnordered;
28use futures::task::AtomicWaker;
29use futures::{FutureExt, StreamExt, TryFutureExt};
30use futures_timeout::Timeout;
31use parking_lot::Mutex;
32use pollable_map::optional::Optional;
33use std::panic::AssertUnwindSafe;
34use std::sync::atomic::AtomicBool;
35use std::sync::{Arc, Weak};
36
37struct ScopeState<'scope> {
38    inbox: Mutex<Vec<BoxFuture<'scope, ()>>>,
39    waker: AtomicWaker,
40}
41
42/// A scope within which tasks can be spawned that borrow from the enclosing
43/// stack frame.
44pub struct Scope<'scope, 'env: 'scope> {
45    state: Weak<ScopeState<'scope>>,
46    _scope: PhantomData<&'scope mut &'scope ()>,
47    _env: PhantomData<&'env mut &'env ()>,
48}
49
50impl<'scope, 'env> Scope<'scope, 'env> {
51    fn new() -> Self {
52        Self {
53            state: Weak::new(),
54            _scope: PhantomData,
55            _env: PhantomData,
56        }
57    }
58
59    fn push(&self, task: BoxFuture<'scope, ()>) {
60        if let Some(state) = self.state.upgrade() {
61            state.inbox.lock().push(task);
62            state.waker.wake();
63        }
64    }
65
66    /// Spawn a task into this scope.
67    ///
68    /// The future may borrow any data that outlives its lifetime `'env`. The task will
69    /// be polled cooperatively alongside the scope's user closure and any
70    /// other spawned tasks.
71    pub fn spawn<Fut>(&'scope self, fut: Fut) -> ScopedJoinHandle<Fut::Output>
72    where
73        Fut: Future + Send + 'scope,
74        Fut::Output: Send + 'scope,
75    {
76        let (tx, rx) = oneshot::channel();
77        let wrapped: BoxFuture<'scope, ()> = async move {
78            let output = AssertUnwindSafe(fut)
79                .catch_unwind()
80                .await
81                .map_err(|_| JoinError::Panicked);
82            // If the receiver was dropped, the caller doesn't care about
83            // the output so we will discard it.
84            let _ = tx.send(output);
85        }
86        .boxed();
87
88        self.push(wrapped);
89
90        ScopedJoinHandle { rx }
91    }
92
93    /// Spawn a task into this scope and return an [`AbortableJoinHandle`].
94    ///
95    /// This mirrors [`Executor::spawn_abortable`] for cooperatively
96    /// scheduled tasks. The returned handle aborts the task when all
97    /// references to it have been dropped.
98    pub fn spawn_abortable<Fut>(&'scope self, fut: Fut) -> AbortableJoinHandle<Fut::Output>
99    where
100        Fut: Future + Send + 'scope,
101        Fut::Output: Send + 'scope,
102    {
103        let (abort_handle, abort_reg) = AbortHandle::new_pair();
104        let abortable = abortable_result(fut, abort_reg);
105        let (tx, rx) = oneshot::channel();
106        let finished = Arc::new(AtomicBool::new(false));
107        let completion = CompletionGuard::new(finished.clone());
108
109        let wrapped: BoxFuture<'scope, ()> = async move {
110            let _completion = completion;
111            let val = abortable.await;
112            let _ = tx.send(val);
113        }
114        .boxed();
115        self.push(wrapped);
116
117        let join = JoinHandle {
118            inner: InnerJoinHandle::CustomHandle {
119                inner: Optional::new(rx),
120                handle: abort_handle,
121                finished,
122            },
123        };
124        AbortableJoinHandle::from(join)
125    }
126
127    /// Spawn a task into this scope without keeping a handle to it.
128    ///
129    /// Equivalent to [`Executor::dispatch`] for scoped tasks.
130    pub fn dispatch<Fut>(&'scope self, fut: Fut)
131    where
132        Fut: Future + Send + 'scope,
133        Fut::Output: Send + 'scope,
134    {
135        drop(self.spawn(fut));
136    }
137
138    /// Spawn a message-driven coroutine into this scope.
139    ///
140    /// Equivalent to [`Executor::spawn_coroutine`] for scoped tasks.
141    pub fn spawn_coroutine<T, F, Fut>(&'scope self, f: F) -> CommunicationTask<T>
142    where
143        F: FnMut(T) -> Fut + Send + 'scope,
144        Fut: Future<Output = ()> + Send + 'scope,
145        T: Send + 'scope,
146    {
147        self.spawn_coroutine_with_buffer(1, f)
148    }
149
150    /// Like [`Scope::spawn_coroutine`] but with a configurable channel
151    /// buffer.
152    pub fn spawn_coroutine_with_buffer<T, F, Fut>(
153        &'scope self,
154        buffer: usize,
155        mut f: F,
156    ) -> CommunicationTask<T>
157    where
158        F: FnMut(T) -> Fut + Send + 'scope,
159        Fut: Future<Output = ()> + Send + 'scope,
160        T: Send + 'scope,
161    {
162        let (tx, mut rx) = futures::channel::mpsc::channel(buffer);
163        let task_handle = self.spawn_abortable(async move {
164            while let Some(message) = rx.next().await {
165                f(message).await;
166            }
167        });
168        CommunicationTask::new(task_handle, tx)
169    }
170
171    /// Spawn an unbounded message-driven coroutine into this scope.
172    ///
173    /// Equivalent to [`Executor::spawn_unbounded_coroutine`] for scoped
174    /// tasks.
175    pub fn spawn_unbounded_coroutine<T, F, Fut>(
176        &'scope self,
177        mut f: F,
178    ) -> UnboundedCommunicationTask<T>
179    where
180        F: FnMut(T) -> Fut + Send + 'scope,
181        Fut: Future<Output = ()> + Send + 'scope,
182        T: Send + 'scope,
183    {
184        let (tx, mut rx) = futures::channel::mpsc::unbounded();
185        let task_handle = self.spawn_abortable(async move {
186            while let Some(message) = rx.next().await {
187                f(message).await;
188            }
189        });
190        UnboundedCommunicationTask::new(task_handle, tx)
191    }
192
193    /// Spawn a message-driven coroutine with caller-provided context.
194    ///
195    /// If the context must be borrowed across awaits, use
196    /// [`Scope::spawn_coroutine_with_receiver_and_context`].
197    pub fn spawn_coroutine_with_context<T, C, F, Fut>(
198        &'scope self,
199        context: C,
200        f: F,
201    ) -> CommunicationTask<T>
202    where
203        F: FnMut(&mut C, T) -> Fut + Send + 'scope,
204        Fut: Future<Output = ()> + Send + 'scope,
205        C: Send + 'scope,
206        T: Send + 'scope,
207    {
208        self.spawn_coroutine_with_buffer_and_context(context, 1, f)
209    }
210
211    /// Like [`Scope::spawn_coroutine_with_context`] but with a configurable
212    /// channel buffer.
213    pub fn spawn_coroutine_with_buffer_and_context<T, C, F, Fut>(
214        &'scope self,
215        context: C,
216        buffer: usize,
217        mut f: F,
218    ) -> CommunicationTask<T>
219    where
220        F: FnMut(&mut C, T) -> Fut + Send + 'scope,
221        Fut: Future<Output = ()> + Send + 'scope,
222        C: Send + 'scope,
223        T: Send + 'scope,
224    {
225        let (tx, mut rx) = futures::channel::mpsc::channel(buffer);
226        let task_handle = self.spawn_abortable(async move {
227            let mut context = context;
228            while let Some(message) = rx.next().await {
229                f(&mut context, message).await;
230            }
231        });
232        CommunicationTask::new(task_handle, tx)
233    }
234
235    /// Spawn an unbounded message-driven coroutine with caller-provided
236    /// context.
237    pub fn spawn_unbounded_coroutine_with_context<T, C, F, Fut>(
238        &'scope self,
239        context: C,
240        mut f: F,
241    ) -> UnboundedCommunicationTask<T>
242    where
243        F: FnMut(&mut C, T) -> Fut + Send + 'scope,
244        Fut: Future<Output = ()> + Send + 'scope,
245        C: Send + 'scope,
246        T: Send + 'scope,
247    {
248        let (tx, mut rx) = futures::channel::mpsc::unbounded();
249        let task_handle = self.spawn_abortable(async move {
250            let mut context = context;
251            while let Some(message) = rx.next().await {
252                f(&mut context, message).await;
253            }
254        });
255        UnboundedCommunicationTask::new(task_handle, tx)
256    }
257
258    /// Spawn a coroutine that receives the bounded channel directly.
259    pub fn spawn_coroutine_with_receiver<T, F, Fut>(&'scope self, f: F) -> CommunicationTask<T>
260    where
261        F: FnMut(Receiver<T>) -> Fut,
262        Fut: Future<Output = ()> + Send + 'scope,
263    {
264        self.spawn_coroutine_with_receiver_and_buffer(1, f)
265    }
266
267    /// Like [`Scope::spawn_coroutine_with_receiver`] but with a configurable
268    /// channel buffer.
269    pub fn spawn_coroutine_with_receiver_and_buffer<T, F, Fut>(
270        &'scope self,
271        buffer: usize,
272        mut f: F,
273    ) -> CommunicationTask<T>
274    where
275        F: FnMut(Receiver<T>) -> Fut,
276        Fut: Future<Output = ()> + Send + 'scope,
277    {
278        let (tx, rx) = futures::channel::mpsc::channel(buffer);
279        let task_handle = self.spawn_abortable(f(rx));
280        CommunicationTask::new(task_handle, tx)
281    }
282
283    /// Spawn a coroutine that receives caller-provided context and the
284    /// bounded channel directly.
285    pub fn spawn_coroutine_with_receiver_and_context<T, F, C, Fut>(
286        &'scope self,
287        context: C,
288        f: F,
289    ) -> CommunicationTask<T>
290    where
291        F: FnMut(C, Receiver<T>) -> Fut,
292        Fut: Future<Output = ()> + Send + 'scope,
293    {
294        self.spawn_coroutine_with_receiver_buffer_and_context(context, 1, f)
295    }
296
297    /// Like [`Scope::spawn_coroutine_with_receiver_and_context`] but with a
298    /// configurable channel buffer.
299    pub fn spawn_coroutine_with_receiver_buffer_and_context<T, F, C, Fut>(
300        &'scope self,
301        context: C,
302        buffer: usize,
303        mut f: F,
304    ) -> CommunicationTask<T>
305    where
306        F: FnMut(C, Receiver<T>) -> Fut,
307        Fut: Future<Output = ()> + Send + 'scope,
308    {
309        let (tx, rx) = futures::channel::mpsc::channel(buffer);
310        let task_handle = self.spawn_abortable(f(context, rx));
311        CommunicationTask::new(task_handle, tx)
312    }
313
314    /// Spawn a coroutine that receives the unbounded channel directly.
315    pub fn spawn_unbounded_coroutine_with_receiver<T, F, Fut>(
316        &'scope self,
317        mut f: F,
318    ) -> UnboundedCommunicationTask<T>
319    where
320        F: FnMut(UnboundedReceiver<T>) -> Fut,
321        Fut: Future<Output = ()> + Send + 'scope,
322    {
323        let (tx, rx) = futures::channel::mpsc::unbounded();
324        let task_handle = self.spawn_abortable(f(rx));
325        UnboundedCommunicationTask::new(task_handle, tx)
326    }
327
328    /// Spawn a coroutine that receives caller-provided context and the
329    /// unbounded channel directly.
330    pub fn spawn_unbounded_coroutine_with_receiver_and_context<T, F, C, Fut>(
331        &'scope self,
332        context: C,
333        mut f: F,
334    ) -> UnboundedCommunicationTask<T>
335    where
336        F: FnMut(C, UnboundedReceiver<T>) -> Fut,
337        Fut: Future<Output = ()> + Send + 'scope,
338    {
339        let (tx, rx) = futures::channel::mpsc::unbounded();
340        let task_handle = self.spawn_abortable(f(context, rx));
341        UnboundedCommunicationTask::new(task_handle, tx)
342    }
343
344    /// Spawn a scoped task that must complete within `duration`.
345    ///
346    /// If the future does not finish before `duration` elapses, it is dropped and the task
347    /// completes with [`TimeoutError`]. This is the scoped analogue of
348    /// [`ExecutorTimeout::spawn_timeout`].
349    pub fn spawn_timeout<F>(
350        &'scope self,
351        duration: std::time::Duration,
352        f: F,
353    ) -> ScopedJoinHandle<Result<F::Output, TimeoutError>>
354    where
355        F: Future + Send + 'scope,
356        F::Output: Send + 'scope,
357    {
358        self.spawn(Timeout::from_future(f, duration).map_err(|_| TimeoutError))
359    }
360
361    /// Spawns a task after waiting for a duration before the task is polled.
362    pub fn spawn_delay<F>(
363        &'scope self,
364        duration: std::time::Duration,
365        f: F,
366    ) -> ScopedJoinHandle<F::Output>
367    where
368        F: Future + Send + 'scope,
369        F::Output: Send + 'scope,
370    {
371        self.spawn(async move {
372            let _ = Timeout::from_future(futures::future::pending::<()>(), duration).await;
373            f.await
374        })
375    }
376
377    /// Spawn a scoped task that must complete within `duration`, returning an
378    /// [`AbortableJoinHandle`] that cancels the task once all references to it are dropped.
379    ///
380    /// If the future does not finish before `duration` elapses, it is dropped and the task
381    /// completes with [`TimeoutError`].
382    pub fn spawn_abortable_timeout<F>(
383        &'scope self,
384        duration: std::time::Duration,
385        f: F,
386    ) -> AbortableJoinHandle<Result<F::Output, TimeoutError>>
387    where
388        F: Future + Send + 'scope,
389        F::Output: Send + 'scope,
390    {
391        self.spawn_abortable(Timeout::from_future(f, duration).map_err(|_| TimeoutError))
392    }
393
394    /// Spawns a task after waiting for a duration before the task is polled.
395    pub fn spawn_abortable_delay<F>(
396        &'scope self,
397        duration: std::time::Duration,
398        f: F,
399    ) -> AbortableJoinHandle<F::Output>
400    where
401        F: Future + Send + 'scope,
402        F::Output: Send + 'scope,
403    {
404        self.spawn_abortable(async move {
405            let _ = Timeout::from_future(futures::future::pending::<()>(), duration).await;
406            f.await
407        })
408    }
409}
410
411/// Drive a scope once: absorb any inbox entries, then poll the active
412/// set until no more immediate progress can be made.
413fn drive_scope<'scope>(
414    active: &mut FuturesUnordered<BoxFuture<'scope, ()>>,
415    state: &ScopeState<'scope>,
416    cx: &mut Context<'_>,
417) -> (bool, bool) {
418    let mut made_progress = false;
419    loop {
420        state.waker.register(cx.waker());
421
422        // Take the queued tasks while holding the lock only long enough to
423        // swap the inbox, never while polling user code.
424        let incoming = std::mem::take(&mut *state.inbox.lock());
425        active.extend(incoming);
426        match active.poll_next_unpin(cx) {
427            Poll::Ready(Some(())) => made_progress = true,
428            Poll::Ready(None) => {
429                state.waker.register(cx.waker());
430                if state.inbox.lock().is_empty() {
431                    return (made_progress, true);
432                }
433            }
434            Poll::Pending => {
435                state.waker.register(cx.waker());
436                if state.inbox.lock().is_empty() {
437                    return (made_progress, false);
438                }
439            }
440        }
441    }
442}
443
444/// A handle to a task spawned on a [`Scope`].
445///
446/// Awaiting the handle yields the task's output. A panicking task yields
447/// [`JoinError::Panicked`]. If the scope is dropped before the task finishes
448/// (for example, because the scope future was cancelled), awaiting yields
449/// [`JoinError::Cancelled`].
450///
451/// The handle does not borrow the spawned future itself. It can therefore
452/// outlive the scope when its output type is also `'static`, which borrowed output
453/// types retain their normal lifetime restrictions.
454pub struct ScopedJoinHandle<T> {
455    rx: oneshot::Receiver<Result<T, JoinError>>,
456}
457
458impl<T> Future for ScopedJoinHandle<T> {
459    type Output = Result<T, JoinError>;
460
461    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
462        match Pin::new(&mut self.rx).poll(cx) {
463            Poll::Ready(Ok(result)) => Poll::Ready(result),
464            Poll::Ready(Err(_)) => Poll::Ready(Err(JoinError::Cancelled)),
465            Poll::Pending => Poll::Pending,
466        }
467    }
468}
469
470/// Create a scope for spawning tasks that borrow from the enclosing stack
471/// frame.
472///
473/// The provided async closure receives a reference to the [`Scope`] and
474/// returns a future. That future is driven alongside any tasks spawned on
475/// the scope; when it completes, any still-running spawned tasks are
476/// drained before `scope` returns its value.
477///
478/// # Example
479///
480/// ```no_run
481/// # async fn run() {
482/// use async_rt::scoped::scope;
483///
484/// let data = vec![1, 2, 3, 4];
485/// let data = &data;
486/// let sum = scope(async |s| {
487///     let a = s.spawn(async { data[0] + data[1] });
488///     let b = s.spawn(async { data[2] + data[3] });
489///     a.await.unwrap() + b.await.unwrap()
490/// })
491/// .await;
492/// assert_eq!(sum, 10);
493/// # }
494/// ```
495pub async fn scope<'env, F, T>(f: F) -> T
496where
497    F: for<'scope> AsyncFnOnce(&'scope Scope<'scope, 'env>) -> T,
498{
499    // Declaration order is part of the safety invariant: `active` is dropped
500    // first, then `state` (and its queued tasks), and finally `scope`.
501    let mut scope = Scope::new();
502    let state = Arc::new(ScopeState {
503        inbox: Mutex::new(Vec::new()),
504        waker: AtomicWaker::new(),
505    });
506    scope.state = Arc::downgrade(&state);
507    // The active task set lives on the driver, not inside `Scope`. Only
508    // `drive_scope` touches it, so no lock guards it, and the inbox mutex
509    // is enough for the spawn side.
510    let mut active = FuturesUnordered::new();
511
512    let result = {
513        let user_fut = f(&scope);
514        let mut user_fut = std::pin::pin!(user_fut);
515
516        poll_fn(|cx| {
517            loop {
518                if let Poll::Ready(r) = user_fut.as_mut().poll(cx) {
519                    return Poll::Ready(r);
520                }
521                let (made_progress, _empty) = drive_scope(&mut active, &state, cx);
522                if !made_progress {
523                    return Poll::Pending;
524                }
525            }
526        })
527        .await
528    };
529
530    // Drain any remaining spawned tasks before the scope goes away, so
531    // every borrow of the data is released before this frame unwinds.
532    poll_fn(|cx| {
533        let (_made_progress, empty) = drive_scope(&mut active, &state, cx);
534        if empty {
535            Poll::Ready(())
536        } else {
537            Poll::Pending
538        }
539    })
540    .await;
541
542    result
543}
544
545/// An [`Executor`] wrapper that tracks spawned tasks so they can be
546/// canceled when a scope ends.
547///
548/// `ScopeExecutor` itself implements [`Executor`], so it can be passed to
549/// any code that takes `Executor` every spawn performed through
550/// that code will be tracked by the scope.
551///
552/// Because submitted futures must still be `Send + 'static` (enforced by
553/// [`Executor::spawn`]), this flavour of scope does *not* allow borrowing
554/// from the enclosing stack frame. Use [`scope`] for that.
555pub struct ScopeExecutor<'scope, E> {
556    inner: &'scope E,
557    task_handles: Mutex<Vec<AbortableJoinHandle<()>>>,
558    _scope: PhantomData<&'scope mut &'scope ()>,
559}
560
561impl<'scope, E> ScopeExecutor<'scope, E> {
562    fn new(inner: &'scope E) -> Self {
563        Self {
564            inner,
565            task_handles: Mutex::new(Vec::new()),
566            _scope: PhantomData,
567        }
568    }
569
570    /// Abort every task currently tracked by this scope.
571    fn abort_all(&self) {
572        for handle in self.task_handles.lock().iter() {
573            handle.abort();
574        }
575    }
576}
577
578impl<E> ScopeExecutor<'_, E>
579where
580    E: Executor,
581{
582    fn spawn_tracked<F, T>(&self, future: F) -> JoinHandle<T>
583    where
584        F: Future<Output = Result<T, JoinError>> + Send + 'static,
585        T: Send + 'static,
586    {
587        let (abort_handle, abort_registration) = AbortHandle::new_pair();
588        let (tx, rx) = oneshot::channel();
589        let finished = Arc::new(AtomicBool::new(false));
590        let completion = CompletionGuard::new(finished.clone());
591        let wrapped = async move {
592            let _completion = completion;
593            let result = abortable_result(future, abort_registration)
594                .await
595                .and_then(|result| result);
596            let _ = tx.send(result);
597        };
598
599        // Track an abort-on-drop handle so cancellation remains effective even
600        // after the handles are drained from `ScopeExecutor` for joining.
601        let task_handle = self.inner.spawn_abortable(wrapped);
602        self.task_handles.lock().push(task_handle);
603
604        JoinHandle {
605            inner: InnerJoinHandle::CustomHandle {
606                inner: Optional::new(rx),
607                handle: abort_handle,
608                finished,
609            },
610        }
611    }
612}
613
614impl<E> Drop for ScopeExecutor<'_, E> {
615    fn drop(&mut self) {
616        self.abort_all();
617    }
618}
619
620impl<'scope, E> Executor for ScopeExecutor<'scope, E>
621where
622    E: Executor,
623{
624    fn runtime_type(&self) -> Option<&'static str> {
625        self.inner.runtime_type()
626    }
627
628    fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
629    where
630        F: Future + Send + 'static,
631        F::Output: Send + 'static,
632    {
633        self.spawn_tracked(async move { Ok(future.await) })
634    }
635}
636
637impl<E> ExecutorBlocking for ScopeExecutor<'_, E>
638where
639    E: ExecutorBlocking,
640{
641    fn spawn_blocking<F, R>(&self, f: F) -> JoinHandle<R>
642    where
643        F: FnOnce() -> R + Send + 'static,
644        R: Send + 'static,
645    {
646        let blocking_handle = self.inner.spawn_blocking_abortable(f);
647        // Note that the monitor owns the blocking handle, so aborting the monitor drops its
648        // abort-on-drop handle, which prevents queued work from starting when
649        // the backend supports that. Work already running may continue.
650        self.spawn_tracked(blocking_handle)
651    }
652}
653
654impl<E> ExecutorTimeout for ScopeExecutor<'_, E> where E: Executor {}
655
656/// Run an async closure with a scoped [`Executor`] wrapper.
657///
658/// If the future containing `executor_scope` itself is dropped
659/// (canceled externally), it will abort every outstanding task.
660///
661/// Unlike [`scope`], futures spawned here must be `Send + 'static`
662/// they're going onto the real executor, so they can't borrow from the
663/// enclosing stack frame.
664///
665/// # Example
666///
667/// ```no_run
668/// # async fn run() {
669/// use async_rt::Executor;
670/// use async_rt::global::ConfiguredExecutor;
671///
672/// let executor: ConfiguredExecutor = ConfiguredExecutor::default();
673/// let total = executor
674///     .executor_scope(async |s| {
675///         let a = s.spawn(async { 1 + 2 });
676///         let b = s.spawn(async { 3 + 4 });
677///         a.await.unwrap() + b.await.unwrap()
678///     })
679///     .await;
680///     assert_eq!(total, 10);
681/// # }
682/// ```
683pub async fn executor_scope<'scope, E, F, T>(executor: &'scope E, f: F) -> T
684where
685    E: Executor,
686    F: AsyncFnOnce(&ScopeExecutor<'scope, E>) -> T,
687{
688    let scope_exec = ScopeExecutor::new(executor);
689    let result = f(&scope_exec).await;
690
691    // Wait for every spawned task to complete
692    let handles: Vec<_> = scope_exec.task_handles.lock().drain(..).collect();
693    for handle in handles {
694        let _ = handle.await;
695    }
696
697    result
698}
699
700#[cfg(test)]
701mod tests {
702    use super::*;
703    #[cfg(feature = "tokio")]
704    use futures_timer::Delay;
705    #[cfg(feature = "tokio")]
706    use std::time::Duration;
707
708    #[tokio::test]
709    async fn borrows_stack_data() {
710        let data = vec![1, 2, 3, 4];
711        let data = &data;
712        let sum = scope(async |s: &Scope<'_, '_>| {
713            let a = s.spawn(async move { data[0] + data[1] });
714            let b = s.spawn(async move { data[2] + data[3] });
715            a.await.unwrap() + b.await.unwrap()
716        })
717        .await;
718        assert_eq!(sum, 10);
719    }
720
721    #[tokio::test]
722    async fn drains_unawaited_tasks() {
723        use std::sync::atomic::{AtomicUsize, Ordering};
724        let counter = AtomicUsize::new(0);
725        let counter_ref = &counter;
726        scope(async |s: &Scope<'_, '_>| {
727            for _ in 0..8 {
728                s.spawn(async move {
729                    counter_ref.fetch_add(1, Ordering::SeqCst);
730                });
731            }
732        })
733        .await;
734        assert_eq!(counter.load(Ordering::SeqCst), 8);
735    }
736
737    #[tokio::test]
738    async fn returns_closure_value() {
739        let v: i32 = scope(async |_s: &Scope<'_, '_>| 42).await;
740        assert_eq!(v, 42);
741    }
742
743    #[tokio::test]
744    async fn join_handle_yields_output() {
745        let out = scope(async |s: &Scope<'_, '_>| {
746            let h = s.spawn(async { "hello" });
747            h.await.unwrap()
748        })
749        .await;
750        assert_eq!(out, "hello");
751    }
752
753    #[cfg(panic = "unwind")]
754    #[tokio::test]
755    async fn join_handle_reports_task_panic() {
756        let result = scope(async |s: &Scope<'_, '_>| {
757            s.spawn(async { panic!("expected scoped task panic") })
758                .await
759        })
760        .await;
761
762        assert!(matches!(result, Err(JoinError::Panicked)));
763    }
764
765    #[cfg(panic = "unwind")]
766    #[tokio::test]
767    async fn unawaited_task_panic_does_not_stop_other_tasks() {
768        let completed = AtomicBool::new(false);
769
770        scope(async |s: &Scope<'_, '_>| {
771            s.spawn(async { panic!("expected unawaited scoped task panic") });
772            s.spawn(async {
773                completed.store(true, std::sync::atomic::Ordering::SeqCst);
774            });
775        })
776        .await;
777
778        assert!(completed.load(std::sync::atomic::Ordering::SeqCst));
779    }
780
781    #[tokio::test]
782    async fn abortable_handle_reports_completion_without_being_polled() {
783        scope(async |s: &Scope<'_, '_>| {
784            let handle = s.spawn_abortable(async {});
785
786            // Yield the user future so the cooperative driver can complete
787            // the child without polling its join handle.
788            crate::task::yield_now().await;
789
790            assert!(handle.is_finished());
791        })
792        .await;
793    }
794
795    #[test]
796    fn pushing_task_wakes_scope_driver() {
797        use std::sync::atomic::{AtomicUsize, Ordering};
798        use std::task::{Wake, Waker};
799
800        struct WakeCounter(AtomicUsize);
801
802        impl Wake for WakeCounter {
803            fn wake(self: Arc<Self>) {
804                self.0.fetch_add(1, Ordering::SeqCst);
805            }
806
807            fn wake_by_ref(self: &Arc<Self>) {
808                self.0.fetch_add(1, Ordering::SeqCst);
809            }
810        }
811
812        let mut scope = Scope::new();
813        let state = Arc::new(ScopeState {
814            inbox: Mutex::new(Vec::new()),
815            waker: AtomicWaker::new(),
816        });
817        scope.state = Arc::downgrade(&state);
818
819        let wake_counter = Arc::new(WakeCounter(AtomicUsize::new(0)));
820        let waker = Waker::from(wake_counter.clone());
821        state.waker.register(&waker);
822
823        let scope_ref = &scope;
824        scope.push(
825            async move {
826                // Consume the driver's registered waker while tasks are
827                // being polled, forcing drive_scope to register it again.
828                scope_ref.push(async {}.boxed());
829            }
830            .boxed(),
831        );
832
833        assert_eq!(state.inbox.lock().len(), 1);
834        assert_eq!(wake_counter.0.load(Ordering::SeqCst), 1);
835
836        let mut active = FuturesUnordered::new();
837        let mut context = Context::from_waker(&waker);
838        let (_, empty) = drive_scope(&mut active, &state, &mut context);
839        assert!(empty);
840        assert_eq!(wake_counter.0.load(Ordering::SeqCst), 2);
841
842        // The nested push consumed the previous registration. This push
843        // only wakes the driver if drive_scope registered again afterward.
844        scope.push(async {}.boxed());
845        assert_eq!(wake_counter.0.load(Ordering::SeqCst), 3);
846
847        let (_, empty) = drive_scope(&mut active, &state, &mut context);
848        assert!(empty);
849
850        let state_ref = Arc::downgrade(&state);
851        scope.push(
852            poll_fn(move |_cx| {
853                // Model a push that occurs after registration but before
854                // the inbox is drained: its wake is consumed, then the
855                // newly active task returns Pending.
856                state_ref.upgrade().unwrap().waker.wake();
857                Poll::<()>::Pending
858            })
859            .boxed(),
860        );
861        assert_eq!(wake_counter.0.load(Ordering::SeqCst), 4);
862
863        let (_, empty) = drive_scope(&mut active, &state, &mut context);
864        assert!(!empty);
865        let wakes_after_pending = wake_counter.0.load(Ordering::SeqCst);
866        assert!(wakes_after_pending > 4);
867
868        // The Pending path must have registered once more before returning.
869        scope.push(async {}.boxed());
870        assert_eq!(
871            wake_counter.0.load(Ordering::SeqCst),
872            wakes_after_pending + 1
873        );
874    }
875
876    #[tokio::test]
877    async fn many_concurrent_tasks_complete() {
878        use std::sync::atomic::{AtomicUsize, Ordering};
879        let counter = AtomicUsize::new(0);
880        let counter_ref = &counter;
881        let total: usize = scope(async |s: &Scope<'_, '_>| {
882            let handles: Vec<_> = (0..32)
883                .map(|i| {
884                    s.spawn(async move {
885                        counter_ref.fetch_add(1, Ordering::SeqCst);
886                        i
887                    })
888                })
889                .collect();
890            let mut sum = 0usize;
891            for h in handles {
892                sum += h.await.unwrap();
893            }
894            sum
895        })
896        .await;
897        assert_eq!(total, (0..32).sum());
898        assert_eq!(counter.load(Ordering::SeqCst), 32);
899    }
900
901    #[tokio::test]
902    async fn child_task_can_spawn_nested_task() {
903        let result = scope(async |s: &Scope<'_, '_>| {
904            let outer = s.spawn(async move {
905                let inner = s.spawn(async { 41usize });
906                inner.await.unwrap() + 1
907            });
908            outer.await.unwrap()
909        })
910        .await;
911
912        assert_eq!(result, 42);
913    }
914
915    #[tokio::test]
916    async fn drains_unawaited_nested_task() {
917        use std::sync::atomic::{AtomicBool, Ordering};
918
919        let nested_ran = AtomicBool::new(false);
920        let nested_ran_ref = &nested_ran;
921
922        scope(async |s: &Scope<'_, '_>| {
923            s.dispatch(async move {
924                s.dispatch(async move {
925                    nested_ran_ref.store(true, Ordering::SeqCst);
926                });
927            });
928        })
929        .await;
930
931        assert!(nested_ran.load(Ordering::SeqCst));
932    }
933
934    #[cfg(feature = "tokio")]
935    #[tokio::test]
936    async fn executor_scope_runs_tasks() {
937        use crate::rt::tokio::TokioExecutor;
938        let executor = TokioExecutor;
939        let total = executor
940            .executor_scope(async |s| {
941                let a = s.spawn(async { 1 + 2 });
942                let b = s.spawn(async { 3 + 4 });
943                a.await.unwrap() + b.await.unwrap()
944            })
945            .await;
946        assert_eq!(total, 10);
947    }
948
949    #[cfg(feature = "tokio")]
950    #[tokio::test]
951    async fn executor_scope_supports_timeouts() {
952        use crate::rt::tokio::TokioExecutor;
953        use futures::future::pending;
954
955        let executor = TokioExecutor;
956        executor
957            .executor_scope(async |s| {
958                let timeout = s.spawn_timeout(Duration::from_millis(10), pending::<()>());
959                assert!(matches!(timeout.await, Ok(Err(TimeoutError))));
960
961                let abortable_timeout =
962                    s.spawn_abortable_timeout(Duration::from_millis(10), pending::<()>());
963                assert!(matches!(abortable_timeout.await, Ok(Err(TimeoutError))));
964            })
965            .await;
966    }
967
968    #[cfg(feature = "tokio")]
969    #[tokio::test]
970    async fn executor_scope_supports_blocking_tasks() {
971        use crate::rt::tokio::TokioExecutor;
972
973        let executor = TokioExecutor;
974        executor
975            .executor_scope(async |s| {
976                assert_eq!(s.spawn_blocking(|| 42).await.unwrap(), 42);
977
978                let panicked = s
979                    .spawn_blocking(|| -> () { panic!("deliberate blocking task panic") })
980                    .await;
981                assert!(matches!(panicked, Err(JoinError::Panicked)));
982            })
983            .await;
984    }
985
986    #[cfg(feature = "tokio")]
987    #[tokio::test]
988    async fn executor_scope_drains_unawaited_blocking_tasks() {
989        use crate::rt::tokio::TokioExecutor;
990        use std::sync::Arc;
991        use std::sync::atomic::{AtomicBool, Ordering};
992
993        let executor = TokioExecutor;
994        let completed = Arc::new(AtomicBool::new(false));
995        let task_completed = completed.clone();
996
997        executor
998            .executor_scope(async move |s| {
999                let _handle = s.spawn_blocking(move || {
1000                    std::thread::sleep(Duration::from_millis(25));
1001                    task_completed.store(true, Ordering::SeqCst);
1002                });
1003            })
1004            .await;
1005
1006        assert!(completed.load(Ordering::SeqCst));
1007    }
1008
1009    #[cfg(feature = "tokio")]
1010    #[tokio::test(flavor = "current_thread")]
1011    async fn executor_scope_blocking_abort_is_reported() {
1012        use crate::rt::tokio::TokioExecutor;
1013        use futures::future::{Either, select};
1014
1015        let executor = TokioExecutor;
1016        executor
1017            .executor_scope(async |s| {
1018                let (started_tx, started_rx) = oneshot::channel();
1019                let (release_tx, release_rx) = std::sync::mpsc::channel();
1020                let handle = s.spawn_blocking(move || {
1021                    let _ = started_tx.send(());
1022                    let _ = release_rx.recv();
1023                });
1024
1025                started_rx.await.unwrap();
1026                handle.abort();
1027
1028                let handle = Box::pin(handle);
1029                let result = match select(handle, Delay::new(Duration::from_secs(1))).await {
1030                    Either::Left((result, _)) => result,
1031                    Either::Right((_, handle)) => {
1032                        release_tx.send(()).unwrap();
1033                        let _ = handle.await;
1034                        panic!("aborting the blocking handle did not cancel its monitor");
1035                    }
1036                };
1037
1038                release_tx.send(()).unwrap();
1039                assert!(matches!(result, Err(JoinError::Aborted)));
1040            })
1041            .await;
1042    }
1043
1044    #[cfg(feature = "tokio")]
1045    #[tokio::test(flavor = "current_thread")]
1046    async fn executor_scope_handle_reports_completion_without_being_polled() {
1047        use crate::rt::tokio::TokioExecutor;
1048
1049        let executor = TokioExecutor;
1050        executor
1051            .executor_scope(async |s| {
1052                let (completed_tx, completed_rx) = oneshot::channel();
1053                let handle = s.spawn(async move {
1054                    let _ = completed_tx.send(());
1055                });
1056
1057                completed_rx.await.unwrap();
1058
1059                // On the current-thread runtime, the spawned wrapper finishes
1060                // before the task it woke can be polled again.
1061                assert!(handle.is_finished());
1062            })
1063            .await;
1064    }
1065
1066    #[tokio::test]
1067    async fn scope_spawn_coroutine_receives_messages() {
1068        use std::sync::atomic::{AtomicUsize, Ordering};
1069        let total = AtomicUsize::new(0);
1070        let total_ref = &total;
1071
1072        scope(async |s: &Scope<'_, '_>| {
1073            let mut task = s.spawn_coroutine(|value| async move {
1074                total_ref.fetch_add(value, Ordering::SeqCst);
1075            });
1076            for v in [1usize, 2, 3, 4] {
1077                task.send(v).await.unwrap();
1078            }
1079            drop(task); // closes channel → coroutine exits cleanly
1080        })
1081        .await;
1082
1083        assert_eq!(total.load(Ordering::SeqCst), 10);
1084    }
1085
1086    #[tokio::test]
1087    async fn scope_receiver_coroutine_receives_messages() {
1088        use std::sync::atomic::{AtomicUsize, Ordering};
1089        let total = AtomicUsize::new(0);
1090        let total_ref = &total;
1091
1092        scope(async |s: &Scope<'_, '_>| {
1093            let mut task = s.spawn_coroutine_with_receiver(|mut rx| async move {
1094                while let Some(value) = rx.next().await {
1095                    total_ref.fetch_add(value, Ordering::SeqCst);
1096                }
1097            });
1098            for value in [1usize, 2, 3, 4] {
1099                task.send(value).await.unwrap();
1100            }
1101            drop(task);
1102        })
1103        .await;
1104
1105        assert_eq!(total.load(Ordering::SeqCst), 10);
1106    }
1107
1108    #[tokio::test]
1109    async fn scope_coroutine_api_matches_executor() {
1110        use futures::future::ready;
1111
1112        scope(async |s: &Scope<'_, '_>| {
1113            let task = s.spawn_coroutine_with_buffer(2, |_value: usize| ready(()));
1114            drop(task);
1115
1116            let task = s.spawn_unbounded_coroutine(|_value: usize| ready(()));
1117            drop(task);
1118
1119            let task =
1120                s.spawn_coroutine_with_context(0usize, |context: &mut usize, value: usize| {
1121                    *context += value;
1122                    ready(())
1123                });
1124            drop(task);
1125
1126            let task = s.spawn_coroutine_with_buffer_and_context(
1127                0usize,
1128                2,
1129                |context: &mut usize, value: usize| {
1130                    *context += value;
1131                    ready(())
1132                },
1133            );
1134            drop(task);
1135
1136            let task = s.spawn_unbounded_coroutine_with_context(
1137                0usize,
1138                |context: &mut usize, value: usize| {
1139                    *context += value;
1140                    ready(())
1141                },
1142            );
1143            drop(task);
1144
1145            let task =
1146                s.spawn_coroutine_with_receiver_and_buffer(2, |_rx: Receiver<usize>| async {});
1147            drop(task);
1148
1149            let task = s.spawn_coroutine_with_receiver_and_context(
1150                0usize,
1151                |_context, _rx: Receiver<usize>| async {},
1152            );
1153            drop(task);
1154
1155            let task = s.spawn_coroutine_with_receiver_buffer_and_context(
1156                0usize,
1157                2,
1158                |_context, _rx: Receiver<usize>| async {},
1159            );
1160            drop(task);
1161
1162            let task =
1163                s.spawn_unbounded_coroutine_with_receiver(|_rx: UnboundedReceiver<usize>| async {});
1164            drop(task);
1165
1166            let task = s.spawn_unbounded_coroutine_with_receiver_and_context(
1167                0usize,
1168                |_context, _rx: UnboundedReceiver<usize>| async {},
1169            );
1170            drop(task);
1171        })
1172        .await;
1173    }
1174
1175    #[tokio::test]
1176    async fn scope_dispatch_runs_fire_and_forget() {
1177        use std::sync::atomic::{AtomicBool, Ordering};
1178        let flag = AtomicBool::new(false);
1179        let flag_ref = &flag;
1180
1181        scope(async |s: &Scope<'_, '_>| {
1182            s.dispatch(async move {
1183                flag_ref.store(true, Ordering::SeqCst);
1184            });
1185        })
1186        .await;
1187
1188        assert!(flag.load(Ordering::SeqCst));
1189    }
1190
1191    #[cfg(feature = "tokio")]
1192    #[tokio::test]
1193    async fn executor_scope_drains_unawaited_tasks() {
1194        use crate::rt::tokio::TokioExecutor;
1195        use std::sync::Arc;
1196        use std::sync::atomic::{AtomicBool, Ordering};
1197
1198        let executor = TokioExecutor;
1199        let flag = Arc::new(AtomicBool::new(false));
1200        let flag_clone = flag.clone();
1201
1202        executor
1203            .executor_scope(async move |s| {
1204                // Spawn a task but do NOT await its handle.
1205                // executor_scope should wait for it to complete before
1206                // returning.
1207                let _h = s.spawn(async move {
1208                    Delay::new(Duration::from_millis(50)).await;
1209                    flag_clone.store(true, Ordering::SeqCst);
1210                });
1211            })
1212            .await;
1213
1214        assert!(
1215            flag.load(Ordering::SeqCst),
1216            "unawaited task should have completed before executor_scope returned"
1217        );
1218    }
1219
1220    #[cfg(feature = "tokio")]
1221    #[tokio::test]
1222    async fn executor_scope_swallows_task_panic() {
1223        use crate::rt::tokio::TokioExecutor;
1224        use std::sync::Arc;
1225        use std::sync::atomic::{AtomicUsize, Ordering};
1226
1227        let executor = TokioExecutor;
1228        let sibling_done = Arc::new(AtomicUsize::new(0));
1229        let sibling_done_clone = sibling_done.clone();
1230
1231        let result = executor
1232            .executor_scope(async move |s| {
1233                // Task A: panics. Its JoinHandle is dropped without
1234                // being awaited. the panic goes to the runtime.
1235                let _panicker = s.spawn(async {
1236                    panic!("deliberate test panic");
1237                });
1238                // Task B: completes normally. The scope should still
1239                // drain it before returning.
1240                let _sibling = s.spawn(async move {
1241                    sibling_done_clone.fetch_add(1, Ordering::SeqCst);
1242                });
1243                42usize
1244            })
1245            .await;
1246
1247        assert_eq!(result, 42);
1248        assert_eq!(sibling_done.load(Ordering::SeqCst), 1);
1249    }
1250
1251    #[cfg(feature = "tokio")]
1252    #[tokio::test]
1253    async fn executor_scope_aborts_on_external_cancel() {
1254        use crate::rt::tokio::TokioExecutor;
1255        use futures::future::Either;
1256        use std::sync::Arc;
1257        use std::sync::atomic::{AtomicBool, Ordering};
1258
1259        let executor = TokioExecutor;
1260        let flag = Arc::new(AtomicBool::new(false));
1261        let flag_clone = flag.clone();
1262
1263        {
1264            let scope_fut = executor.executor_scope(async move |s| {
1265                let _h = s.spawn(async move {
1266                    Delay::new(Duration::from_millis(200)).await;
1267                    flag_clone.store(true, Ordering::SeqCst);
1268                });
1269                futures::future::pending::<()>().await;
1270            });
1271
1272            let scope_fut = std::pin::pin!(scope_fut);
1273            let timer = std::pin::pin!(Delay::new(Duration::from_millis(30)));
1274            let result = futures::future::select(scope_fut, timer).await;
1275            assert!(
1276                matches!(result, Either::Right(_)),
1277                "timer should have won the race"
1278            );
1279        }
1280
1281        // Give the (supposedly aborted) task plenty of time.
1282        Delay::new(Duration::from_millis(300)).await;
1283        assert!(
1284            !flag.load(Ordering::SeqCst),
1285            "task should have been aborted by scope drop"
1286        );
1287    }
1288
1289    #[cfg(feature = "tokio")]
1290    #[tokio::test]
1291    async fn executor_scope_aborts_when_cancelled_during_drain() {
1292        use crate::rt::tokio::TokioExecutor;
1293        use futures::future::{Either, pending, select};
1294
1295        let executor = TokioExecutor;
1296        let (started_tx, started_rx) = oneshot::channel();
1297        let (held_tx, held_rx) = oneshot::channel::<()>();
1298
1299        let scope_fut = Box::pin(executor.executor_scope(async move |s| {
1300            let _handle = s.spawn(async move {
1301                let _held_until_task_drop = held_tx;
1302                let _ = started_tx.send(());
1303                pending::<()>().await;
1304            });
1305        }));
1306
1307        let scope_fut = match select(scope_fut, started_rx).await {
1308            Either::Right((Ok(()), scope_fut)) => scope_fut,
1309            Either::Left(_) => panic!("scope unexpectedly completed"),
1310            Either::Right((Err(_), _)) => panic!("child task never started"),
1311        };
1312
1313        drop(scope_fut);
1314
1315        match select(held_rx, Delay::new(Duration::from_secs(1))).await {
1316            Either::Left((Err(_), _)) => {}
1317            Either::Left((Ok(_), _)) => unreachable!("child never sends a value"),
1318            Either::Right(_) => panic!("child remained detached after scope cancellation"),
1319        }
1320    }
1321}