Skip to main content

tokio/runtime/task/
core.rs

1//! Core task module.
2//!
3//! # Safety
4//!
5//! The functions in this module are private to the `task` module. All of them
6//! should be considered `unsafe` to use, but are not marked as such since it
7//! would be too noisy.
8//!
9//! Make sure to consult the relevant safety section of each function before
10//! use.
11
12// It doesn't make sense to enforce `unsafe_op_in_unsafe_fn` for this module because
13//
14// * This module is doing the low-level task management that requires tons of unsafe
15//   operations.
16// * Excessive `unsafe {}` blocks hurt readability significantly.
17// TODO: replace with `#[expect(unsafe_op_in_unsafe_fn)]` after bumpping
18// the MSRV to 1.81.0.
19#![allow(unsafe_op_in_unsafe_fn)]
20
21use crate::future::Future;
22use crate::loom::cell::UnsafeCell;
23use crate::runtime::context;
24use crate::runtime::metrics::ScheduleLatencyInstant;
25use crate::runtime::task::raw::{self, Vtable};
26use crate::runtime::task::state::State;
27use crate::runtime::task::{Id, Schedule, TaskHarnessScheduleHooks};
28use crate::util::linked_list;
29
30use std::num::NonZeroU64;
31#[cfg(tokio_unstable)]
32use std::panic::Location;
33use std::pin::Pin;
34use std::ptr::NonNull;
35use std::task::{Context, Poll, Waker};
36
37/// The task cell. Contains the components of the task.
38///
39/// It is critical for `Header` to be the first field as the task structure will
40/// be referenced by both *mut Cell and *mut Header.
41///
42/// Any changes to the layout of this struct _must_ also be reflected in the
43/// `const` fns in raw.rs.
44///
45// # This struct should be cache padded to avoid false sharing. The cache padding rules are copied
46// from crossbeam-utils/src/cache_padded.rs
47//
48// Starting from Intel's Sandy Bridge, spatial prefetcher is now pulling pairs of 64-byte cache
49// lines at a time, so we have to align to 128 bytes rather than 64.
50//
51// Sources:
52// - https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf
53// - https://github.com/facebook/folly/blob/1b5288e6eea6df074758f877c849b6e73bbb9fbb/folly/lang/Align.h#L107
54//
55// ARM's big.LITTLE architecture has asymmetric cores and "big" cores have 128-byte cache line size.
56//
57// Sources:
58// - https://www.mono-project.com/news/2016/09/12/arm64-icache/
59//
60// powerpc64 has 128-byte cache line size.
61//
62// Sources:
63// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_ppc64x.go#L9
64#[cfg_attr(
65    any(
66        target_arch = "x86_64",
67        target_arch = "aarch64",
68        target_arch = "powerpc64",
69    ),
70    repr(align(128))
71)]
72// arm, mips, mips64, sparc, and hexagon have 32-byte cache line size.
73//
74// Sources:
75// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_arm.go#L7
76// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_mips.go#L7
77// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_mipsle.go#L7
78// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_mips64x.go#L9
79// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/sparc/include/asm/cache.h#L17
80// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/hexagon/include/asm/cache.h#L12
81#[cfg_attr(
82    any(
83        target_arch = "arm",
84        target_arch = "mips",
85        target_arch = "mips64",
86        target_arch = "sparc",
87        target_arch = "hexagon",
88    ),
89    repr(align(32))
90)]
91// m68k has 16-byte cache line size.
92//
93// Sources:
94// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/m68k/include/asm/cache.h#L9
95#[cfg_attr(target_arch = "m68k", repr(align(16)))]
96// s390x has 256-byte cache line size.
97//
98// Sources:
99// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_s390x.go#L7
100// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/s390/include/asm/cache.h#L13
101#[cfg_attr(target_arch = "s390x", repr(align(256)))]
102// x86, riscv, wasm, and sparc64 have 64-byte cache line size.
103//
104// Sources:
105// - https://github.com/golang/go/blob/dda2991c2ea0c5914714469c4defc2562a907230/src/internal/cpu/cpu_x86.go#L9
106// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_wasm.go#L7
107// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/sparc/include/asm/cache.h#L19
108// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/riscv/include/asm/cache.h#L10
109//
110// All others are assumed to have 64-byte cache line size.
111#[cfg_attr(
112    not(any(
113        target_arch = "x86_64",
114        target_arch = "aarch64",
115        target_arch = "powerpc64",
116        target_arch = "arm",
117        target_arch = "mips",
118        target_arch = "mips64",
119        target_arch = "sparc",
120        target_arch = "hexagon",
121        target_arch = "m68k",
122        target_arch = "s390x",
123    )),
124    repr(align(64))
125)]
126#[repr(C)]
127pub(super) struct Cell<T: Future, S> {
128    /// Hot task state data
129    pub(super) header: Header,
130
131    /// Either the future or output, depending on the execution stage.
132    pub(super) core: Core<T, S>,
133
134    /// Cold data
135    pub(super) trailer: Trailer,
136}
137
138pub(super) struct CoreStage<T: Future> {
139    stage: UnsafeCell<Stage<T>>,
140}
141
142/// The core of the task.
143///
144/// Holds the future or output, depending on the stage of execution.
145///
146/// Any changes to the layout of this struct _must_ also be reflected in the
147/// `const` fns in raw.rs.
148#[repr(C)]
149pub(super) struct Core<T: Future, S> {
150    /// Scheduler used to drive this future.
151    pub(super) scheduler: S,
152
153    /// The task's ID, used for populating `JoinError`s.
154    pub(super) task_id: Id,
155
156    /// The source code location where the task was spawned.
157    ///
158    /// This is used for populating the `TaskMeta` passed to the task runtime
159    /// hooks.
160    #[cfg(tokio_unstable)]
161    pub(super) spawned_at: &'static Location<'static>,
162
163    /// Either the future or the output.
164    pub(super) stage: CoreStage<T>,
165}
166
167/// Crate public as this is also needed by the pool.
168#[repr(C)]
169pub(crate) struct Header {
170    /// Task state.
171    pub(super) state: State,
172
173    /// Pointer to next task, used with the injection queue.
174    pub(super) queue_next: UnsafeCell<Option<NonNull<Header>>>,
175
176    /// Table of function pointers for executing actions on the task.
177    pub(super) vtable: &'static Vtable,
178
179    /// This integer contains the id of the `OwnedTasks` or `LocalOwnedTasks`
180    /// that this task is stored in. If the task is not in any list, should be
181    /// the id of the list that it was previously in, or `None` if it has never
182    /// been in any list.
183    ///
184    /// Once a task has been bound to a list, it can never be bound to another
185    /// list, even if removed from the first list.
186    ///
187    /// The id is not unset when removed from a list because we want to be able
188    /// to read the id without synchronization, even if it is concurrently being
189    /// removed from the list.
190    pub(super) owner_id: UnsafeCell<Option<NonZeroU64>>,
191
192    /// The tracing ID for this instrumented task.
193    #[cfg(all(tokio_unstable, feature = "tracing"))]
194    pub(super) tracing_id: Option<tracing::Id>,
195
196    /// The last time this task was scheduled. Used to measure schedule latency.
197    pub(super) scheduled_at: UnsafeCell<ScheduleLatencyInstant>,
198}
199
200unsafe impl Send for Header {}
201unsafe impl Sync for Header {}
202
203/// Cold data is stored after the future. Data is considered cold if it is only
204/// used during creation or shutdown of the task.
205pub(super) struct Trailer {
206    /// Pointers for the linked list in the `OwnedTasks` that owns this task.
207    pub(super) owned: linked_list::Pointers<Header>,
208    /// Consumer task waiting on completion of this task.
209    pub(super) waker: UnsafeCell<Option<Waker>>,
210    /// Optional hooks needed in the harness.
211    #[cfg_attr(not(tokio_unstable), allow(dead_code))] //TODO: remove when hooks are stabilized
212    pub(super) hooks: TaskHarnessScheduleHooks,
213}
214
215generate_addr_of_methods! {
216    impl<> Trailer {
217        pub(super) unsafe fn addr_of_owned(self: NonNull<Self>) -> NonNull<linked_list::Pointers<Header>> {
218            &self.owned
219        }
220    }
221}
222
223/// Either the future or the output.
224#[repr(C)] // https://github.com/rust-lang/miri/issues/3780
225pub(super) enum Stage<T: Future> {
226    Running(T),
227    Finished(super::Result<T::Output>),
228    Consumed,
229}
230
231impl<T: Future, S: Schedule> Cell<T, S> {
232    /// Allocates a new task cell, containing the header, trailer, and core
233    /// structures.
234    pub(super) fn new(
235        future: T,
236        scheduler: S,
237        state: State,
238        task_id: Id,
239        #[cfg(tokio_unstable)] spawned_at: &'static Location<'static>,
240    ) -> Box<Cell<T, S>> {
241        // Separated into a non-generic function to reduce LLVM codegen
242        fn new_header(
243            state: State,
244            vtable: &'static Vtable,
245            #[cfg(all(tokio_unstable, feature = "tracing"))] tracing_id: Option<tracing::Id>,
246        ) -> Header {
247            Header {
248                state,
249                queue_next: UnsafeCell::new(None),
250                vtable,
251                owner_id: UnsafeCell::new(None),
252                #[cfg(all(tokio_unstable, feature = "tracing"))]
253                tracing_id,
254                scheduled_at: UnsafeCell::new(ScheduleLatencyInstant::new(None)),
255            }
256        }
257
258        #[cfg(all(tokio_unstable, feature = "tracing"))]
259        let tracing_id = future.id();
260        let vtable = raw::vtable::<T, S>();
261        let result = Box::new(Cell {
262            trailer: Trailer::new(scheduler.hooks()),
263            header: new_header(
264                state,
265                vtable,
266                #[cfg(all(tokio_unstable, feature = "tracing"))]
267                tracing_id,
268            ),
269            core: Core {
270                scheduler,
271                stage: CoreStage {
272                    stage: UnsafeCell::new(Stage::Running(future)),
273                },
274                task_id,
275                #[cfg(tokio_unstable)]
276                spawned_at,
277            },
278        });
279
280        #[cfg(debug_assertions)]
281        {
282            // Using a separate function for this code avoids instantiating it separately for every `T`.
283            unsafe fn check<S>(
284                header: &Header,
285                trailer: &Trailer,
286                scheduler: &S,
287                task_id: &Id,
288                #[cfg(tokio_unstable)] spawn_location: &&'static Location<'static>,
289            ) {
290                let trailer_addr = trailer as *const Trailer as usize;
291                let trailer_ptr = unsafe { Header::get_trailer(NonNull::from(header)) };
292                assert_eq!(trailer_addr, trailer_ptr.as_ptr() as usize);
293
294                let scheduler_addr = scheduler as *const S as usize;
295                let scheduler_ptr = unsafe { Header::get_scheduler::<S>(NonNull::from(header)) };
296                assert_eq!(scheduler_addr, scheduler_ptr.as_ptr() as usize);
297
298                let id_addr = task_id as *const Id as usize;
299                let id_ptr = unsafe { Header::get_id_ptr(NonNull::from(header)) };
300                assert_eq!(id_addr, id_ptr.as_ptr() as usize);
301
302                #[cfg(tokio_unstable)]
303                {
304                    let spawn_location_addr =
305                        spawn_location as *const &'static Location<'static> as usize;
306                    let spawn_location_ptr =
307                        unsafe { Header::get_spawn_location_ptr(NonNull::from(header)) };
308                    assert_eq!(spawn_location_addr, spawn_location_ptr.as_ptr() as usize);
309                }
310            }
311            unsafe {
312                check(
313                    &result.header,
314                    &result.trailer,
315                    &result.core.scheduler,
316                    &result.core.task_id,
317                    #[cfg(tokio_unstable)]
318                    &result.core.spawned_at,
319                );
320            }
321        }
322
323        result
324    }
325}
326
327impl<T: Future> CoreStage<T> {
328    pub(super) fn with_mut<R>(&self, f: impl FnOnce(*mut Stage<T>) -> R) -> R {
329        self.stage.with_mut(f)
330    }
331}
332
333/// Set and clear the task id in the context when the future is executed or
334/// dropped, or when the output produced by the future is dropped.
335pub(crate) struct TaskIdGuard {
336    parent_task_id: Option<Id>,
337}
338
339impl TaskIdGuard {
340    fn enter(id: Id) -> Self {
341        TaskIdGuard {
342            parent_task_id: context::set_current_task_id(Some(id)),
343        }
344    }
345}
346
347impl Drop for TaskIdGuard {
348    fn drop(&mut self) {
349        context::set_current_task_id(self.parent_task_id);
350    }
351}
352
353impl<T: Future, S: Schedule> Core<T, S> {
354    /// Polls the future.
355    ///
356    /// # Safety
357    ///
358    /// The caller must ensure it is safe to mutate the `state` field. This
359    /// requires ensuring mutual exclusion between any concurrent thread that
360    /// might modify the future or output field.
361    ///
362    /// The mutual exclusion is implemented by `Harness` and the `Lifecycle`
363    /// component of the task state.
364    ///
365    /// `self` must also be pinned. This is handled by storing the task on the
366    /// heap.
367    pub(super) fn poll(&self, mut cx: Context<'_>) -> Poll<T::Output> {
368        let res = {
369            self.stage.stage.with_mut(|ptr| {
370                // Safety: The caller ensures mutual exclusion to the field.
371                let future = match unsafe { &mut *ptr } {
372                    Stage::Running(future) => future,
373                    _ => unreachable!("unexpected stage"),
374                };
375
376                // Safety: The caller ensures the future is pinned.
377                let future = unsafe { Pin::new_unchecked(future) };
378
379                let _guard = TaskIdGuard::enter(self.task_id);
380                future.poll(&mut cx)
381            })
382        };
383
384        if res.is_ready() {
385            self.drop_future_or_output();
386        }
387
388        res
389    }
390
391    /// Drops the future.
392    ///
393    /// # Safety
394    ///
395    /// The caller must ensure it is safe to mutate the `stage` field.
396    pub(super) fn drop_future_or_output(&self) {
397        // Safety: the caller ensures mutual exclusion to the field.
398        unsafe {
399            self.set_stage(Stage::Consumed);
400        }
401    }
402
403    /// Stores the task output.
404    ///
405    /// # Safety
406    ///
407    /// The caller must ensure it is safe to mutate the `stage` field.
408    pub(super) fn store_output(&self, output: super::Result<T::Output>) {
409        // Safety: the caller ensures mutual exclusion to the field.
410        unsafe {
411            self.set_stage(Stage::Finished(output));
412        }
413    }
414
415    /// Takes the task output.
416    ///
417    /// # Safety
418    ///
419    /// The caller must ensure it is safe to mutate the `stage` field.
420    pub(super) fn take_output(&self) -> super::Result<T::Output> {
421        use std::mem;
422
423        self.stage.stage.with_mut(|ptr| {
424            // Safety:: the caller ensures mutual exclusion to the field.
425            match mem::replace(unsafe { &mut *ptr }, Stage::Consumed) {
426                Stage::Finished(output) => output,
427                _ => panic!("JoinHandle polled after completion"),
428            }
429        })
430    }
431
432    unsafe fn set_stage(&self, stage: Stage<T>) {
433        let _guard = TaskIdGuard::enter(self.task_id);
434        self.stage.stage.with_mut(|ptr| *ptr = stage);
435    }
436}
437
438impl Header {
439    pub(super) unsafe fn set_next(&self, next: Option<NonNull<Header>>) {
440        self.queue_next.with_mut(|ptr| *ptr = next);
441    }
442
443    // safety: The caller must guarantee exclusive access to this field, and
444    // must ensure that the id is either `None` or the id of the OwnedTasks
445    // containing this task.
446    pub(super) unsafe fn set_owner_id(&self, owner: NonZeroU64) {
447        self.owner_id.with_mut(|ptr| *ptr = Some(owner));
448    }
449
450    pub(super) fn get_owner_id(&self) -> Option<NonZeroU64> {
451        // safety: If there are concurrent writes, then that write has violated
452        // the safety requirements on `set_owner_id`.
453        unsafe { self.owner_id.with(|ptr| *ptr) }
454    }
455
456    /// Gets a pointer to the `Trailer` of the task containing this `Header`.
457    ///
458    /// # Safety
459    ///
460    /// The provided raw pointer must point at the header of a task.
461    pub(super) unsafe fn get_trailer(me: NonNull<Header>) -> NonNull<Trailer> {
462        let offset = me.as_ref().vtable.trailer_offset;
463        let trailer = me.as_ptr().cast::<u8>().add(offset).cast::<Trailer>();
464        NonNull::new_unchecked(trailer)
465    }
466
467    /// Gets a pointer to the scheduler of the task containing this `Header`.
468    ///
469    /// # Safety
470    ///
471    /// The provided raw pointer must point at the header of a task.
472    ///
473    /// The generic type S must be set to the correct scheduler type for this
474    /// task.
475    pub(super) unsafe fn get_scheduler<S>(me: NonNull<Header>) -> NonNull<S> {
476        let offset = me.as_ref().vtable.scheduler_offset;
477        let scheduler = me.as_ptr().cast::<u8>().add(offset).cast::<S>();
478        NonNull::new_unchecked(scheduler)
479    }
480
481    /// Gets a pointer to the id of the task containing this `Header`.
482    ///
483    /// # Safety
484    ///
485    /// The provided raw pointer must point at the header of a task.
486    pub(super) unsafe fn get_id_ptr(me: NonNull<Header>) -> NonNull<Id> {
487        let offset = me.as_ref().vtable.id_offset;
488        let id = me.as_ptr().cast::<u8>().add(offset).cast::<Id>();
489        NonNull::new_unchecked(id)
490    }
491
492    /// Gets the id of the task containing this `Header`.
493    ///
494    /// # Safety
495    ///
496    /// The provided raw pointer must point at the header of a task.
497    pub(super) unsafe fn get_id(me: NonNull<Header>) -> Id {
498        let ptr = Header::get_id_ptr(me).as_ptr();
499        *ptr
500    }
501
502    /// Gets a pointer to the source code location where the task containing
503    /// this `Header` was spawned.
504    ///
505    /// # Safety
506    ///
507    /// The provided raw pointer must point at the header of a task.
508    #[cfg(tokio_unstable)]
509    pub(super) unsafe fn get_spawn_location_ptr(
510        me: NonNull<Header>,
511    ) -> NonNull<&'static Location<'static>> {
512        let offset = me.as_ref().vtable.spawn_location_offset;
513        let spawned_at = me
514            .as_ptr()
515            .cast::<u8>()
516            .add(offset)
517            .cast::<&'static Location<'static>>();
518        NonNull::new_unchecked(spawned_at)
519    }
520
521    /// Gets the source code location where the task containing
522    /// this `Header` was spawned
523    ///
524    /// # Safety
525    ///
526    /// The provided raw pointer must point at the header of a task.
527    #[cfg(tokio_unstable)]
528    pub(super) unsafe fn get_spawn_location(me: NonNull<Header>) -> &'static Location<'static> {
529        let ptr = Header::get_spawn_location_ptr(me).as_ptr();
530        *ptr
531    }
532
533    /// Gets the tracing id of the task containing this `Header`.
534    ///
535    /// # Safety
536    ///
537    /// The provided raw pointer must point at the header of a task.
538    #[cfg(all(tokio_unstable, feature = "tracing"))]
539    pub(super) unsafe fn get_tracing_id(me: &NonNull<Header>) -> Option<&tracing::Id> {
540        me.as_ref().tracing_id.as_ref()
541    }
542
543    /// Updates the last time this task was scheduled. Used to calculate
544    /// the time elapsed between task scheduling and polling.
545    ///
546    /// # Safety
547    ///
548    /// The caller must guarantee exclusive access to this field.
549    pub(super) unsafe fn set_scheduled_at(&self, scheduled_at: ScheduleLatencyInstant) {
550        self.scheduled_at.with_mut(|ptr| *ptr = scheduled_at);
551    }
552
553    /// Gets the last time this task was scheduled.
554    pub(super) fn get_scheduled_at(&self) -> ScheduleLatencyInstant {
555        // Safety: If there are concurrent writes, then that write has violated
556        // the safety requirements on `set_scheduled_at`.
557        unsafe { self.scheduled_at.with(|ptr| *ptr) }
558    }
559}
560
561impl Trailer {
562    fn new(hooks: TaskHarnessScheduleHooks) -> Self {
563        Trailer {
564            waker: UnsafeCell::new(None),
565            owned: linked_list::Pointers::new(),
566            hooks,
567        }
568    }
569
570    pub(super) unsafe fn set_waker(&self, waker: Option<Waker>) {
571        self.waker.with_mut(|ptr| {
572            *ptr = waker;
573        });
574    }
575
576    pub(super) unsafe fn will_wake(&self, waker: &Waker) -> bool {
577        self.waker
578            .with(|ptr| (*ptr).as_ref().unwrap().will_wake(waker))
579    }
580
581    pub(super) fn wake_join(&self) {
582        self.waker.with(|ptr| match unsafe { &*ptr } {
583            Some(waker) => waker.wake_by_ref(),
584            None => panic!("waker missing"),
585        });
586    }
587}
588
589#[test]
590#[cfg(not(loom))]
591fn header_lte_cache_line() {
592    assert!(std::mem::size_of::<Header>() <= 8 * std::mem::size_of::<*const ()>());
593}