Skip to main content

nuclei/rt/task/
join.rs

1//! The handle of a spawned task: [`Task`], and its [`FallibleTask`] form.
2
3use core::fmt;
4use core::future::Future;
5use core::marker::PhantomData;
6use core::mem::{ManuallyDrop, MaybeUninit};
7use core::pin::Pin;
8use core::ptr::NonNull;
9use core::task::{Context, Poll};
10use std::panic::{RefUnwindSafe, UnwindSafe};
11
12use super::header::Header;
13use super::raw::Panic;
14use super::state::{AWAITER, CLOSED, COMPLETED, FLAGS, REFERENCE, RUNNING, SCHEDULED, TASK};
15use crate::sync::internal::Ordering;
16
17/// A spawned task.
18///
19/// Awaiting it gives the task's output. Dropping it cancels the task: its
20/// future is dropped without being polled again. [`detach`](Self::detach)
21/// lets the task run to its end without a handle.
22///
23/// If the task's future panics, awaiting the handle resumes the panic.
24///
25/// # Panics
26///
27/// Awaiting a task that was cancelled before it completed (through an
28/// [`AbortHandle`], or by its runtime's shutdown) panics;
29/// [`join`](Self::join) answers a [`JoinError`] instead, and
30/// [`fallible`](Self::fallible) gives a handle that returns `None`.
31#[must_use = "dropping a task cancels it; call `detach` to let it run"]
32pub struct Task<T> {
33    ptr: NonNull<()>,
34    _output: PhantomData<T>,
35}
36
37// SAFETY: the handle hands the output out by value, so it may move to
38// another thread when the output may.
39unsafe impl<T: Send> Send for Task<T> {}
40// SAFETY: `&Task` only reads the state word.
41unsafe impl<T> Sync for Task<T> {}
42
43impl<T> UnwindSafe for Task<T> {}
44impl<T> RefUnwindSafe for Task<T> {}
45
46impl<T> Unpin for Task<T> {}
47
48impl<T> Task<T> {
49    /// # Safety
50    ///
51    /// `ptr` is a task with output `T` whose `TASK` flag the handle takes
52    /// over.
53    pub(super) unsafe fn from_raw(ptr: NonNull<()>) -> Self {
54        Self {
55            ptr,
56            _output: PhantomData,
57        }
58    }
59
60    fn header(&self) -> &Header {
61        // SAFETY: the TASK flag keeps the task alive.
62        unsafe { self.ptr.cast::<Header>().as_ref() }
63    }
64
65    /// Lets the task run to its end without a handle; its output is dropped.
66    #[inline(always)]
67    pub fn detach(self) {
68        let mut this = ManuallyDrop::new(self);
69        // A handle detached right after the spawn is the common case: one
70        // exchange, inline.
71        if this
72            .header()
73            .state
74            .compare_exchange_weak(
75                SCHEDULED | TASK | REFERENCE,
76                SCHEDULED | REFERENCE,
77                Ordering::AcqRel,
78                Ordering::Acquire,
79            )
80            .is_ok()
81        {
82            return;
83        }
84        this.detach_late();
85    }
86
87    /// A detach after the task ran, was woken or completed: out of line, so
88    /// the exchange above needs no frame.
89    #[inline(never)]
90    fn detach_late(&mut self) {
91        drop(self.set_detached());
92    }
93
94    /// Cancels the task and waits for its future to be dropped. Returns the
95    /// output if the task had already completed.
96    pub async fn cancel(self) -> Option<T> {
97        let mut this = self;
98        this.set_canceled();
99        core::future::poll_fn(|cx| this.poll_task(cx)).await
100    }
101
102    /// A handle that returns `None` where this one panics: when the task was
103    /// cancelled, or its runtime dropped it before it completed.
104    pub fn fallible(self) -> FallibleTask<T> {
105        FallibleTask { task: self }
106    }
107
108    /// Whether the task completed or was cancelled. Awaiting a finished task
109    /// returns at once.
110    pub fn is_finished(&self) -> bool {
111        self.header().state.load(Ordering::Acquire) & (COMPLETED | CLOSED) != 0
112    }
113
114    /// Closes the task unless it completed; see [`cancel`].
115    fn set_canceled(&mut self) {
116        // SAFETY: the TASK flag keeps the task alive.
117        unsafe { cancel(self.ptr.as_ptr().cast_const()) }
118    }
119
120    /// The task's id.
121    pub fn id(&self) -> super::Id {
122        self.header().id
123    }
124
125    /// Cancels the task without waiting: its future is dropped where it runs,
126    /// and awaiting the handle then finds no output (unless the task had
127    /// completed). [`join`](Self::join) reports it as a cancelled
128    /// [`JoinError`].
129    pub fn abort(&self) {
130        // SAFETY: the TASK flag keeps the task alive.
131        unsafe { cancel(self.ptr.as_ptr().cast_const()) }
132    }
133
134    /// A handle that cancels the task from anywhere, without the [`Task`]:
135    /// it can be cloned and sent to another thread, and outlives the handle.
136    pub fn abort_handle(&self) -> AbortHandle {
137        let ptr = self.ptr.as_ptr().cast_const();
138        let header = self.header();
139        // A waker clone holds one reference of the task, and its drop, the
140        // last one, drops an open task's future where the task runs (never on
141        // the dropping thread): what an abort handle needs.
142        // SAFETY: the TASK flag keeps the task alive while the clone is made.
143        let waker = core::mem::ManuallyDrop::new(unsafe {
144            core::task::Waker::from_raw(core::task::RawWaker::new(ptr, &header.vtable.waker))
145        });
146        AbortHandle {
147            waker: (*waker).clone(),
148            id: header.id,
149        }
150    }
151
152    /// A future that gives the task's output, or a [`JoinError`] saying why
153    /// there is none: the task was cancelled (its runtime dropped it too), or
154    /// its future panicked, with the payload.
155    pub fn join(self) -> Join<T> {
156        Join { task: self }
157    }
158}
159
160/// Closes the task `ptr` unless it completed. An idle task is scheduled once
161/// more, so its future is dropped where the task runs, and an awaiter is told.
162///
163/// # Safety
164///
165/// `ptr` is a live task (its handle or a reference keeps it alive).
166unsafe fn cancel(ptr: *const ()) {
167    // SAFETY: the caller's contract.
168    let header = unsafe { &*ptr.cast::<Header>() };
169    {
170        let mut state = header.state.load(Ordering::Acquire);
171        loop {
172            if state & (COMPLETED | CLOSED) != 0 {
173                return;
174            }
175            let idle = state & (SCHEDULED | RUNNING) == 0;
176            let new = if idle {
177                (state | SCHEDULED | CLOSED) + REFERENCE
178            } else {
179                state | CLOSED
180            };
181            match header.state.compare_exchange_weak(
182                state,
183                new,
184                Ordering::AcqRel,
185                Ordering::Acquire,
186            ) {
187                Ok(_) => {
188                    if idle {
189                        // SAFETY: the reference added above goes to the
190                        // runnable, which drops the future when it runs.
191                        unsafe { (header.vtable.schedule)(ptr, super::ScheduleInfo::new(false)) };
192                    }
193                    if state & AWAITER != 0 {
194                        header.notify(None);
195                    }
196                    return;
197                }
198                Err(actual) => state = actual,
199            }
200        }
201    }
202}
203
204impl<T> Task<T> {
205    /// Lets go of the handle. Returns the output when the task had completed
206    /// and nobody took it, so the caller drops it outside the transition.
207    fn set_detached(&mut self) -> Option<Result<T, Panic>> {
208        let ptr = self.ptr.as_ptr().cast_const();
209        let header = self.header();
210        let mut output = None;
211        // A handle detached right after the spawn is the common case: one
212        // exchange.
213        let Err(mut state) = header.state.compare_exchange_weak(
214            SCHEDULED | TASK | REFERENCE,
215            SCHEDULED | REFERENCE,
216            Ordering::AcqRel,
217            Ordering::Acquire,
218        ) else {
219            return None;
220        };
221        loop {
222            if state & COMPLETED != 0 && state & CLOSED == 0 {
223                // Completed and not taken: close it to take the output.
224                match header.state.compare_exchange_weak(
225                    state,
226                    state | CLOSED,
227                    Ordering::AcqRel,
228                    Ordering::Acquire,
229                ) {
230                    Ok(_) => {
231                        output = Some(self.read_output());
232                        state |= CLOSED;
233                    }
234                    Err(actual) => state = actual,
235                }
236                continue;
237            }
238            // The last reference with the task still open: close it and
239            // schedule it once more so its future is dropped where it runs.
240            let last = state & !FLAGS == 0;
241            let new = if last && state & CLOSED == 0 {
242                SCHEDULED | CLOSED | REFERENCE
243            } else {
244                state & !TASK
245            };
246            match header.state.compare_exchange_weak(
247                state,
248                new,
249                Ordering::AcqRel,
250                Ordering::Acquire,
251            ) {
252                Ok(_) => {
253                    if last {
254                        // SAFETY: with no reference left, the handle decides:
255                        // a closed task is freed, an open one gets the
256                        // reference set above for its runnable.
257                        unsafe {
258                            if state & CLOSED == 0 {
259                                (header.vtable.schedule)(ptr, super::ScheduleInfo::new(false));
260                            } else {
261                                (header.vtable.destroy)(ptr);
262                            }
263                        }
264                    }
265                    return output;
266                }
267                Err(actual) => state = actual,
268            }
269        }
270    }
271
272    /// Moves the output out of the cell.
273    fn read_output(&self) -> Result<T, Panic> {
274        let ptr = self.ptr.as_ptr().cast_const();
275        let mut out = MaybeUninit::<Result<T, Panic>>::uninit();
276        // SAFETY: the caller closed the completed task, so the output is
277        // there and this call alone takes it.
278        unsafe {
279            (self.header().vtable.read_output)(ptr, out.as_mut_ptr().cast());
280            out.assume_init()
281        }
282    }
283
284    /// `Some(output)` once the task completed (resuming a panic of its
285    /// future), `None` once it was closed without an output for the handle.
286    #[inline(always)]
287    fn poll_task(&mut self, cx: &mut Context<'_>) -> Poll<Option<T>> {
288        match self.poll_raw(cx) {
289            Poll::Ready(Some(Ok(output))) => Poll::Ready(Some(output)),
290            Poll::Ready(Some(Err(panic))) => std::panic::resume_unwind(panic),
291            Poll::Ready(None) => Poll::Ready(None),
292            Poll::Pending => Poll::Pending,
293        }
294    }
295
296    /// The output once the task completed (or the panic of its future),
297    /// `None` once it was closed without an output for the handle (cancelled,
298    /// or dropped by its runtime) and its future is gone.
299    #[inline(always)]
300    fn poll_raw(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<T, Panic>>> {
301        let header = self.header();
302        let mut state = header.state.load(Ordering::Acquire);
303        loop {
304            if state & CLOSED != 0 {
305                // The future may still be in a run queue or a poll: wait for
306                // it to be dropped.
307                if state & (SCHEDULED | RUNNING) != 0 {
308                    header.register(cx.waker());
309                    state = header.state.load(Ordering::Acquire);
310                    if state & (SCHEDULED | RUNNING) != 0 {
311                        return Poll::Pending;
312                    }
313                }
314                header.notify(Some(cx.waker()));
315                return Poll::Ready(None);
316            }
317            if state & COMPLETED == 0 {
318                header.register(cx.waker());
319                // The task may have completed or closed just before the
320                // registration.
321                state = header.state.load(Ordering::Acquire);
322                if state & CLOSED != 0 {
323                    continue;
324                }
325                if state & COMPLETED == 0 {
326                    return Poll::Pending;
327                }
328            }
329            match header.state.compare_exchange(
330                state,
331                state | CLOSED,
332                Ordering::AcqRel,
333                Ordering::Acquire,
334            ) {
335                Ok(_) => {
336                    if state & AWAITER != 0 {
337                        header.notify(Some(cx.waker()));
338                    }
339                    return Poll::Ready(Some(self.read_output()));
340                }
341                Err(actual) => state = actual,
342            }
343        }
344    }
345}
346
347impl<T> Drop for Task<T> {
348    #[inline]
349    fn drop(&mut self) {
350        // The output taken and every reference gone, nothing but the handle
351        // reaches the task and its word cannot change: what the cancel and the
352        // detach below would end in, the free, comes at once.
353        if self.header().state.load(Ordering::Acquire) == COMPLETED | CLOSED | TASK {
354            // SAFETY: the handle is the task's last holder.
355            unsafe { (self.header().vtable.destroy)(self.ptr.as_ptr().cast_const()) };
356            return;
357        }
358        self.cancel_and_let_go();
359    }
360}
361
362impl<T> Task<T> {
363    /// The drop of a handle whose task may still run or hold its output:
364    /// out of line, so the drop of an awaited handle carries none of it.
365    #[inline(never)]
366    fn cancel_and_let_go(&mut self) {
367        self.set_canceled();
368        drop(self.set_detached());
369    }
370}
371
372impl<T> Future for Task<T> {
373    type Output = T;
374
375    #[expect(
376        clippy::panic,
377        reason = "documented under `# Panics` of `Task`; `Task::fallible` answers `None` instead"
378    )]
379    #[inline(always)]
380    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
381        match self.poll_task(cx) {
382            Poll::Ready(Some(output)) => Poll::Ready(output),
383            Poll::Ready(None) => panic!(
384                "the task has no output: it was cancelled or its runtime shut down before it \
385                 completed, or it was awaited again after completing"
386            ),
387            Poll::Pending => Poll::Pending,
388        }
389    }
390}
391
392impl<T> fmt::Debug for Task<T> {
393    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
394        let state = self.header().state.load(Ordering::Acquire);
395        f.debug_struct("Task")
396            .field("scheduled", &(state & SCHEDULED != 0))
397            .field("running", &(state & RUNNING != 0))
398            .field("completed", &(state & COMPLETED != 0))
399            .field("closed", &(state & CLOSED != 0))
400            .finish()
401    }
402}
403
404/// A spawned task whose output is `None` when the task ended without one.
405///
406/// Made by [`Task::fallible`]: awaiting it returns `Some(output)`, or `None`
407/// where awaiting the [`Task`] would panic.
408#[must_use = "dropping a task cancels it; call `detach` to let it run"]
409pub struct FallibleTask<T> {
410    task: Task<T>,
411}
412
413impl<T> FallibleTask<T> {
414    /// Lets the task run to its end without a handle; see [`Task::detach`].
415    pub fn detach(self) {
416        self.task.detach()
417    }
418
419    /// Cancels the task; see [`Task::cancel`].
420    pub async fn cancel(self) -> Option<T> {
421        self.task.cancel().await
422    }
423
424    /// Whether the task completed or was cancelled.
425    pub fn is_finished(&self) -> bool {
426        self.task.is_finished()
427    }
428
429    /// The task's id.
430    pub fn id(&self) -> super::Id {
431        self.task.id()
432    }
433}
434
435impl<T> Future for FallibleTask<T> {
436    type Output = Option<T>;
437
438    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T>> {
439        self.task.poll_task(cx)
440    }
441}
442
443impl<T> fmt::Debug for FallibleTask<T> {
444    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
445        f.debug_tuple("FallibleTask").field(&self.task).finish()
446    }
447}
448
449/// The future of [`Task::join`]: the task's output, or why there is none.
450#[must_use = "dropping it cancels the task, as dropping the `Task` does"]
451pub struct Join<T> {
452    task: Task<T>,
453}
454
455impl<T> Join<T> {
456    /// Lets the task run to its end without a handle; see [`Task::detach`].
457    pub fn detach(self) {
458        self.task.detach()
459    }
460
461    /// The task's id.
462    pub fn id(&self) -> super::Id {
463        self.task.id()
464    }
465}
466
467impl<T> Future for Join<T> {
468    type Output = Result<T, JoinError>;
469
470    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
471        let id = self.task.id();
472        self.task.poll_raw(cx).map(|output| match output {
473            Some(Ok(output)) => Ok(output),
474            Some(Err(panic)) => Err(JoinError {
475                id,
476                panic: Some(panic),
477            }),
478            None => Err(JoinError { id, panic: None }),
479        })
480    }
481}
482
483impl<T> fmt::Debug for Join<T> {
484    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
485        f.debug_tuple("Join").field(&self.task).finish()
486    }
487}
488
489/// Why a task gave no output: it was cancelled (by its handle, an
490/// [`AbortHandle`], or its runtime shutting down), or its future panicked.
491pub struct JoinError {
492    id: super::Id,
493    panic: Option<Panic>,
494}
495
496impl JoinError {
497    /// The task was cancelled.
498    pub fn is_cancelled(&self) -> bool {
499        self.panic.is_none()
500    }
501
502    /// The task's future panicked.
503    pub fn is_panic(&self) -> bool {
504        self.panic.is_some()
505    }
506
507    /// The payload of the panic.
508    ///
509    /// # Panics
510    ///
511    /// When the task was cancelled, not panicked; [`try_into_panic`](Self::try_into_panic)
512    /// answers it instead.
513    #[expect(clippy::panic, reason = "documented under `# Panics`")]
514    pub fn into_panic(self) -> Box<dyn core::any::Any + Send + 'static> {
515        match self.try_into_panic() {
516            Ok(panic) => panic,
517            Err(error) => panic!("`JoinError::into_panic` on a cancelled task ({error})"),
518        }
519    }
520
521    /// The payload of the panic, or the error itself when the task was
522    /// cancelled.
523    pub fn try_into_panic(self) -> Result<Box<dyn core::any::Any + Send + 'static>, JoinError> {
524        match self.panic {
525            Some(panic) => Ok(panic),
526            None => Err(self),
527        }
528    }
529
530    /// The id of the task.
531    pub fn id(&self) -> super::Id {
532        self.id
533    }
534}
535
536impl fmt::Display for JoinError {
537    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
538        match &self.panic {
539            None => write!(f, "task {} was cancelled", self.id),
540            Some(panic) => match panic
541                .downcast_ref::<&str>()
542                .copied()
543                .or_else(|| panic.downcast_ref::<String>().map(String::as_str))
544            {
545                Some(message) => write!(f, "task {} panicked: {message}", self.id),
546                None => write!(f, "task {} panicked", self.id),
547            },
548        }
549    }
550}
551
552impl fmt::Debug for JoinError {
553    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
554        f.debug_struct("JoinError")
555            .field("id", &self.id)
556            .field("cancelled", &self.is_cancelled())
557            .finish()
558    }
559}
560
561impl std::error::Error for JoinError {}
562
563impl From<JoinError> for std::io::Error {
564    fn from(error: JoinError) -> Self {
565        std::io::Error::other(error.to_string())
566    }
567}
568
569/// Cancels a task from anywhere: made by [`Task::abort_handle`], cloned and
570/// sent freely, and it outlives the [`Task`]. It keeps the task's memory, not
571/// its future: an aborted task's future is dropped where the task runs.
572#[derive(Clone)]
573pub struct AbortHandle {
574    /// A reference of the task, as a waker of it holds one.
575    waker: core::task::Waker,
576    id: super::Id,
577}
578
579impl AbortHandle {
580    /// Cancels the task unless it completed; see [`Task::abort`].
581    pub fn abort(&self) {
582        // SAFETY: the waker's reference keeps the task alive.
583        unsafe { cancel(self.waker.data()) }
584    }
585
586    /// Whether the task completed or was cancelled.
587    pub fn is_finished(&self) -> bool {
588        // SAFETY: the waker's reference keeps the task alive.
589        let header = unsafe { &*self.waker.data().cast::<Header>() };
590        header.state.load(Ordering::Acquire) & (COMPLETED | CLOSED) != 0
591    }
592
593    /// The task's id.
594    pub fn id(&self) -> super::Id {
595        self.id
596    }
597}
598
599impl fmt::Debug for AbortHandle {
600    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
601        f.debug_struct("AbortHandle").field("id", &self.id).finish()
602    }
603}