Skip to main content

async_rt/
lib.rs

1pub mod arc;
2pub mod error;
3#[cfg(feature = "fs")]
4pub mod fs;
5pub mod global;
6#[cfg(all(
7    feature = "net",
8    not(target_arch = "wasm32"),
9    any(feature = "tokio", feature = "smol", feature = "compio")
10))]
11pub mod net;
12pub mod rt;
13pub mod task;
14pub mod tracker;
15
16#[cfg(feature = "either")]
17pub mod either;
18pub mod rc;
19pub mod scoped;
20
21use std::fmt::{Debug, Formatter};
22use std::panic::AssertUnwindSafe;
23use std::sync::atomic::{AtomicBool, Ordering};
24
25pub use crate::error::JoinError;
26pub use crate::error::TimeoutError;
27pub use crate::scoped::{Scope, ScopeExecutor, ScopedJoinHandle};
28#[cfg(all(
29    feature = "macros",
30    feature = "compio",
31    not(any(feature = "tokio", feature = "smol", target_arch = "wasm32"))
32))]
33#[doc(inline)]
34pub use async_rt_macros::{main_compio as main, test_compio as test};
35#[cfg(all(
36    feature = "macros",
37    not(any(
38        feature = "tokio",
39        feature = "smol",
40        feature = "compio",
41        feature = "threadpool",
42        feature = "lite"
43    )),
44    not(target_arch = "wasm32")
45))]
46#[doc(inline)]
47pub use async_rt_macros::{main_fail as main, test_fail as test};
48#[cfg(all(
49    feature = "macros",
50    feature = "lite",
51    not(any(
52        feature = "tokio",
53        feature = "smol",
54        feature = "compio",
55        feature = "threadpool",
56        target_arch = "wasm32"
57    ))
58))]
59#[doc(inline)]
60pub use async_rt_macros::{main_lite as main, test_lite as test};
61#[cfg(all(
62    feature = "macros",
63    feature = "smol",
64    not(any(feature = "tokio", target_arch = "wasm32"))
65))]
66#[doc(inline)]
67pub use async_rt_macros::{main_smol as main, test_smol as test};
68#[cfg(all(
69    feature = "macros",
70    feature = "threadpool",
71    not(any(
72        feature = "tokio",
73        feature = "smol",
74        feature = "compio",
75        target_arch = "wasm32"
76    ))
77))]
78#[doc(inline)]
79pub use async_rt_macros::{main_threadpool as main, test_threadpool as test};
80#[cfg(all(feature = "macros", feature = "tokio", not(target_arch = "wasm32")))]
81#[doc(inline)]
82pub use async_rt_macros::{main_tokio as main, test_tokio as test};
83#[cfg(all(feature = "macros", target_arch = "wasm32"))]
84#[doc(inline)]
85pub use async_rt_macros::{main_wasm_fail as main, test_wasm_fail as test};
86use futures::channel::mpsc::{Receiver, UnboundedReceiver};
87use futures::future::{AbortHandle, AbortRegistration, Abortable};
88use futures::task::AtomicWaker;
89use futures::{FutureExt, SinkExt, StreamExt, TryFutureExt};
90use futures_timeout::Timeout;
91#[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
92use parking_lot::Mutex;
93use pollable_map::optional::Optional;
94use std::future::Future;
95use std::pin::Pin;
96use std::sync::Arc;
97use std::task::{Context, Poll};
98
99#[cfg(feature = "macros")]
100extern crate self as async_rt;
101
102#[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
103type BoxCancelFuture<T> = Pin<Box<dyn Future<Output = Option<T>> + Send + 'static>>;
104
105#[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
106type StartCompioCancel<T> = fn(&Mutex<InnerCompioHandle<T>>, &AtomicBool);
107
108#[cfg_attr(feature = "tokio", allow(dead_code))]
109pub(crate) struct CompletionGuard {
110    finished: Arc<AtomicBool>,
111}
112
113impl CompletionGuard {
114    #[cfg_attr(feature = "tokio", allow(dead_code))]
115    pub(crate) fn new(finished: Arc<AtomicBool>) -> Self {
116        Self { finished }
117    }
118}
119
120impl Drop for CompletionGuard {
121    fn drop(&mut self) {
122        self.finished.store(true, Ordering::Release);
123    }
124}
125
126pub(crate) async fn abortable_result<F>(
127    future: F,
128    abort_registration: AbortRegistration,
129) -> Result<F::Output, JoinError>
130where
131    F: Future,
132{
133    let future = AssertUnwindSafe(future).catch_unwind();
134
135    match Abortable::new(future, abort_registration).await {
136        Ok(Ok(value)) => Ok(value),
137        Ok(Err(_)) => Err(JoinError::Panicked),
138        Err(_) => Err(JoinError::Aborted),
139    }
140}
141
142/// An owned permission to join on a task (await its termination).
143///
144/// This can be seen as an equivalent to [`std::thread::JoinHandle`] but for [`Future`] tasks rather than a thread.
145/// Note that the task associated with this `JoinHandle` will start running when
146/// [`Executor::spawn`] is called according to the selected runtime, even if the
147/// `JoinHandle` has not been awaited.
148///
149/// Dropping `JoinHandle` will not abort or cancel the task. In other words, the task will continue to run in the background
150/// and any return value will be lost.
151///
152/// This `struct` is created by the [`Executor::spawn`].
153pub struct JoinHandle<T> {
154    inner: InnerJoinHandle<T>,
155}
156
157impl<T> Debug for JoinHandle<T> {
158    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
159        f.debug_struct("JoinHandle").finish()
160    }
161}
162
163#[derive(Default)]
164enum InnerJoinHandle<T> {
165    #[cfg(all(feature = "tokio", not(target_arch = "wasm32")))]
166    TokioHandle {
167        handle: Optional<::tokio::task::JoinHandle<T>>,
168        abort_requested: AtomicBool,
169    },
170    #[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
171    CompioHandle {
172        handle: Mutex<InnerCompioHandle<T>>,
173        abort_requested: AtomicBool,
174        start_cancel: StartCompioCancel<T>,
175    },
176    #[cfg(all(feature = "smol", not(target_arch = "wasm32")))]
177    SmolHandle { handle: SmolTask<T> },
178    #[allow(dead_code)]
179    CustomHandle {
180        inner: Optional<futures::channel::oneshot::Receiver<Result<T, JoinError>>>,
181        handle: AbortHandle,
182        finished: Arc<AtomicBool>,
183    },
184    #[default]
185    Empty,
186}
187
188#[cfg(all(feature = "smol", not(target_arch = "wasm32")))]
189struct SmolTask<T> {
190    inner: Optional<::async_task::FallibleTask<Result<T, JoinError>>>,
191    abort_handle: AbortHandle,
192}
193
194#[cfg(all(feature = "smol", not(target_arch = "wasm32")))]
195impl<T> SmolTask<T> {
196    fn new(task: ::smol::Task<Result<T, JoinError>>, abort_handle: AbortHandle) -> Self {
197        Self {
198            inner: Optional::new(task.fallible()),
199            abort_handle,
200        }
201    }
202
203    fn abort(&self) {
204        self.abort_handle.abort();
205    }
206
207    fn is_finished(&self) -> bool {
208        self.inner
209            .as_ref()
210            .map(|task| task.is_finished())
211            .unwrap_or(true)
212    }
213}
214
215#[cfg(all(feature = "smol", not(target_arch = "wasm32")))]
216impl<T> Drop for SmolTask<T> {
217    fn drop(&mut self) {
218        if let Some(task) = self.inner.take() {
219            task.detach();
220        }
221    }
222}
223
224#[cfg(all(feature = "smol", not(target_arch = "wasm32")))]
225impl<T> Future for SmolTask<T> {
226    type Output = Result<T, JoinError>;
227
228    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
229        match futures::ready!(Pin::new(&mut self.inner).poll(cx)) {
230            Some(result) => Poll::Ready(result),
231            None => Poll::Ready(Err(JoinError::Cancelled)),
232        }
233    }
234}
235
236#[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
237enum InnerCompioHandle<T> {
238    Running(Option<::compio::runtime::JoinHandle<T>>),
239    Cancelling(CompioCancel<T>),
240    Ready(Option<Option<T>>),
241}
242
243#[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
244impl<T> Unpin for InnerCompioHandle<T> {}
245
246#[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
247struct CompioCancelWake {
248    outer: AtomicWaker,
249}
250
251#[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
252impl futures::task::ArcWake for CompioCancelWake {
253    fn wake_by_ref(arc_self: &Arc<Self>) {
254        arc_self.outer.wake();
255    }
256}
257
258#[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
259struct CompioCancel<T> {
260    future: BoxCancelFuture<T>,
261    wake: Arc<CompioCancelWake>,
262}
263
264#[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
265impl<T> CompioCancel<T> {
266    fn new(future: BoxCancelFuture<T>) -> Self {
267        Self {
268            future,
269            wake: Arc::new(CompioCancelWake {
270                outer: AtomicWaker::new(),
271            }),
272        }
273    }
274
275    fn poll_inner(&mut self) -> Poll<Option<T>> {
276        let waker = futures::task::waker(self.wake.clone());
277        let mut cx = Context::from_waker(&waker);
278        self.future.as_mut().poll(&mut cx)
279    }
280}
281
282#[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
283impl<T> Future for CompioCancel<T> {
284    type Output = Option<T>;
285
286    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
287        self.wake.outer.register(cx.waker());
288        self.poll_inner()
289    }
290}
291
292// Note: We implement a custom drop due to compio `JoinHandle` cancellation of the task upon drop
293//       therefore we need to detach the handle upon drop so it will continue running in the
294//       background to follow the same flow as the other executors implementations.
295#[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
296impl<T> Drop for InnerCompioHandle<T> {
297    fn drop(&mut self) {
298        if let InnerCompioHandle::Running(handle) = self {
299            let Some(handle) = handle.take() else {
300                return;
301            };
302            handle.detach();
303        }
304    }
305}
306
307#[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
308impl<T> Future for InnerCompioHandle<T> {
309    type Output = Result<Option<T>, JoinError>;
310    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
311        let this = &mut *self;
312
313        match this {
314            InnerCompioHandle::Running(handle) => {
315                let Some(mut handle) = handle.take() else {
316                    return Poll::Ready(Ok(None));
317                };
318                match Pin::new(&mut handle).poll(cx) {
319                    Poll::Ready(result) => Poll::Ready(result.map(Some).map_err(JoinError::from)),
320                    Poll::Pending => {
321                        *this = InnerCompioHandle::Running(Some(handle));
322                        Poll::Pending
323                    }
324                }
325            }
326            InnerCompioHandle::Cancelling(future) => match Pin::new(future).poll(cx) {
327                Poll::Ready(result) => {
328                    *this = InnerCompioHandle::Ready(None);
329                    Poll::Ready(Ok(result))
330                }
331                Poll::Pending => Poll::Pending,
332            },
333            InnerCompioHandle::Ready(result) => match result.take() {
334                Some(result) => Poll::Ready(Ok(result)),
335                None => Poll::Ready(Err(JoinError::Empty)),
336            },
337        }
338    }
339}
340
341#[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
342fn start_compio_cancel<T: Send + 'static>(
343    state: &Mutex<InnerCompioHandle<T>>,
344    requested: &AtomicBool,
345) {
346    let mut state = state.lock();
347
348    let InnerCompioHandle::Running(slot) = &mut *state else {
349        return;
350    };
351    let Some(handle) = slot.take() else {
352        return;
353    };
354
355    requested.store(true, Ordering::Release);
356
357    let future: BoxCancelFuture<T> = Box::pin(handle.cancel());
358    let mut future = CompioCancel::new(future);
359    *state = match future.poll_inner() {
360        Poll::Ready(result) => InnerCompioHandle::Ready(Some(result)),
361        Poll::Pending => InnerCompioHandle::Cancelling(future),
362    };
363}
364
365#[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
366impl<T> InnerCompioHandle<T> {
367    fn is_finished(&mut self) -> bool {
368        match self {
369            InnerCompioHandle::Running(handle) => {
370                handle.as_ref().map(|h| h.is_finished()).unwrap_or(true)
371            }
372            InnerCompioHandle::Cancelling(future) => match future.poll_inner() {
373                Poll::Ready(result) => {
374                    *self = InnerCompioHandle::Ready(Some(result));
375                    true
376                }
377                Poll::Pending => false,
378            },
379            InnerCompioHandle::Ready(_) => true,
380        }
381    }
382}
383
384impl<T> InnerJoinHandle<T> {
385    #[cfg(all(feature = "tokio", not(target_arch = "wasm32")))]
386    fn tokio(handle: ::tokio::task::JoinHandle<T>) -> Self {
387        Self::TokioHandle {
388            handle: Optional::new(handle),
389            abort_requested: AtomicBool::new(false),
390        }
391    }
392
393    #[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
394    fn compio(handle: ::compio::runtime::JoinHandle<T>) -> Self
395    where
396        T: Send + 'static,
397    {
398        Self::CompioHandle {
399            handle: Mutex::new(InnerCompioHandle::Running(Some(handle))),
400            abort_requested: AtomicBool::new(false),
401            start_cancel: start_compio_cancel::<T>,
402        }
403    }
404
405    #[cfg(all(feature = "smol", not(target_arch = "wasm32")))]
406    fn smol(handle: ::smol::Task<Result<T, JoinError>>, abort_handle: AbortHandle) -> Self {
407        Self::SmolHandle {
408            handle: SmolTask::new(handle, abort_handle),
409        }
410    }
411}
412
413impl<T> JoinHandle<T> {
414    /// Provide an empty [`JoinHandle`] with no associated task.
415    pub fn empty() -> Self {
416        JoinHandle {
417            inner: InnerJoinHandle::Empty,
418        }
419    }
420}
421
422impl<T> JoinHandle<T> {
423    /// Abort the task associated with the handle.
424    ///
425    /// If cancellation is observed after this method is called, awaiting the
426    /// handle returns [`JoinError::Aborted`]. Cancellation without an abort
427    /// request through this handle returns [`JoinError::Cancelled`].
428    pub fn abort(&self) {
429        match &self.inner {
430            #[cfg(all(feature = "tokio", not(target_arch = "wasm32")))]
431            InnerJoinHandle::TokioHandle {
432                handle,
433                abort_requested,
434            } => {
435                if let Some(handle) = handle.as_ref() {
436                    abort_requested.store(true, Ordering::Release);
437                    handle.abort();
438                }
439            }
440            #[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
441            InnerJoinHandle::CompioHandle {
442                handle,
443                abort_requested,
444                start_cancel,
445            } => {
446                start_cancel(handle, abort_requested);
447            }
448            #[cfg(all(feature = "smol", not(target_arch = "wasm32")))]
449            InnerJoinHandle::SmolHandle { handle } => handle.abort(),
450            InnerJoinHandle::CustomHandle { handle, .. } => handle.abort(),
451            InnerJoinHandle::Empty => {}
452        }
453    }
454
455    /// Check if the task associated with this `JoinHandle` has finished.
456    ///
457    /// Note that this method can return false even if [`JoinHandle::abort`] has been called on the
458    /// task due to the time it may take for the task to cancel.
459    pub fn is_finished(&self) -> bool {
460        match &self.inner {
461            #[cfg(all(feature = "tokio", not(target_arch = "wasm32")))]
462            InnerJoinHandle::TokioHandle { handle, .. } => {
463                handle.as_ref().map(|h| h.is_finished()).unwrap_or(true)
464            }
465            #[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
466            InnerJoinHandle::CompioHandle { handle, .. } => handle.lock().is_finished(),
467            #[cfg(all(feature = "smol", not(target_arch = "wasm32")))]
468            InnerJoinHandle::SmolHandle { handle } => handle.is_finished(),
469            InnerJoinHandle::CustomHandle {
470                inner, finished, ..
471            } => finished.load(Ordering::Acquire) || inner.is_none(),
472            InnerJoinHandle::Empty => true,
473        }
474    }
475
476    /// Replace the current handle with the provided [`JoinHandle`], while dropping the source.
477    ///
478    /// # Warning
479    ///
480    /// Note that if this is called with a non-empty handle, the existing task
481    /// will not be terminated when it is replaced and could run indefinitely.
482    /// Best to use when `JoinHandle` is `JoinHandle::empty` where empty is a used
483    /// placeholder.
484    pub fn replace(&mut self, mut handle: JoinHandle<T>) {
485        self.inner = std::mem::take(&mut handle.inner);
486    }
487
488    /// Replace the current handle with the provided [`JoinHandle`], making the source become
489    /// an equivalent to [`JoinHandle::empty`].
490    ///
491    /// # Warning
492    ///
493    /// Note that if this is called with a non-empty handle, the existing task
494    /// will not be terminated when it is replaced and could run indefinitely.
495    /// Best to use when `JoinHandle` is `JoinHandle::empty` where empty is a used
496    /// placeholder, and you want to update the source later with another task handle.
497    pub fn replace_in_place(&mut self, handle: &mut JoinHandle<T>) {
498        self.inner = std::mem::take(&mut handle.inner);
499    }
500}
501
502impl<T> Future for JoinHandle<T> {
503    type Output = Result<T, JoinError>;
504    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
505        let inner = &mut self.inner;
506        match inner {
507            #[cfg(all(feature = "tokio", not(target_arch = "wasm32")))]
508            InnerJoinHandle::TokioHandle {
509                handle,
510                abort_requested,
511            } => {
512                let fut = futures::ready!(Pin::new(handle).poll(cx));
513
514                match fut {
515                    Ok(val) => Poll::Ready(Ok(val)),
516                    Err(e) if e.is_cancelled() && abort_requested.load(Ordering::Acquire) => {
517                        Poll::Ready(Err(JoinError::Aborted))
518                    }
519                    Err(e) => Poll::Ready(Err(e.into())),
520                }
521            }
522            #[cfg(all(feature = "compio", not(target_arch = "wasm32")))]
523            InnerJoinHandle::CompioHandle {
524                handle,
525                abort_requested,
526                ..
527            } => {
528                let handle = &mut *handle.lock();
529                let fut = futures::ready!(Pin::new(handle).poll(cx));
530
531                match fut {
532                    Ok(Some(val)) => Poll::Ready(Ok(val)),
533                    Ok(None) if abort_requested.load(Ordering::Acquire) => {
534                        Poll::Ready(Err(JoinError::Aborted))
535                    }
536                    Ok(None) => Poll::Ready(Err(JoinError::Empty)),
537                    Err(_) if abort_requested.load(Ordering::Acquire) => {
538                        Poll::Ready(Err(JoinError::Aborted))
539                    }
540                    Err(e) => Poll::Ready(Err(e)),
541                }
542            }
543            #[cfg(all(feature = "smol", not(target_arch = "wasm32")))]
544            InnerJoinHandle::SmolHandle { handle } => Pin::new(handle).poll(cx),
545            InnerJoinHandle::CustomHandle { inner, .. } => {
546                let fut = futures::ready!(Pin::new(inner).poll(cx));
547                match fut {
548                    Ok(result) => Poll::Ready(result),
549                    Err(_) => Poll::Ready(Err(JoinError::Cancelled)),
550                }
551            }
552            InnerJoinHandle::Empty => Poll::Ready(Err(JoinError::Empty)),
553        }
554    }
555}
556
557/// The same as [`JoinHandle`] but designed to abort the task when all associated references
558/// to the returned `AbortableJoinHandle` have been dropped.
559#[derive(Clone)]
560pub struct AbortableJoinHandle<T> {
561    handle: Arc<InnerHandle<T>>,
562}
563
564impl<T> Debug for AbortableJoinHandle<T> {
565    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
566        f.debug_struct("AbortableJoinHandle").finish()
567    }
568}
569
570impl<T> From<JoinHandle<T>> for AbortableJoinHandle<T> {
571    fn from(handle: JoinHandle<T>) -> Self {
572        AbortableJoinHandle {
573            handle: Arc::new(InnerHandle {
574                inner: parking_lot::Mutex::new(handle),
575                waker: AtomicWaker::new(),
576            }),
577        }
578    }
579}
580
581impl<T> AbortableJoinHandle<T> {
582    /// Provide an empty [`AbortableJoinHandle`] with no associated task.
583    pub fn empty() -> Self {
584        Self {
585            handle: Arc::new(InnerHandle {
586                inner: parking_lot::Mutex::new(JoinHandle::empty()),
587                waker: AtomicWaker::new(),
588            }),
589        }
590    }
591}
592
593impl<T> AbortableJoinHandle<T> {
594    /// See [`JoinHandle::abort`]
595    pub fn abort(&self) {
596        self.handle.inner.lock().abort();
597        self.handle.wake();
598    }
599
600    /// See [`JoinHandle::is_finished`]
601    pub fn is_finished(&self) -> bool {
602        self.handle.inner.lock().is_finished()
603    }
604
605    /// Replace the current handle with an existing one.
606    ///
607    /// # Warning
608    ///
609    /// Note that if this is called with a non-empty handle, the existing task
610    /// will not be terminated when it is replaced.
611    pub fn replace(&self, other: AbortableJoinHandle<T>) {
612        if Arc::ptr_eq(&self.handle, &other.handle) {
613            return;
614        }
615
616        let replacement = {
617            let mut source = other.handle.inner.lock();
618            std::mem::replace(&mut *source, JoinHandle::empty())
619        };
620
621        other.handle.wake();
622
623        let previous = {
624            let mut destination = self.handle.inner.lock();
625            std::mem::replace(&mut *destination, replacement)
626        };
627
628        drop(previous);
629
630        self.handle.wake();
631    }
632}
633
634struct InnerHandle<T> {
635    pub inner: parking_lot::Mutex<JoinHandle<T>>,
636    pub waker: AtomicWaker,
637}
638
639impl<T> Drop for InnerHandle<T> {
640    fn drop(&mut self) {
641        self.inner.lock().abort();
642    }
643}
644
645impl<T> InnerHandle<T> {
646    pub fn wake(&self) {
647        self.waker.wake();
648    }
649}
650
651impl<T> Future for AbortableJoinHandle<T> {
652    type Output = Result<T, JoinError>;
653    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
654        self.handle.waker.register(cx.waker());
655        let inner = &mut *self.handle.inner.lock();
656        Pin::new(inner).poll(cx)
657    }
658}
659
660/// A task that accepts messages
661pub struct CommunicationTask<T> {
662    _task_handle: AbortableJoinHandle<()>,
663    _channel_tx: futures::channel::mpsc::Sender<T>,
664}
665
666impl<T> Clone for CommunicationTask<T> {
667    fn clone(&self) -> Self {
668        CommunicationTask {
669            _task_handle: self._task_handle.clone(),
670            _channel_tx: self._channel_tx.clone(),
671        }
672    }
673}
674
675impl<T> Debug for CommunicationTask<T> {
676    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
677        f.debug_struct("CommunicationTask").finish()
678    }
679}
680
681impl<T> CommunicationTask<T> {
682    pub(crate) fn new(
683        task_handle: AbortableJoinHandle<()>,
684        channel_tx: futures::channel::mpsc::Sender<T>,
685    ) -> Self {
686        Self {
687            _task_handle: task_handle,
688            _channel_tx: channel_tx,
689        }
690    }
691
692    /// Send a message to the task
693    pub async fn send(&mut self, data: T) -> std::io::Result<()> {
694        self._channel_tx
695            .send(data)
696            .await
697            .map_err(std::io::Error::other)
698    }
699
700    /// Attempts to send a message to the task, returning an error if the channel is full or closed due to the task being aborted.
701    pub fn try_send(&mut self, data: T) -> std::io::Result<()> {
702        self._channel_tx
703            .try_send(data)
704            .map_err(|e| std::io::Error::other(e.to_string()))
705    }
706
707    /// Abort the task
708    pub fn abort(mut self) {
709        self._channel_tx.close_channel();
710        self._task_handle.abort();
711    }
712
713    /// Check to determine if the task is active.
714    pub fn is_active(&self) -> bool {
715        !self._task_handle.is_finished() && !self._channel_tx.is_closed()
716    }
717}
718
719/// A task that accepts messages
720pub struct UnboundedCommunicationTask<T> {
721    _task_handle: AbortableJoinHandle<()>,
722    _channel_tx: futures::channel::mpsc::UnboundedSender<T>,
723}
724
725impl<T> Clone for UnboundedCommunicationTask<T> {
726    fn clone(&self) -> Self {
727        UnboundedCommunicationTask {
728            _task_handle: self._task_handle.clone(),
729            _channel_tx: self._channel_tx.clone(),
730        }
731    }
732}
733
734impl<T> Debug for UnboundedCommunicationTask<T> {
735    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
736        f.debug_struct("UnboundedCommunicationTask").finish()
737    }
738}
739
740impl<T> UnboundedCommunicationTask<T> {
741    pub(crate) fn new(
742        task_handle: AbortableJoinHandle<()>,
743        channel_tx: futures::channel::mpsc::UnboundedSender<T>,
744    ) -> Self {
745        Self {
746            _task_handle: task_handle,
747            _channel_tx: channel_tx,
748        }
749    }
750
751    /// Send a message to task
752    pub fn send(&mut self, data: T) -> std::io::Result<()> {
753        self._channel_tx
754            .unbounded_send(data)
755            .map_err(|e| std::io::Error::other(e.to_string()))
756    }
757
758    /// Abort the task
759    pub fn abort(self) {
760        self._channel_tx.close_channel();
761        self._task_handle.abort();
762    }
763
764    /// Check to determine if the task is active.
765    pub fn is_active(&self) -> bool {
766        !self._task_handle.is_finished() && !self._channel_tx.is_closed()
767    }
768}
769
770pub trait Executor {
771    /// Returns an optional runtime name of the executor.
772    fn runtime_type(&self) -> Option<&'static str> {
773        None
774    }
775
776    /// Spawns a new asynchronous task in the background, returning a Future [`JoinHandle`] for it.
777    fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
778    where
779        F: Future + Send + 'static,
780        F::Output: Send + 'static;
781
782    /// Spawns a new asynchronous task in the background, returning an abortable handle that will cancel the task
783    /// once the handle is dropped.
784    ///
785    /// Note: This function is used if the task is expected to run until the handle is dropped. It is recommended to use
786    /// [`Executor::spawn`] or [`Executor::dispatch`] otherwise.
787    fn spawn_abortable<F>(&self, future: F) -> AbortableJoinHandle<F::Output>
788    where
789        F: Future + Send + 'static,
790        F::Output: Send + 'static,
791    {
792        let handle = self.spawn(future);
793        handle.into()
794    }
795
796    /// Spawns a new asynchronous task in the background without an handle.
797    /// Basically the same as [`Executor::spawn`].
798    fn dispatch<F>(&self, future: F)
799    where
800        F: Future + Send + 'static,
801        F::Output: Send + 'static,
802    {
803        self.spawn(future);
804    }
805
806    /// Spawns a new asynchronous task that accepts messages to the task.
807    /// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
808    /// (in other words, all handles are dropped), the task would be aborted.
809    fn spawn_coroutine<T, F, Fut>(&self, f: F) -> CommunicationTask<T>
810    where
811        F: FnMut(T) -> Fut + Send + 'static,
812        Fut: Future<Output = ()> + Send + 'static,
813        T: Send + 'static,
814    {
815        Self::spawn_coroutine_with_buffer(self, 1, f)
816    }
817
818    /// Spawns a new asynchronous task that accepts messages to the task with a set buffer.
819    /// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
820    /// (in other words, all handles are dropped), the task would be aborted.
821    fn spawn_coroutine_with_buffer<T, F, Fut>(
822        &self,
823        buffer: usize,
824        mut f: F,
825    ) -> CommunicationTask<T>
826    where
827        F: FnMut(T) -> Fut + Send + 'static,
828        Fut: Future<Output = ()> + Send + 'static,
829        T: Send + 'static,
830    {
831        let (tx, mut rx) = futures::channel::mpsc::channel(buffer);
832        let _task_handle = self.spawn_abortable(async move {
833            while let Some(msg) = rx.next().await {
834                f(msg).await;
835            }
836        });
837        CommunicationTask {
838            _task_handle,
839            _channel_tx: tx,
840        }
841    }
842
843    /// Spawns a new asynchronous task that accepts unbounded messages to the task.
844    /// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
845    /// (in other words, all handles are dropped), the task would be aborted.
846    fn spawn_unbounded_coroutine<T, F, Fut>(&self, mut f: F) -> UnboundedCommunicationTask<T>
847    where
848        F: FnMut(T) -> Fut + Send + 'static,
849        Fut: Future<Output = ()> + Send + 'static,
850        T: Send + 'static,
851    {
852        let (tx, mut rx) = futures::channel::mpsc::unbounded();
853        let _task_handle = self.spawn_abortable(async move {
854            while let Some(msg) = rx.next().await {
855                f(msg).await;
856            }
857        });
858        UnboundedCommunicationTask {
859            _task_handle,
860            _channel_tx: tx,
861        }
862    }
863
864    /// Spawns a new asynchronous task with provided context that accepts messages to the task.
865    /// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
866    /// (in other words, all handles are dropped), the task would be aborted.
867    ///
868    /// # Note
869    /// If state must be borrowed across awaits,
870    /// use [`Executor::spawn_coroutine_with_receiver_and_context`].
871    fn spawn_coroutine_with_context<T, C, F, Fut>(&self, context: C, f: F) -> CommunicationTask<T>
872    where
873        F: FnMut(&mut C, T) -> Fut + Send + 'static,
874        Fut: Future<Output = ()> + Send + 'static,
875        C: Send + 'static,
876        T: Send + 'static,
877    {
878        Self::spawn_coroutine_with_buffer_and_context(self, context, 1, f)
879    }
880
881    /// Spawns a new asynchronous task with provided context that accepts messages to the task with a set buffer.
882    /// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
883    /// (in other words, all handles are dropped), the task would be aborted.
884    fn spawn_coroutine_with_buffer_and_context<T, C, F, Fut>(
885        &self,
886        context: C,
887        buffer: usize,
888        mut f: F,
889    ) -> CommunicationTask<T>
890    where
891        F: FnMut(&mut C, T) -> Fut + Send + 'static,
892        Fut: Future<Output = ()> + Send + 'static,
893        C: Send + 'static,
894        T: Send + 'static,
895    {
896        let (tx, mut rx) = futures::channel::mpsc::channel(buffer);
897        let _task_handle = self.spawn_abortable(async move {
898            let mut context = context;
899            while let Some(msg) = rx.next().await {
900                f(&mut context, msg).await;
901            }
902        });
903        CommunicationTask {
904            _task_handle,
905            _channel_tx: tx,
906        }
907    }
908
909    /// Spawns a new asynchronous task with provided context that accepts unbounded messages to the task.
910    /// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
911    /// (in other words, all handles are dropped), the task would be aborted.
912    fn spawn_unbounded_coroutine_with_context<T, C, F, Fut>(
913        &self,
914        context: C,
915        mut f: F,
916    ) -> UnboundedCommunicationTask<T>
917    where
918        F: FnMut(&mut C, T) -> Fut + Send + 'static,
919        Fut: Future<Output = ()> + Send + 'static,
920        C: Send + 'static,
921        T: Send + 'static,
922    {
923        let (tx, mut rx) = futures::channel::mpsc::unbounded();
924        let _task_handle = self.spawn_abortable(async move {
925            let mut context = context;
926            while let Some(msg) = rx.next().await {
927                f(&mut context, msg).await;
928            }
929        });
930        UnboundedCommunicationTask {
931            _task_handle,
932            _channel_tx: tx,
933        }
934    }
935
936    /// Spawns a new asynchronous task that accepts messages to the task using [`channels`](futures::channel::mpsc).
937    /// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
938    /// (in other words, all handles are dropped), the task would be aborted.
939    fn spawn_coroutine_with_receiver<T, F, Fut>(&self, f: F) -> CommunicationTask<T>
940    where
941        F: FnMut(Receiver<T>) -> Fut,
942        Fut: Future<Output = ()> + Send + 'static,
943    {
944        Self::spawn_coroutine_with_receiver_and_buffer(self, 1, f)
945    }
946
947    /// Spawns a new asynchronous task with a set channel buffer that accepts messages to the task using [`channels`](futures::channel::mpsc).
948    /// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
949    /// (in other words, all handles are dropped), the task would be aborted.
950    fn spawn_coroutine_with_receiver_and_buffer<T, F, Fut>(
951        &self,
952        buffer: usize,
953        mut f: F,
954    ) -> CommunicationTask<T>
955    where
956        F: FnMut(Receiver<T>) -> Fut,
957        Fut: Future<Output = ()> + Send + 'static,
958    {
959        let (tx, rx) = futures::channel::mpsc::channel(buffer);
960        let fut = f(rx);
961        let _task_handle = self.spawn_abortable(fut);
962        CommunicationTask {
963            _task_handle,
964            _channel_tx: tx,
965        }
966    }
967
968    /// Spawns a new asynchronous task with provided context that accepts messages to the task using [`channels`](futures::channel::mpsc).
969    /// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
970    /// (in other words, all handles are dropped), the task would be aborted.
971    fn spawn_coroutine_with_receiver_and_context<T, F, C, Fut>(
972        &self,
973        context: C,
974        f: F,
975    ) -> CommunicationTask<T>
976    where
977        F: FnMut(C, Receiver<T>) -> Fut,
978        Fut: Future<Output = ()> + Send + 'static,
979    {
980        Self::spawn_coroutine_with_receiver_buffer_and_context(self, context, 1, f)
981    }
982
983    /// Spawns a new asynchronous task with a set channel buffer and provided context that accepts messages to the task using [`channels`](futures::channel::mpsc).
984    /// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
985    /// (in other words, all handles are dropped), the task would be aborted.
986    fn spawn_coroutine_with_receiver_buffer_and_context<T, F, C, Fut>(
987        &self,
988        context: C,
989        buffer: usize,
990        mut f: F,
991    ) -> CommunicationTask<T>
992    where
993        F: FnMut(C, Receiver<T>) -> Fut,
994        Fut: Future<Output = ()> + Send + 'static,
995    {
996        let (tx, rx) = futures::channel::mpsc::channel(buffer);
997        let fut = f(context, rx);
998        let _task_handle = self.spawn_abortable(fut);
999        CommunicationTask {
1000            _task_handle,
1001            _channel_tx: tx,
1002        }
1003    }
1004
1005    /// Spawns a new asynchronous task that accepts messages to the task using [`channels`](futures::channel::mpsc).
1006    /// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
1007    /// (in other words, all handles are dropped), the task would be aborted.
1008    fn spawn_unbounded_coroutine_with_receiver<T, F, Fut>(
1009        &self,
1010        mut f: F,
1011    ) -> UnboundedCommunicationTask<T>
1012    where
1013        F: FnMut(UnboundedReceiver<T>) -> Fut,
1014        Fut: Future<Output = ()> + Send + 'static,
1015    {
1016        let (tx, rx) = futures::channel::mpsc::unbounded();
1017        let fut = f(rx);
1018        let _task_handle = self.spawn_abortable(fut);
1019        UnboundedCommunicationTask {
1020            _task_handle,
1021            _channel_tx: tx,
1022        }
1023    }
1024
1025    /// Spawns a new asynchronous task with provided context that accepts messages to the task using [`channels`](futures::channel::mpsc).
1026    /// This function returns a handle that allows sending a message, or if there is no reference to the handle at all
1027    /// (in other words, all handles are dropped), the task would be aborted.
1028    fn spawn_unbounded_coroutine_with_receiver_and_context<T, F, C, Fut>(
1029        &self,
1030        context: C,
1031        mut f: F,
1032    ) -> UnboundedCommunicationTask<T>
1033    where
1034        F: FnMut(C, UnboundedReceiver<T>) -> Fut,
1035        Fut: Future<Output = ()> + Send + 'static,
1036    {
1037        let (tx, rx) = futures::channel::mpsc::unbounded();
1038        let fut = f(context, rx);
1039        let _task_handle = self.spawn_abortable(fut);
1040        UnboundedCommunicationTask {
1041            _task_handle,
1042            _channel_tx: tx,
1043        }
1044    }
1045
1046    /// Create a structured-concurrency scope in which tasks may be spawned
1047    /// that borrow from the enclosing stack frame.
1048    ///
1049    /// Unlike [`Executor::spawn`], tasks spawned on the [`Scope`] are driven
1050    /// cooperatively by the returned future so they may borrow any data that outlives
1051    /// the `'env` lifetime. Every task is either completed or canceled before
1052    /// `scope` returns, so borrows never outlive the stack frame.
1053    ///
1054    /// This is the async analogue of [`std::thread::scope`].
1055    ///
1056    /// # Example
1057    ///
1058    /// ```no_run
1059    /// # async fn run() {
1060    /// use async_rt::Executor;
1061    /// use async_rt::global::ConfiguredExecutor;
1062    ///
1063    /// let executor: ConfiguredExecutor = ConfiguredExecutor::default();
1064    /// let data = vec![1, 2, 3, 4];
1065    /// let sum = executor
1066    ///     .scope(async |s| {
1067    ///         let a = s.spawn(async { data[0] + data[1] });
1068    ///         let b = s.spawn(async { data[2] + data[3] });
1069    ///         a.await.unwrap() + b.await.unwrap()
1070    ///     })
1071    ///     .await;
1072    /// assert_eq!(sum, 10);
1073    /// # }
1074    /// ```
1075    fn scope<'env, F, T>(&self, f: F) -> impl Future<Output = T>
1076    where
1077        F: for<'scope> AsyncFnOnce(&'scope Scope<'scope, 'env>) -> T,
1078    {
1079        scoped::scope(f)
1080    }
1081
1082    /// Run an async closure with a scoped [`Executor`] wrapper that
1083    /// forwards spawns to this executor, waits for all spawned tasks
1084    /// to finish when the closure returns, and aborts any outstanding
1085    /// tasks if the scope future itself is cancelled.
1086    ///
1087    /// Unlike [`Executor::scope`], tasks run on the real executor (so
1088    /// they get real parallelism) but must be `Send + 'static`.
1089    ///
1090    /// # Example
1091    ///
1092    /// ```no_run
1093    /// # async fn run() {
1094    /// use async_rt::Executor;
1095    /// use async_rt::global::ConfiguredExecutor;
1096    ///
1097    /// let executor: ConfiguredExecutor = ConfiguredExecutor::default();
1098    /// let total = executor
1099    ///     .executor_scope(async |s| {
1100    ///         let a = s.spawn(async { 1 + 2 });
1101    ///         let b = s.spawn(async { 3 + 4 });
1102    ///         a.await.unwrap() + b.await.unwrap()
1103    ///     })
1104    ///     .await;
1105    /// assert_eq!(total, 10);
1106    /// # }
1107    /// ```
1108    fn executor_scope<'scope, F, T>(&'scope self, f: F) -> impl Future<Output = T>
1109    where
1110        Self: Sized,
1111        F: AsyncFnOnce(&ScopeExecutor<'scope, Self>) -> T,
1112    {
1113        scoped::executor_scope(self, f)
1114    }
1115}
1116
1117pub trait ExecutorBlocking: Executor {
1118    /// Spawn a thread in the background with the ability to concurrently await on the results without
1119    /// blocking the executor.
1120    ///
1121    /// Note that there is no real way to abort the thread, and calling [`JoinHandle::abort`]
1122    /// would only abort the underlining tasked that is being polled but not the thread itself.
1123    fn spawn_blocking<F, R>(&self, f: F) -> JoinHandle<R>
1124    where
1125        F: FnOnce() -> R + Send + 'static,
1126        R: Send + 'static;
1127
1128    /// Spawns a thread in the background, returning an abortable handle that will cancel it
1129    /// once the handle is dropped.
1130    ///
1131    /// Note: This function is used if the task is expected to run until the handle is dropped.
1132    /// It is recommended to use [`ExecutorBlocking::spawn_blocking`]. Additionally, there is no guarantee
1133    /// that the thread will cancel or abort.
1134    fn spawn_blocking_abortable<F, R>(&self, f: F) -> AbortableJoinHandle<R>
1135    where
1136        F: FnOnce() -> R + Send + 'static,
1137        R: Send + 'static,
1138    {
1139        let handle = self.spawn_blocking(f);
1140        handle.into()
1141    }
1142}
1143
1144pub trait ExecutorTimeout: Executor {
1145    /// Spawns a new asynchronous task in the background that must complete within `duration`,
1146    /// returning a [`JoinHandle`].
1147    ///
1148    /// If the future does not finish before `duration` elapses, it is dropped and the task
1149    /// completes with [`TimeoutError`].
1150    fn spawn_timeout<F>(
1151        &self,
1152        duration: std::time::Duration,
1153        f: F,
1154    ) -> JoinHandle<Result<F::Output, TimeoutError>>
1155    where
1156        F: Future + Send + 'static,
1157        F::Output: Send + 'static,
1158    {
1159        self.spawn(Timeout::from_future(f, duration).map_err(|_| TimeoutError))
1160    }
1161
1162    /// Spawns a task after waiting for a duration before the task is polled.
1163    fn spawn_delay<F>(&self, duration: std::time::Duration, f: F) -> JoinHandle<F::Output>
1164    where
1165        F: Future + Send + 'static,
1166        F::Output: Send + 'static,
1167    {
1168        self.spawn(async move {
1169            let _ = Timeout::from_future(futures::future::pending::<()>(), duration).await;
1170            f.await
1171        })
1172    }
1173
1174    /// Spawns a new asynchronous task in the background that must complete within `duration`,
1175    /// returning an abortable handle that will cancel the task once the handle is dropped.
1176    ///
1177    /// If the future does not finish before `duration` elapses, it is dropped and the task
1178    /// completes with [`TimeoutError`].
1179    fn spawn_abortable_timeout<F>(
1180        &self,
1181        duration: std::time::Duration,
1182        f: F,
1183    ) -> AbortableJoinHandle<Result<F::Output, TimeoutError>>
1184    where
1185        F: Future + Send + 'static,
1186        F::Output: Send + 'static,
1187    {
1188        self.spawn_abortable(Timeout::from_future(f, duration).map_err(|_| TimeoutError))
1189    }
1190
1191    /// Spawns an abortable task after waiting for `duration` before the task is polled.
1192    fn spawn_abortable_delay<F>(
1193        &self,
1194        duration: std::time::Duration,
1195        f: F,
1196    ) -> AbortableJoinHandle<F::Output>
1197    where
1198        F: Future + Send + 'static,
1199        F::Output: Send + 'static,
1200    {
1201        self.spawn_abortable(async move {
1202            let _ = Timeout::from_future(futures::future::pending::<()>(), duration).await;
1203            f.await
1204        })
1205    }
1206}
1207
1208pub trait ExecutorBlockOn: Executor {
1209    /// Blocks the current thread until the provided future has completed.
1210    ///
1211    /// Note that calling this function within an executor context may cause a deadlock.
1212    fn block_on<F: Future>(&self, future: F) -> F::Output;
1213}
1214
1215#[cfg(test)]
1216mod tests {
1217    use crate::CompletionGuard;
1218    use crate::error::JoinError;
1219    use crate::{Executor, ExecutorBlocking, InnerJoinHandle, JoinHandle};
1220    use futures::future::AbortHandle;
1221    use pollable_map::optional::Optional;
1222    use std::future::Future;
1223    use std::sync::Arc;
1224    use std::sync::atomic::AtomicBool;
1225
1226    async fn task(tx: futures::channel::oneshot::Sender<()>) {
1227        futures_timer::Delay::new(std::time::Duration::from_secs(5)).await;
1228        let _ = tx.send(());
1229        unreachable!();
1230    }
1231
1232    #[cfg(all(feature = "tokio", not(target_arch = "wasm32")))]
1233    #[tokio::test]
1234    async fn replacing_handle_wakes_pending_clone() {
1235        use futures::task::{ArcWake, waker_ref};
1236        use std::pin::Pin;
1237        use std::sync::atomic::{AtomicUsize, Ordering};
1238        use std::task::Context;
1239
1240        struct WakeCounter(AtomicUsize);
1241
1242        impl ArcWake for WakeCounter {
1243            fn wake_by_ref(arc_self: &Arc<Self>) {
1244                arc_self.0.fetch_add(1, Ordering::SeqCst);
1245            }
1246        }
1247
1248        let executor = crate::rt::tokio::TokioExecutor;
1249        let handle = executor.spawn_abortable(futures::future::pending::<usize>());
1250        let mut pending_clone = handle.clone();
1251
1252        let wake_counter = Arc::new(WakeCounter(AtomicUsize::new(0)));
1253        let waker = waker_ref(&wake_counter);
1254        let mut context = Context::from_waker(&waker);
1255
1256        assert!(Pin::new(&mut pending_clone).poll(&mut context).is_pending());
1257        assert_eq!(wake_counter.0.load(Ordering::SeqCst), 0);
1258
1259        let replacement = executor.spawn_abortable(async { 42 });
1260        handle.replace(replacement);
1261
1262        assert!(wake_counter.0.load(Ordering::SeqCst) > 0);
1263        assert_eq!(pending_clone.await.unwrap(), 42);
1264    }
1265
1266    #[test]
1267    fn custom_abortable_task() {
1268        struct FuturesExecutor {
1269            pool: futures::executor::ThreadPool,
1270        }
1271
1272        impl Default for FuturesExecutor {
1273            fn default() -> Self {
1274                Self {
1275                    pool: futures::executor::ThreadPool::new().unwrap(),
1276                }
1277            }
1278        }
1279
1280        impl Executor for FuturesExecutor {
1281            fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
1282            where
1283                F: Future + Send + 'static,
1284                F::Output: Send + 'static,
1285            {
1286                let (abort_handle, abort_registration) = AbortHandle::new_pair();
1287                let future = crate::abortable_result(future, abort_registration);
1288                let (tx, rx) = futures::channel::oneshot::channel();
1289
1290                let fin = Arc::new(AtomicBool::new(false));
1291                let finished = fin.clone();
1292                let completion = CompletionGuard::new(fin);
1293                let fut = async move {
1294                    let _completion = completion;
1295                    let val = future.await;
1296                    let _ = tx.send(val);
1297                };
1298
1299                self.pool.spawn_ok(fut);
1300                let inner = InnerJoinHandle::CustomHandle {
1301                    inner: Optional::new(rx),
1302                    handle: abort_handle,
1303                    finished,
1304                };
1305
1306                JoinHandle { inner }
1307            }
1308        }
1309
1310        impl ExecutorBlocking for FuturesExecutor {
1311            fn spawn_blocking<F, R>(&self, _: F) -> JoinHandle<R>
1312            where
1313                F: FnOnce() -> R + Send + 'static,
1314                R: Send + 'static,
1315            {
1316                unimplemented!()
1317            }
1318        }
1319
1320        futures::executor::block_on(async move {
1321            let executor = FuturesExecutor::default();
1322
1323            let (tx, rx) = futures::channel::oneshot::channel::<()>();
1324            let handle = executor.spawn_abortable(task(tx));
1325            drop(handle);
1326            let result = rx.await;
1327            assert!(result.is_err());
1328        });
1329    }
1330
1331    #[test]
1332    fn empty_handle_reports_empty() {
1333        let handle = JoinHandle::<()>::empty();
1334
1335        assert!(matches!(
1336            futures::executor::block_on(handle),
1337            Err(JoinError::Empty)
1338        ));
1339    }
1340
1341    #[test]
1342    fn custom_handle_reports_cancelled_when_sender_dropped() {
1343        let (tx, rx) = futures::channel::oneshot::channel::<Result<(), JoinError>>();
1344        let (abort_handle, _abort_registration) = AbortHandle::new_pair();
1345        let handle = JoinHandle {
1346            inner: InnerJoinHandle::CustomHandle {
1347                inner: Optional::new(rx),
1348                handle: abort_handle,
1349                finished: Arc::new(AtomicBool::new(false)),
1350            },
1351        };
1352
1353        // Dropping the sender without producing a value mimics a task the
1354        // executor discarded (e.g. runtime shutdown) rather than an explicit
1355        // abort, which should surface as `Cancelled` rather than `Aborted`.
1356        drop(tx);
1357
1358        assert!(matches!(
1359            futures::executor::block_on(handle),
1360            Err(JoinError::Cancelled)
1361        ));
1362    }
1363}