Skip to main content

tokio/runtime/task/
raw.rs

1// It doesn't make sense to enforce `unsafe_op_in_unsafe_fn` for this module because
2//
3// * This module is doing the low-level task management that requires tons of unsafe
4//   operations.
5// * Excessive `unsafe {}` blocks hurt readability significantly.
6// TODO: replace with `#[expect(unsafe_op_in_unsafe_fn)]` after bumpping
7// the MSRV to 1.81.0.
8#![allow(unsafe_op_in_unsafe_fn)]
9
10use crate::future::Future;
11use crate::runtime::task::core::{Core, Trailer};
12use crate::runtime::task::{Cell, Harness, Header, Id, Schedule, State};
13#[cfg(tokio_unstable)]
14use std::panic::Location;
15use std::ptr::NonNull;
16use std::task::{Poll, Waker};
17
18/// Raw task handle
19#[derive(Clone)]
20pub(crate) struct RawTask {
21    ptr: NonNull<Header>,
22}
23
24pub(super) struct Vtable {
25    /// Polls the future.
26    pub(super) poll: unsafe fn(NonNull<Header>),
27
28    /// Schedules the task for execution on the runtime.
29    pub(super) schedule: unsafe fn(NonNull<Header>),
30
31    /// Deallocates the memory.
32    pub(super) dealloc: unsafe fn(NonNull<Header>),
33
34    /// Reads the task output, if complete.
35    pub(super) try_read_output: unsafe fn(NonNull<Header>, *mut (), &Waker),
36
37    /// The join handle has been dropped.
38    pub(super) drop_join_handle_slow: unsafe fn(NonNull<Header>),
39
40    /// An abort handle has been dropped.
41    pub(super) drop_abort_handle: unsafe fn(NonNull<Header>),
42
43    /// Scheduler is being shutdown.
44    pub(super) shutdown: unsafe fn(NonNull<Header>),
45
46    /// The number of bytes that the `trailer` field is offset from the header.
47    pub(super) trailer_offset: usize,
48
49    /// The number of bytes that the `scheduler` field is offset from the header.
50    pub(super) scheduler_offset: usize,
51
52    /// The number of bytes that the `id` field is offset from the header.
53    pub(super) id_offset: usize,
54
55    /// The number of bytes that the `spawned_at` field is offset from the header.
56    #[cfg(tokio_unstable)]
57    pub(super) spawn_location_offset: usize,
58}
59
60/// Get the vtable for the requested `T` and `S` generics.
61pub(super) fn vtable<T: Future, S: Schedule>() -> &'static Vtable {
62    &Vtable {
63        poll: poll::<T, S>,
64        schedule: schedule::<S>,
65        dealloc: dealloc::<T, S>,
66        try_read_output: try_read_output::<T, S>,
67        drop_join_handle_slow: drop_join_handle_slow::<T, S>,
68        drop_abort_handle: drop_abort_handle::<T, S>,
69        shutdown: shutdown::<T, S>,
70        trailer_offset: OffsetHelper::<T, S>::TRAILER_OFFSET,
71        scheduler_offset: OffsetHelper::<T, S>::SCHEDULER_OFFSET,
72        id_offset: OffsetHelper::<T, S>::ID_OFFSET,
73        #[cfg(tokio_unstable)]
74        spawn_location_offset: OffsetHelper::<T, S>::SPAWN_LOCATION_OFFSET,
75    }
76}
77
78/// Calling `get_trailer_offset` directly in vtable doesn't work because it
79/// prevents the vtable from being promoted to a static reference.
80///
81/// See this thread for more info:
82/// <https://users.rust-lang.org/t/custom-vtables-with-integers/78508>
83struct OffsetHelper<T, S>(T, S);
84impl<T: Future, S: Schedule> OffsetHelper<T, S> {
85    // Pass `size_of`/`align_of` as arguments rather than calling them directly
86    // inside `get_trailer_offset` because trait bounds on generic parameters
87    // of const fn are unstable on our MSRV.
88    const TRAILER_OFFSET: usize = get_trailer_offset(
89        std::mem::size_of::<Header>(),
90        std::mem::size_of::<Core<T, S>>(),
91        std::mem::align_of::<Core<T, S>>(),
92        std::mem::align_of::<Trailer>(),
93    );
94
95    // The `scheduler` is the first field of `Core`, so it has the same
96    // offset as `Core`.
97    const SCHEDULER_OFFSET: usize = get_core_offset(
98        std::mem::size_of::<Header>(),
99        std::mem::align_of::<Core<T, S>>(),
100    );
101
102    const ID_OFFSET: usize = get_id_offset(
103        std::mem::size_of::<Header>(),
104        std::mem::align_of::<Core<T, S>>(),
105        std::mem::size_of::<S>(),
106        std::mem::align_of::<Id>(),
107    );
108
109    #[cfg(tokio_unstable)]
110    const SPAWN_LOCATION_OFFSET: usize = get_spawn_location_offset(
111        std::mem::size_of::<Header>(),
112        std::mem::align_of::<Core<T, S>>(),
113        std::mem::size_of::<S>(),
114        std::mem::align_of::<Id>(),
115        std::mem::size_of::<Id>(),
116        std::mem::align_of::<&'static Location<'static>>(),
117    );
118}
119
120/// Compute the offset of the `Trailer` field in `Cell<T, S>` using the
121/// `#[repr(C)]` algorithm.
122///
123/// Pseudo-code for the `#[repr(C)]` algorithm can be found here:
124/// <https://doc.rust-lang.org/reference/type-layout.html#reprc-structs>
125const fn get_trailer_offset(
126    header_size: usize,
127    core_size: usize,
128    core_align: usize,
129    trailer_align: usize,
130) -> usize {
131    let mut offset = header_size;
132
133    let core_misalign = offset % core_align;
134    if core_misalign > 0 {
135        offset += core_align - core_misalign;
136    }
137    offset += core_size;
138
139    let trailer_misalign = offset % trailer_align;
140    if trailer_misalign > 0 {
141        offset += trailer_align - trailer_misalign;
142    }
143
144    offset
145}
146
147/// Compute the offset of the `Core<T, S>` field in `Cell<T, S>` using the
148/// `#[repr(C)]` algorithm.
149///
150/// Pseudo-code for the `#[repr(C)]` algorithm can be found here:
151/// <https://doc.rust-lang.org/reference/type-layout.html#reprc-structs>
152const fn get_core_offset(header_size: usize, core_align: usize) -> usize {
153    let mut offset = header_size;
154
155    let core_misalign = offset % core_align;
156    if core_misalign > 0 {
157        offset += core_align - core_misalign;
158    }
159
160    offset
161}
162
163/// Compute the offset of the `Id` field in `Cell<T, S>` using the
164/// `#[repr(C)]` algorithm.
165///
166/// Pseudo-code for the `#[repr(C)]` algorithm can be found here:
167/// <https://doc.rust-lang.org/reference/type-layout.html#reprc-structs>
168const fn get_id_offset(
169    header_size: usize,
170    core_align: usize,
171    scheduler_size: usize,
172    id_align: usize,
173) -> usize {
174    let mut offset = get_core_offset(header_size, core_align);
175    offset += scheduler_size;
176
177    let id_misalign = offset % id_align;
178    if id_misalign > 0 {
179        offset += id_align - id_misalign;
180    }
181
182    offset
183}
184
185/// Compute the offset of the `&'static Location<'static>` field in `Cell<T, S>`
186/// using the `#[repr(C)]` algorithm.
187///
188/// Pseudo-code for the `#[repr(C)]` algorithm can be found here:
189/// <https://doc.rust-lang.org/reference/type-layout.html#reprc-structs>
190#[cfg(tokio_unstable)]
191const fn get_spawn_location_offset(
192    header_size: usize,
193    core_align: usize,
194    scheduler_size: usize,
195    id_align: usize,
196    id_size: usize,
197    spawn_location_align: usize,
198) -> usize {
199    let mut offset = get_id_offset(header_size, core_align, scheduler_size, id_align);
200    offset += id_size;
201
202    let spawn_location_misalign = offset % spawn_location_align;
203    if spawn_location_misalign > 0 {
204        offset += spawn_location_align - spawn_location_misalign;
205    }
206
207    offset
208}
209
210impl RawTask {
211    pub(super) fn new<T, S>(
212        task: T,
213        scheduler: S,
214        id: Id,
215        _spawned_at: super::SpawnLocation,
216    ) -> RawTask
217    where
218        T: Future,
219        S: Schedule,
220    {
221        let ptr = Box::into_raw(Cell::<_, S>::new(
222            task,
223            scheduler,
224            State::new(),
225            id,
226            #[cfg(tokio_unstable)]
227            _spawned_at.0,
228        ));
229        let ptr = unsafe { NonNull::new_unchecked(ptr.cast()) };
230
231        RawTask { ptr }
232    }
233
234    /// # Safety
235    ///
236    /// `ptr` must be a valid pointer to a [`Header`].
237    pub(super) unsafe fn from_raw(ptr: NonNull<Header>) -> RawTask {
238        RawTask { ptr }
239    }
240
241    pub(super) fn header_ptr(&self) -> NonNull<Header> {
242        self.ptr
243    }
244
245    cfg_taskdump! {
246        pub(super) fn header_ptr_ref(&self) -> &NonNull<Header> {
247            &self.ptr
248        }
249    }
250
251    pub(super) fn trailer_ptr(&self) -> NonNull<Trailer> {
252        unsafe { Header::get_trailer(self.ptr) }
253    }
254
255    /// Returns a reference to the task's header.
256    pub(super) fn header(&self) -> &Header {
257        unsafe { self.ptr.as_ref() }
258    }
259
260    /// Returns a reference to the task's trailer.
261    pub(super) fn trailer(&self) -> &Trailer {
262        unsafe { &*self.trailer_ptr().as_ptr() }
263    }
264
265    /// Returns a reference to the task's state.
266    pub(super) fn state(&self) -> &State {
267        &self.header().state
268    }
269
270    /// Safety: mutual exclusion is required to call this function.
271    pub(crate) fn poll(self) {
272        let vtable = self.header().vtable;
273        unsafe { (vtable.poll)(self.ptr) }
274    }
275
276    pub(super) fn schedule(self) {
277        let vtable = self.header().vtable;
278        unsafe { (vtable.schedule)(self.ptr) }
279    }
280
281    pub(super) fn dealloc(self) {
282        let vtable = self.header().vtable;
283        unsafe {
284            (vtable.dealloc)(self.ptr);
285        }
286    }
287
288    /// Safety: `dst` must be a `*mut Poll<super::Result<T::Output>>` where `T`
289    /// is the future stored by the task.
290    pub(super) unsafe fn try_read_output<O>(self, dst: *mut Poll<super::Result<O>>, waker: &Waker) {
291        let vtable = self.header().vtable;
292        (vtable.try_read_output)(self.ptr, dst as *mut _, waker);
293    }
294
295    pub(super) fn drop_join_handle_slow(self) {
296        let vtable = self.header().vtable;
297        unsafe { (vtable.drop_join_handle_slow)(self.ptr) }
298    }
299
300    pub(super) fn drop_abort_handle(self) {
301        let vtable = self.header().vtable;
302        unsafe { (vtable.drop_abort_handle)(self.ptr) }
303    }
304
305    pub(super) fn shutdown(self) {
306        let vtable = self.header().vtable;
307        unsafe { (vtable.shutdown)(self.ptr) }
308    }
309
310    /// Increment the task's reference count.
311    ///
312    /// Currently, this is used only when creating an `AbortHandle`.
313    pub(super) fn ref_inc(self) {
314        self.header().state.ref_inc();
315    }
316
317    /// Get the queue-next pointer
318    ///
319    /// This is for usage by the injection queue
320    ///
321    /// Safety: make sure only one queue uses this and access is synchronized.
322    pub(crate) unsafe fn get_queue_next(self) -> Option<RawTask> {
323        self.header()
324            .queue_next
325            .with(|ptr| *ptr)
326            .map(|p| RawTask::from_raw(p))
327    }
328
329    /// Sets the queue-next pointer
330    ///
331    /// This is for usage by the injection queue
332    ///
333    /// Safety: make sure only one queue uses this and access is synchronized.
334    pub(crate) unsafe fn set_queue_next(self, val: Option<RawTask>) {
335        self.header().set_next(val.map(|task| task.ptr));
336    }
337}
338
339impl Copy for RawTask {}
340
341unsafe fn poll<T: Future, S: Schedule>(ptr: NonNull<Header>) {
342    let harness = Harness::<T, S>::from_raw(ptr);
343    harness.poll();
344}
345
346unsafe fn schedule<S: Schedule>(ptr: NonNull<Header>) {
347    use crate::runtime::task::{Notified, Task};
348
349    let scheduler = Header::get_scheduler::<S>(ptr);
350    scheduler
351        .as_ref()
352        .schedule(Notified(Task::from_raw(ptr.cast())));
353}
354
355unsafe fn dealloc<T: Future, S: Schedule>(ptr: NonNull<Header>) {
356    let harness = Harness::<T, S>::from_raw(ptr);
357    harness.dealloc();
358}
359
360unsafe fn try_read_output<T: Future, S: Schedule>(
361    ptr: NonNull<Header>,
362    dst: *mut (),
363    waker: &Waker,
364) {
365    let out = &mut *(dst as *mut Poll<super::Result<T::Output>>);
366
367    let harness = Harness::<T, S>::from_raw(ptr);
368    harness.try_read_output(out, waker);
369}
370
371unsafe fn drop_join_handle_slow<T: Future, S: Schedule>(ptr: NonNull<Header>) {
372    let harness = Harness::<T, S>::from_raw(ptr);
373    harness.drop_join_handle_slow();
374}
375
376unsafe fn drop_abort_handle<T: Future, S: Schedule>(ptr: NonNull<Header>) {
377    let harness = Harness::<T, S>::from_raw(ptr);
378    harness.drop_reference();
379}
380
381unsafe fn shutdown<T: Future, S: Schedule>(ptr: NonNull<Header>) {
382    let harness = Harness::<T, S>::from_raw(ptr);
383    harness.shutdown();
384}