Skip to main content

pal_async/
task.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Task spawning support.
5
6// UNSAFETY: Managing information stored as pointers for debugging purposes.
7#![expect(unsafe_code)]
8
9use loan_cell::LoanCell;
10use parking_lot::Mutex;
11use slab::Slab;
12use std::fmt::Debug;
13use std::fmt::Display;
14use std::future::Future;
15use std::panic::Location;
16use std::pin::Pin;
17use std::sync::Arc;
18use std::sync::Weak;
19use std::sync::atomic::AtomicBool;
20use std::sync::atomic::AtomicU32;
21use std::sync::atomic::AtomicUsize;
22use std::sync::atomic::Ordering;
23
24/// A handle to a task.
25pub type Task<T> = async_task::Task<T, TaskMetadata>;
26
27/// A handle to a task that's ready to run.
28pub type Runnable = async_task::Runnable<TaskMetadata>;
29
30/// Metadata about a spawned task.
31///
32/// This can be accessed via [`Task::metadata()`], [`Runnable::metadata()`], or
33/// [`with_current_task_metadata()`].
34#[derive(Debug)]
35pub struct TaskMetadata {
36    name: Arc<str>,
37    location: &'static Location<'static>,
38    /// The ready/waiting/running/done state of the future itself. Tracked for
39    /// diagnostics purposes.
40    state: AtomicU32,
41    /// Whether the task has been dropped. This could be a high bit in `state`
42    /// or something, but keep this separate to make the codegen straightforward
43    /// for all state updates.
44    dropped: AtomicBool,
45    scheduler: Weak<dyn Schedule>,
46    id: AtomicUsize,
47    _no_pin: std::marker::PhantomPinned,
48}
49
50impl TaskMetadata {
51    const NO_ID: usize = !0;
52
53    #[track_caller]
54    fn new(name: Arc<str>) -> Self {
55        Self {
56            name,
57            location: Location::caller(),
58            state: AtomicU32::new(TASK_STATE_READY),
59            dropped: AtomicBool::new(false),
60            scheduler: Weak::<Scheduler>::new(),
61            id: AtomicUsize::new(Self::NO_ID),
62            _no_pin: std::marker::PhantomPinned,
63        }
64    }
65
66    fn register(self: Pin<&Self>) {
67        assert_eq!(self.id.load(Ordering::Relaxed), Self::NO_ID);
68        // Insert a pointer into the global task list. This is safe because this
69        // object is known to be pinned, so its storage will not be deallocated
70        // without calling `drop` (which will remove it from the list).
71        let id = TASK_LIST
72            .slab
73            .lock()
74            .insert(TaskMetadataPtr(self.get_ref()));
75        self.id.store(id, Ordering::Relaxed);
76    }
77
78    fn pend(&self) {
79        self.state.store(TASK_STATE_WAITING, Ordering::Relaxed);
80    }
81
82    fn done(&self) {
83        self.state.store(TASK_STATE_DONE, Ordering::Relaxed);
84    }
85
86    fn run(&self) {
87        self.state.store(TASK_STATE_RUNNING, Ordering::Relaxed);
88    }
89
90    /// The name of the spawned task.
91    pub fn name(&self) -> &Arc<str> {
92        &self.name
93    }
94
95    /// The location where the task was spawned.
96    pub fn location(&self) -> &'static Location<'static> {
97        self.location
98    }
99
100    /// The current state of the task.
101    fn state(&self) -> TaskState {
102        let state = self.state.load(Ordering::Relaxed);
103        if self.dropped.load(Ordering::Relaxed) {
104            if state == TASK_STATE_DONE {
105                TaskState::Complete
106            } else {
107                TaskState::Cancelled
108            }
109        } else {
110            match self.state.load(Ordering::Relaxed) {
111                TASK_STATE_READY => TaskState::Ready,
112                TASK_STATE_WAITING => TaskState::Waiting,
113                TASK_STATE_RUNNING => TaskState::Running,
114                TASK_STATE_DONE => TaskState::Complete,
115                _ => unreachable!(),
116            }
117        }
118    }
119}
120
121impl Drop for TaskMetadata {
122    fn drop(&mut self) {
123        let id = self.id.load(Ordering::Relaxed);
124        if id != Self::NO_ID {
125            let _task = TASK_LIST.slab.lock().remove(id);
126        }
127    }
128}
129
130#[derive(Debug, Copy, Clone)]
131struct TaskMetadataPtr(*const TaskMetadata);
132
133// Assert `TaskMetadata` is `Send` and `Sync`.
134const _: () = {
135    const fn assert_send_sync<T: Send + Sync>() {}
136    assert_send_sync::<TaskMetadata>();
137};
138
139// SAFETY: `TaskMetadata` can be safely shared between threads (asserted above).
140unsafe impl Send for TaskMetadataPtr {}
141// SAFETY: `TaskMetadata` can be safely shared between threads (asserted above).
142unsafe impl Sync for TaskMetadataPtr {}
143
144/// A queue of tasks that will be run on a single thread.
145#[derive(Debug)]
146pub struct TaskQueue {
147    tasks: async_channel::Receiver<Runnable>,
148}
149
150/// A task scheduler for a [`TaskQueue`].
151#[derive(Debug)]
152pub struct Scheduler {
153    send: async_channel::Sender<Runnable>,
154    name: Mutex<Arc<str>>,
155}
156
157impl Scheduler {
158    /// Updates the name of the scheduler.
159    pub fn set_name(&self, name: impl Into<Arc<str>>) {
160        *self.name.lock() = name.into();
161    }
162}
163
164impl Schedule for Scheduler {
165    fn schedule(&self, runnable: Runnable) {
166        let _ = self.send.try_send(runnable);
167    }
168
169    fn name(&self) -> Arc<str> {
170        self.name.lock().clone()
171    }
172}
173
174/// Creates a new task queue and associated scheduler.
175pub fn task_queue(name: impl Into<Arc<str>>) -> (TaskQueue, Scheduler) {
176    let (send, recv) = async_channel::unbounded();
177    (
178        TaskQueue { tasks: recv },
179        Scheduler {
180            send,
181            name: Mutex::new(name.into()),
182        },
183    )
184}
185
186impl TaskQueue {
187    /// Runs tasks on the queue.
188    ///
189    /// Returns when the associated scheduler has been dropped.
190    pub async fn run(&mut self) {
191        while let Ok(task) = self.tasks.recv().await {
192            // Hold the scheduler for the whole of `run`. When a task completes
193            // or is cancelled, `run` drops its future, then a detached task's
194            // output, then wakes the task's awaiter. Each of those can run code
195            // that looks the scheduler up through a weak reference, and the
196            // future may have held the last strong one.
197            let _scheduler = task.metadata().scheduler.upgrade();
198            task.run();
199        }
200    }
201}
202
203/// Trait for scheduling a task on an executor.
204pub trait Schedule: Send + Sync {
205    /// Schedules a task to run.
206    fn schedule(&self, runnable: Runnable);
207
208    /// Gets the executor name.
209    fn name(&self) -> Arc<str>;
210}
211
212struct TaskFuture<'a, Fut> {
213    metadata: &'a TaskMetadata,
214    _scheduler: Arc<dyn Schedule>, // Keep the scheduler alive until the future is dropped.
215    future: Fut,
216}
217
218impl<'a, Fut: Future> TaskFuture<'a, Fut> {
219    fn new(metadata: Pin<&'a TaskMetadata>, scheduler: Arc<dyn Schedule>, future: Fut) -> Self {
220        metadata.register();
221        Self {
222            metadata: metadata.get_ref(),
223            _scheduler: scheduler,
224            future,
225        }
226    }
227
228    /// Wrapper around `new` for passing to [`async_task::Builder::spawn`].
229    ///
230    /// # Safety
231    /// The caller guarantees that the incoming `metadata` pointer is pinned and
232    /// that the future will not be used beyond the lifetime of `metadata`
233    /// (despite having a static lifetime). This is guaranteed by
234    /// [`async_task::Builder::spawn`] API, which unfortunately is missing the
235    /// explicit `Pin` and the appropriate lifetime on the future.
236    ///
237    /// See <https://github.com/smol-rs/async-task/issues/76>.
238    unsafe fn new_for_async_task(
239        metadata: &'a TaskMetadata,
240        scheduler: Arc<dyn Schedule>,
241        future: Fut,
242    ) -> TaskFuture<'static, Fut> {
243        // SAFETY: `metadata` is pinned by `async_task::Builder::spawn`, and the
244        // caller guarantees this function will only be used in that context.
245        let metadata = unsafe { Pin::new_unchecked(metadata) };
246        let this = Self::new(metadata, scheduler, future);
247        // Transmute to static lifetime, as required by
248        // `async_task::Builder::spawn`.
249        //
250        // SAFETY: the caller guarantees this future will only be passed to
251        // `spawn`, which will guarantee the metadata is not dropped before the
252        // future is dropped.
253        unsafe { std::mem::transmute::<TaskFuture<'a, Fut>, TaskFuture<'static, Fut>>(this) }
254    }
255}
256
257impl<Fut: Future> Future for TaskFuture<'_, Fut> {
258    type Output = Fut::Output;
259
260    fn poll(
261        self: Pin<&mut Self>,
262        cx: &mut std::task::Context<'_>,
263    ) -> std::task::Poll<Self::Output> {
264        // SAFETY: projecting this type for pinned access to the future. The
265        // future will not be moved or dropped.
266        let this = unsafe { self.get_unchecked_mut() };
267        this.metadata.run();
268        // SAFETY: the future is pinned since `self` is pinned.
269        let future = unsafe { Pin::new_unchecked(&mut this.future) };
270        let r = CURRENT_TASK.with(|task| task.lend(this.metadata, || future.poll(cx)));
271        if r.is_pending() {
272            this.metadata.pend();
273        } else {
274            this.metadata.done();
275        }
276        r
277    }
278}
279
280impl<Fut> Drop for TaskFuture<'_, Fut> {
281    fn drop(&mut self) {
282        self.metadata.dropped.store(true, Ordering::Relaxed);
283    }
284}
285
286fn schedule(runnable: Runnable) {
287    let metadata = runnable.metadata();
288    metadata.state.store(TASK_STATE_READY, Ordering::Relaxed);
289    if let Some(scheduler) = metadata.scheduler.upgrade() {
290        scheduler.schedule(runnable);
291    }
292}
293
294/// Trait for spawning a task on an executor.
295pub trait Spawn: Send + Sync {
296    /// Gets a scheduler for a new task.
297    fn scheduler(&self, metadata: &TaskMetadata) -> Arc<dyn Schedule>;
298
299    /// Spawns a task.
300    #[track_caller]
301    fn spawn<T: 'static + Send>(
302        &self,
303        name: impl Into<Arc<str>>,
304        fut: impl Future<Output = T> + Send + 'static,
305    ) -> Task<T>
306    where
307        Self: Sized,
308    {
309        let mut metadata = TaskMetadata::new(name.into());
310        let scheduler = self.scheduler(&metadata);
311        metadata.scheduler = Arc::downgrade(&scheduler);
312        let (runnable, task) = async_task::Builder::new().metadata(metadata).spawn(
313            |metadata| {
314                // SAFETY: calling from the async_task::Builder::spawn closure, as required.
315                unsafe { TaskFuture::new_for_async_task(metadata, scheduler, fut) }
316            },
317            schedule,
318        );
319        runnable.schedule();
320        task
321    }
322}
323
324/// Trait for spawning a non-`Send` task on an executor.
325pub trait SpawnLocal {
326    /// Gets a scheduler for a new task.
327    fn scheduler_local(&self, metadata: &TaskMetadata) -> Arc<dyn Schedule>;
328
329    /// Spawns a task.
330    #[track_caller]
331    fn spawn_local<T: 'static>(
332        &self,
333        name: impl Into<Arc<str>>,
334        fut: impl Future<Output = T> + 'static,
335    ) -> Task<T>
336    where
337        Self: Sized,
338    {
339        let mut metadata = TaskMetadata::new(name.into());
340        let scheduler = self.scheduler_local(&metadata);
341        metadata.scheduler = Arc::downgrade(&scheduler);
342        let (runnable, task) = async_task::Builder::new().metadata(metadata).spawn_local(
343            |metadata| {
344                // SAFETY: calling from the async_task::Builder::spawn closure, as required.
345                unsafe { TaskFuture::new_for_async_task(metadata, scheduler, fut) }
346            },
347            schedule,
348        );
349        runnable.schedule();
350        task
351    }
352}
353
354thread_local! {
355    static CURRENT_TASK: LoanCell<TaskMetadata> = const { LoanCell::new() };
356}
357
358/// Calls `f` with the current task metadata, if there is a current task.
359pub fn with_current_task_metadata<F: FnOnce(Option<&TaskMetadata>) -> R, R>(f: F) -> R {
360    CURRENT_TASK.with(|task| task.borrow(f))
361}
362
363impl<T: ?Sized + Spawn> Spawn for &'_ T {
364    fn scheduler(&self, metadata: &TaskMetadata) -> Arc<dyn Schedule> {
365        (*self).scheduler(metadata)
366    }
367}
368
369impl<T: ?Sized + Spawn> Spawn for Box<T> {
370    fn scheduler(&self, metadata: &TaskMetadata) -> Arc<dyn Schedule> {
371        self.as_ref().scheduler(metadata)
372    }
373}
374
375impl<T: ?Sized + Spawn> Spawn for Arc<T> {
376    fn scheduler(&self, metadata: &TaskMetadata) -> Arc<dyn Schedule> {
377        self.as_ref().scheduler(metadata)
378    }
379}
380
381impl<T: ?Sized + SpawnLocal> SpawnLocal for &'_ T {
382    fn scheduler_local(&self, metadata: &TaskMetadata) -> Arc<dyn Schedule> {
383        (*self).scheduler_local(metadata)
384    }
385}
386
387impl<T: ?Sized + SpawnLocal> SpawnLocal for Box<T> {
388    fn scheduler_local(&self, metadata: &TaskMetadata) -> Arc<dyn Schedule> {
389        self.as_ref().scheduler_local(metadata)
390    }
391}
392
393impl<T: ?Sized + SpawnLocal> SpawnLocal for Arc<T> {
394    fn scheduler_local(&self, metadata: &TaskMetadata) -> Arc<dyn Schedule> {
395        self.as_ref().scheduler_local(metadata)
396    }
397}
398
399const TASK_STATE_READY: u32 = 0;
400const TASK_STATE_WAITING: u32 = 1;
401const TASK_STATE_RUNNING: u32 = 2;
402const TASK_STATE_DONE: u32 = 3;
403
404/// The state of a task.
405#[derive(Debug, Copy, Clone)]
406#[repr(u64)]
407pub enum TaskState {
408    /// The task is ready to run.
409    Ready,
410    /// The task is waiting on some condition.
411    Waiting,
412    /// The task is running on an executor.
413    Running,
414    /// The task has completed.
415    Complete,
416    /// The task was cancelled before it completed.
417    Cancelled,
418}
419
420impl Display for TaskState {
421    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
422        let s = match self {
423            TaskState::Ready => "ready",
424            TaskState::Waiting => "waiting",
425            TaskState::Running => "running",
426            TaskState::Complete => "complete",
427            TaskState::Cancelled => "cancelled",
428        };
429        f.pad(s)
430    }
431}
432
433/// A list of tasks.
434pub struct TaskList {
435    slab: Mutex<Slab<TaskMetadataPtr>>,
436}
437
438static TASK_LIST: TaskList = TaskList::new();
439
440impl TaskList {
441    const fn new() -> Self {
442        Self {
443            slab: Mutex::new(Slab::new()),
444        }
445    }
446
447    /// Gets the global task list.
448    pub fn global() -> &'static Self {
449        &TASK_LIST
450    }
451
452    /// Gets a snapshot of the current tasks.
453    pub fn tasks(&self) -> Vec<TaskData> {
454        let tasks = self.slab.lock();
455        tasks
456            .iter()
457            .map(|(id, task)| {
458                // SAFETY: the pointer is guaranteed to be valid while the lock
459                // is held, since it was published via
460                // [`TaskMetadata::register`] and will be unpublished by
461                // [`TaskMetadata::drop`].
462                let task = unsafe { &*task.0 };
463                let scheduler = task.scheduler.upgrade().map(|s| s.name());
464                TaskData {
465                    id,
466                    name: task.name.clone(),
467                    location: task.location,
468                    state: task.state(),
469                    executor: scheduler,
470                }
471            })
472            .collect()
473    }
474}
475
476/// Information about a task.
477#[derive(Debug)]
478pub struct TaskData {
479    id: usize,
480    name: Arc<str>,
481    location: &'static Location<'static>,
482    state: TaskState,
483    executor: Option<Arc<str>>,
484}
485
486impl TaskData {
487    /// The task's unique ID.
488    ///
489    /// This ID may be reused.
490    pub fn id(&self) -> usize {
491        self.id
492    }
493
494    /// The executor's name.
495    pub fn executor(&self) -> Option<&str> {
496        self.executor.as_deref()
497    }
498
499    /// The task's metadata.
500    pub fn name(&self) -> &str {
501        &self.name
502    }
503
504    /// The location where the task was spawned.
505    pub fn location(&self) -> &'static Location<'static> {
506        self.location
507    }
508
509    /// The state of the task.
510    pub fn state(&self) -> TaskState {
511        self.state
512    }
513}