Skip to main content

nuclei/rt/task/
mod.rs

1//! Tasks: a spawned future, the [`Runnable`] a run queue holds for it, and
2//! the [`Task`] handle that awaits its output.
3//!
4//! A task is one allocation (see `raw`) with one state word. Spawning
5//! returns its `Runnable`, already scheduled, and its handle. Running the
6//! runnable polls the future once; a waker that fires makes a new runnable
7//! and hands it to the task's [`Schedule`], which puts it in a run queue. A
8//! wake during the poll only marks the task, and the run hands it to the
9//! schedule once the poll is over, so a task is never polled twice at once
10//! and never re-polled from inside its own poll.
11//!
12//! The semantics are those of the established task crate the old executor
13//! was built on:
14//!
15//! - A poll that panics completes the task with the panic, and awaiting the
16//!   handle resumes it; a detached task's panic is dropped (the panic hook
17//!   has already reported it).
18//! - Dropping the handle cancels the task: its future is dropped by the next
19//!   run, or right away by the runnable's drop. [`Task::detach`] lets it run
20//!   to the end instead, and [`Task::cancel`] cancels and returns the output
21//!   if the task had completed.
22//! - Dropping a runnable without running it cancels the task and drops its
23//!   future there; awaiting the handle then panics, and a
24//!   [`FallibleTask`] returns `None`.
25//!
26//! Two fixes the established crate has not released are part of this one:
27//! the poll's waker is the task's canonical waker (`will_wake` holds between
28//! it and its clones), and the state is read again after the poll, so the
29//! exchange after a poll that woke the task or cloned its waker does not
30//! start from a value it is sure to miss.
31
32mod header;
33mod join;
34#[cfg(all(test, loom))]
35mod loom_tests;
36pub(crate) mod names;
37mod raw;
38mod state;
39#[cfg(all(test, not(loom)))]
40mod tests;
41
42use core::future::Future;
43use core::mem::ManuallyDrop;
44use core::ptr::NonNull;
45use core::task::{RawWaker, Waker};
46
47use self::header::Header;
48pub use self::join::{AbortHandle, FallibleTask, Join, JoinError, Task};
49
50/// A task's id: unique among the tasks of the process, never reused.
51#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
52pub struct Id(pub(super) core::num::NonZeroU64);
53
54impl core::fmt::Display for Id {
55    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
56        self.0.fmt(f)
57    }
58}
59
60/// The id of the task being polled on this thread, `None` outside a task (in
61/// `block_on`'s own future, on a blocking-pool thread).
62pub(crate) fn current_id() -> Option<Id> {
63    crate::coop::current_task()
64}
65
66use self::raw::RawTask;
67
68/// How a task came to be scheduled.
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub(crate) struct ScheduleInfo {
71    /// The task woke itself during its own poll (a yield, an IO operation
72    /// that completed at once): a run queue gives other tasks a turn first.
73    pub(crate) woken_while_running: bool,
74}
75
76impl ScheduleInfo {
77    pub(crate) const fn new(woken_while_running: bool) -> Self {
78        Self {
79            woken_while_running,
80        }
81    }
82}
83
84/// Where a woken task goes.
85///
86/// # Safety
87///
88/// `schedule` is called with `self` borrowed from the task's own memory.
89/// Once it has handed the runnable to a place another thread takes it from,
90/// that thread may run the task to its end and free it, `self` included: an
91/// implementation then must not touch `self` again unless it took a
92/// reference to the task before the handoff (a [`Runnable::waker`]).
93pub(crate) unsafe trait Schedule {
94    fn schedule(&self, runnable: Runnable, info: ScheduleInfo);
95
96    /// Drops the task's future (`drop` does), where and as the schedule
97    /// wants it dropped.
98    fn drop_future(&self, drop: impl FnOnce()) {
99        drop();
100    }
101
102    /// Just before each poll of the task's future.
103    fn before_poll(&self, _id: Id) {}
104
105    /// Just after each poll of the task's future.
106    fn after_poll(&self, _id: Id) {}
107
108    /// The task's future was dropped: it completed or was cancelled.
109    fn terminated(&self, _id: Id) {}
110
111    /// A poll of the task's future panicked, its outcome now.
112    fn panicked(&self, _id: Id) {}
113}
114
115/// A scheduled task: running it polls the task's future once.
116///
117/// Dropping a runnable without running it cancels the task and drops its
118/// future on the dropping thread.
119pub(crate) struct Runnable {
120    ptr: NonNull<()>,
121}
122
123// SAFETY: a runnable is a reference to a task whose future, for a `spawn`ed
124// task, is `Send`; for a task of `spawn_unchecked` the caller promised that
125// the runnable is run and dropped only where that is sound.
126unsafe impl Send for Runnable {}
127// SAFETY: `&Runnable` only reads the header or clones a waker.
128unsafe impl Sync for Runnable {}
129
130impl Runnable {
131    /// # Safety
132    ///
133    /// `ptr` is a task pointer whose reference the runnable takes over.
134    pub(crate) unsafe fn from_raw(ptr: NonNull<()>) -> Self {
135        Self { ptr }
136    }
137
138    /// The task pointer, keeping the reference: [`from_raw`](Self::from_raw)
139    /// makes the runnable again.
140    pub(crate) fn into_raw(self) -> NonNull<()> {
141        ManuallyDrop::new(self).ptr
142    }
143
144    fn header(&self) -> &Header {
145        // SAFETY: the runnable's reference keeps the task alive.
146        unsafe { self.ptr.cast::<Header>().as_ref() }
147    }
148
149    /// The task's inbox link, keeping the reference:
150    /// [`from_link`](Self::from_link) makes the runnable again.
151    pub(crate) fn into_link(self) -> NonNull<crate::rt::inbox::Link> {
152        let header = self.into_raw().cast::<Header>();
153        // SAFETY: a field of the live header the reference keeps.
154        unsafe { NonNull::new_unchecked(&raw mut (*header.as_ptr()).link) }
155    }
156
157    /// # Safety
158    ///
159    /// `link` came from [`into_link`](Self::into_link), whose reference the
160    /// runnable takes over.
161    pub(crate) unsafe fn from_link(link: NonNull<crate::rt::inbox::Link>) -> Self {
162        let offset = core::mem::offset_of!(Header, link);
163        Self {
164            // SAFETY: the link is the `link` field of a task's header.
165            ptr: unsafe { NonNull::new_unchecked(link.as_ptr().cast::<u8>().sub(offset).cast()) },
166        }
167    }
168
169    /// Polls the task's future once. Returns true when the task woke itself
170    /// during the poll and was handed to its schedule again.
171    pub(crate) fn run(self) -> bool {
172        let ptr = self.into_raw().as_ptr();
173        // SAFETY: the runnable's reference goes to the run.
174        unsafe { ((*ptr.cast::<Header>()).vtable.run)(ptr) }
175    }
176
177    /// Hands the runnable back to the task's schedule.
178    pub(crate) fn schedule(self) {
179        let ptr = self.into_raw().as_ptr();
180        // SAFETY: the runnable's reference goes to the new runnable.
181        unsafe { ((*ptr.cast::<Header>()).vtable.schedule)(ptr, ScheduleInfo::new(false)) }
182    }
183
184    /// A waker of the task, which holds its own reference.
185    pub(crate) fn waker(&self) -> Waker {
186        let ptr = self.ptr.as_ptr().cast_const();
187        // SAFETY: the borrowed waker is never dropped, so it counts no
188        // reference; its clone counts its own.
189        let borrowed = ManuallyDrop::new(unsafe {
190            Waker::from_raw(RawWaker::new(ptr, &self.header().vtable.waker))
191        });
192        (*borrowed).clone()
193    }
194}
195
196impl Drop for Runnable {
197    fn drop(&mut self) {
198        use self::state::{AWAITER, CLOSED, COMPLETED, SCHEDULED};
199        let ptr = self.ptr.as_ptr().cast_const();
200        let header = self.header();
201        let mut state = header.state.load(crate::sync::internal::Ordering::Acquire);
202        loop {
203            if state & (COMPLETED | CLOSED) != 0 {
204                break;
205            }
206            match header.state.compare_exchange_weak(
207                state,
208                state | CLOSED,
209                crate::sync::internal::Ordering::AcqRel,
210                crate::sync::internal::Ordering::Acquire,
211            ) {
212                Ok(_) => break,
213                Err(actual) => state = actual,
214            }
215        }
216        // SAFETY: a runnable exists only for a task that has not completed,
217        // so its future is there, and the runnable owns it now.
218        unsafe { (header.vtable.drop_future)(ptr) };
219        let state = header
220            .state
221            .fetch_and(!SCHEDULED, crate::sync::internal::Ordering::AcqRel);
222        if state & AWAITER != 0 {
223            header.notify(None);
224        }
225        // SAFETY: the runnable's reference.
226        unsafe { (header.vtable.drop_ref)(ptr) };
227    }
228}
229
230impl core::fmt::Debug for Runnable {
231    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
232        f.debug_struct("Runnable").field("task", &self.ptr).finish()
233    }
234}
235
236/// Spawns `future` as a task that `schedule` places when it is woken.
237/// Returns the task's runnable, scheduled once and not yet handed to
238/// `schedule`, and its handle.
239#[inline(always)]
240pub(crate) fn spawn<F, S>(future: F, schedule: S) -> (Runnable, Task<F::Output>)
241where
242    F: Future + Send + 'static,
243    F::Output: Send + 'static,
244    S: Schedule + Send + Sync + 'static,
245{
246    // SAFETY: everything the task holds may cross threads and lives as long
247    // as it needs to.
248    unsafe { spawn_unchecked(future, schedule) }
249}
250
251/// [`spawn`] without the `Send` and `'static` bounds.
252///
253/// # Safety
254///
255/// The future is polled and dropped only on a thread where that is sound
256/// (its owner's, for a future that is not `Send`), everything it borrows
257/// outlives the task, and the schedule may be called from any thread that
258/// holds a waker.
259#[inline(always)]
260pub(crate) unsafe fn spawn_unchecked<F, S>(future: F, schedule: S) -> (Runnable, Task<F::Output>)
261where
262    F: Future,
263    S: Schedule,
264{
265    let ptr = RawTask::<F, S>::allocate(future, schedule);
266    // SAFETY: a new task holds the runnable's reference and the handle's flag.
267    unsafe { (Runnable::from_raw(ptr), Task::from_raw(ptr)) }
268}
269
270/// Runs `f`, aborting the process if it panics: what a destructor or a waker
271/// that panics inside a task transition gets, as unwinding there would leave
272/// the state word half done.
273#[inline]
274pub(crate) fn abort_on_panic<R>(f: impl FnOnce() -> R) -> R {
275    struct Abort;
276    impl Drop for Abort {
277        fn drop(&mut self) {
278            std::process::abort();
279        }
280    }
281    let abort = Abort;
282    let out = f();
283    core::mem::forget(abort);
284    out
285}