Skip to main content

ax_task/executor/
coroutine.rs

1//! Pinned coroutine allocation and lifetime accounting.
2
3use alloc::{
4    alloc::{Layout, dealloc},
5    sync::Arc,
6};
7use core::{
8    cell::{Cell, UnsafeCell},
9    future::Future,
10    marker::PhantomPinned,
11    pin::Pin,
12    ptr,
13    sync::atomic::{AtomicPtr, AtomicUsize, Ordering},
14    task::{Context, Poll},
15};
16
17use super::SharedExecutor;
18use crate::{
19    runtime::{delivery::inbox::InboxNode, task_runtime},
20    thread::{ThreadId, WakeIntent},
21};
22
23pub(super) const RUN_QUEUED: usize = 1 << 0;
24pub(super) const POLLING: usize = 1 << 1;
25pub(super) const COMPLETE: usize = 1 << 2;
26const FUTURE_EMPTY: usize = 1 << 3;
27
28const REFCOUNT_OVERFLOW_INVARIANT: u32 = 0x4558_0001;
29const EARLY_RECLAIM_INVARIANT: u32 = 0x4558_0002;
30
31type PollFuture = unsafe fn(*mut CoroutineHeader, &mut Context<'_>) -> Poll<()>;
32type DropFuture = unsafe fn(*mut CoroutineHeader);
33type Deallocate = unsafe fn(*mut CoroutineHeader);
34
35/// Generation-bearing identity of one coroutine owned by a local executor.
36#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
37pub struct CoroutineId {
38    owner_thread: ThreadId,
39    generation: u64,
40}
41
42impl CoroutineId {
43    pub(super) const fn new(owner_thread: ThreadId, generation: u64) -> Self {
44        Self {
45            owner_thread,
46            generation,
47        }
48    }
49
50    /// Returns the scheduler thread that owns this coroutine.
51    pub const fn owner_thread(self) -> ThreadId {
52        self.owner_thread
53    }
54
55    /// Returns the executor-local allocation generation.
56    pub const fn generation(self) -> u64 {
57        self.generation
58    }
59}
60
61/// Pinned header addressed directly by the coroutine's custom raw waker.
62///
63/// All fields touched outside the owner thread are atomic or immutable. The
64/// future itself follows this header in a private allocation and is inaccessible
65/// to raw waker operations.
66#[repr(C)]
67pub struct CoroutineHeader {
68    reclaim: InboxNode,
69    id: CoroutineId,
70    pub(super) state: AtomicUsize,
71    references: AtomicUsize,
72    executor: Arc<SharedExecutor>,
73    ready_next: AtomicPtr<Self>,
74    owner_next: Cell<*mut Self>,
75    poll_future: PollFuture,
76    drop_future: DropFuture,
77    deallocate: Deallocate,
78    _pin: PhantomPinned,
79}
80
81impl CoroutineHeader {
82    /// Returns this allocation's generation-bearing identity.
83    pub const fn id(&self) -> CoroutineId {
84        self.id
85    }
86
87    /// Returns the owner thread embedded in every waker for this coroutine.
88    pub const fn owner_thread(&self) -> ThreadId {
89        self.id.owner_thread()
90    }
91
92    /// Polls the allocation-specific future.
93    ///
94    /// # Safety
95    ///
96    /// The caller must be the executor owner, hold a live allocation reference,
97    /// and guarantee that no future poll or drop overlaps this call.
98    pub(super) unsafe fn poll_raw(header: *mut Self, context: &mut Context<'_>) -> Poll<()> {
99        let poll_future = unsafe {
100            // Copying the function pointer through the original allocation
101            // pointer does not widen a reference over the containing coroutine.
102            core::ptr::addr_of!((*header).poll_future).read()
103        };
104        unsafe {
105            // Construction installs the allocation-specific function and the
106            // caller guarantees exclusive owner-thread access to the future.
107            poll_future(header, context)
108        }
109    }
110
111    /// Drops the allocation-specific future in place.
112    ///
113    /// # Safety
114    ///
115    /// The caller must be the executor owner, hold a live allocation reference,
116    /// and call this exactly once after completion or owner shutdown.
117    pub(super) unsafe fn drop_future_raw(header: *mut Self) {
118        let drop_future = unsafe {
119            // Preserve allocation provenance for the type-erased future slot.
120            core::ptr::addr_of!((*header).drop_future).read()
121        };
122        unsafe {
123            // Completion and cancellation serialize this operation on the owner.
124            drop_future(header);
125        }
126    }
127
128    /// Frees a zero-reference coroutine allocation in task context.
129    ///
130    /// # Safety
131    ///
132    /// `header` must be detached from the task-system reclaim inbox, have zero
133    /// references, and have had its future emptied by the owner.
134    pub(crate) unsafe fn deallocate_raw(header: *mut Self) {
135        let state = unsafe { (*header).state.load(Ordering::Acquire) };
136        if state & (COMPLETE | FUTURE_EMPTY) != (COMPLETE | FUTURE_EMPTY) {
137            task_runtime::fatal_invariant(EARLY_RECLAIM_INVARIANT, unsafe {
138                (*header).id.generation() as usize
139            });
140        }
141        let deallocate = unsafe {
142            // The detached node remains valid until the callback is copied.
143            (*header).deallocate
144        };
145        unsafe {
146            // Future emptiness makes cross-CPU header destruction incapable of
147            // running a !Send future destructor.
148            deallocate(header);
149        }
150    }
151
152    pub(crate) fn reclaim_node(self: Pin<&'static Self>) -> Pin<&'static InboxNode> {
153        unsafe {
154            // The coroutine header is pinned for its complete lifetime, and the
155            // intrusive reclaim node is never projected mutably or moved.
156            self.map_unchecked(|header| &header.reclaim)
157        }
158    }
159
160    pub(crate) fn address(self: Pin<&'static Self>) -> usize {
161        (self.get_ref() as *const Self).addr()
162    }
163
164    pub(super) fn next(&self, kind: super::inbox::InboxKind) -> &AtomicPtr<Self> {
165        match kind {
166            super::inbox::InboxKind::Ready => &self.ready_next,
167        }
168    }
169
170    pub(super) fn owner_next(&self) -> *mut Self {
171        self.owner_next.get()
172    }
173
174    pub(super) fn set_owner_next(&self, next: *mut Self) {
175        self.owner_next.set(next);
176    }
177}
178
179// SAFETY: External CPUs reach only immutable metadata and atomic fields. The
180// owner-only list and !Send future remain inaccessible through the public header.
181unsafe impl Send for CoroutineHeader {}
182// SAFETY: Shared references expose only immutable metadata and atomic operations;
183// polling, owner-list mutation, and future destruction are private owner actions.
184unsafe impl Sync for CoroutineHeader {}
185
186#[repr(C)]
187pub(super) struct Coroutine<F> {
188    header: CoroutineHeader,
189    future: UnsafeCell<Option<F>>,
190}
191
192impl<F> Coroutine<F>
193where
194    F: Future<Output = ()>,
195{
196    pub(super) fn new(id: CoroutineId, executor: Arc<SharedExecutor>, future: F) -> Self {
197        Self {
198            header: CoroutineHeader {
199                reclaim: InboxNode::new(crate::runtime::delivery::inbox::InboxKind::Reclaim),
200                id,
201                state: AtomicUsize::new(0),
202                references: AtomicUsize::new(1),
203                executor,
204                ready_next: AtomicPtr::new(ptr::null_mut()),
205                owner_next: Cell::new(ptr::null_mut()),
206                poll_future: poll_future::<F>,
207                drop_future: drop_future::<F>,
208                deallocate: deallocate::<F>,
209                _pin: PhantomPinned,
210            },
211            future: UnsafeCell::new(Some(future)),
212        }
213    }
214}
215
216/// Coalesces and publishes one ready notification.
217///
218/// # Safety
219///
220/// `header` must point to a pinned coroutine allocation for which the caller owns
221/// a live reference until this function returns.
222pub(super) unsafe fn schedule(header: *mut CoroutineHeader) {
223    unsafe {
224        // The standard Waker contract carries no sleep-soon scheduler hint.
225        schedule_with_intent(header, WakeIntent::Normal);
226    }
227}
228
229/// Coalesces and publishes one Linux `WF_SYNC` ready notification.
230///
231/// # Safety
232///
233/// `header` must point to a pinned coroutine allocation for which the caller
234/// owns a live reference until this function returns. The caller must be in
235/// task context and expect to block shortly.
236pub(super) unsafe fn schedule_sync(header: *mut CoroutineHeader) {
237    unsafe {
238        schedule_with_intent(header, WakeIntent::Sync);
239    }
240}
241
242unsafe fn schedule_with_intent(header: *mut CoroutineHeader, intent: WakeIntent) {
243    let header_ref = unsafe {
244        // Every caller owns a live reference for the duration of publication.
245        &*header
246    };
247    let mut observed = header_ref.state.load(Ordering::Acquire);
248
249    loop {
250        if observed & (COMPLETE | RUN_QUEUED) != 0 {
251            return;
252        }
253        match header_ref.state.compare_exchange_weak(
254            observed,
255            observed | RUN_QUEUED,
256            Ordering::AcqRel,
257            Ordering::Acquire,
258        ) {
259            Ok(_) => break,
260            Err(updated) => observed = updated,
261        }
262    }
263
264    retain_reference(header_ref);
265    if !header_ref.executor.publish_ready(header, intent) {
266        header_ref.state.fetch_and(!RUN_QUEUED, Ordering::AcqRel);
267        unsafe {
268            // Closing rejected this publication, so the retained queue reference
269            // is released without touching the destroyed local owner object.
270            release_reference(header);
271        }
272    }
273}
274
275pub(super) fn retain_reference(header: &CoroutineHeader) {
276    let mut references = header.references.load(Ordering::Relaxed);
277    loop {
278        let Some(next) = references.checked_add(1) else {
279            task_runtime::fatal_invariant(
280                REFCOUNT_OVERFLOW_INVARIANT,
281                header.id.generation() as usize,
282            );
283        };
284        if references == 0 {
285            task_runtime::fatal_invariant(
286                REFCOUNT_OVERFLOW_INVARIANT,
287                header.id.generation() as usize,
288            );
289        }
290        match header.references.compare_exchange_weak(
291            references,
292            next,
293            Ordering::Relaxed,
294            Ordering::Relaxed,
295        ) {
296            Ok(_) => return,
297            Err(updated) => references = updated,
298        }
299    }
300}
301
302/// Releases one allocation reference.
303///
304/// # Safety
305///
306/// `header` must point to a pinned coroutine allocation and the caller must own
307/// exactly one reference that has not already been released.
308pub(super) unsafe fn release_reference(header: *mut CoroutineHeader) {
309    let header_ref = unsafe {
310        // Caller relinquishes one live reference and does not use it afterward
311        // unless another independently owned reference remains.
312        &*header
313    };
314    let previous = header_ref.references.fetch_sub(1, Ordering::Release);
315    if previous == 0 {
316        task_runtime::fatal_invariant(
317            REFCOUNT_OVERFLOW_INVARIANT,
318            header_ref.id.generation() as usize,
319        );
320    }
321    if previous != 1 {
322        return;
323    }
324    core::sync::atomic::fence(Ordering::Acquire);
325
326    if !task_runtime::in_hard_irq() {
327        unsafe {
328            // The final Release/Acquire pair proves that no raw waker or queue
329            // reader can still address this header. The future was emptied by
330            // the owner before its permanent reference was released, so task
331            // context may destroy the allocation immediately.
332            CoroutineHeader::deallocate_raw(header);
333        }
334        return;
335    }
336
337    let header = unsafe {
338        // Hard IRQ cannot run allocator or ThreadWakeHandle destruction. The
339        // zero-reference allocation therefore stays pinned until the typed
340        // task-system consumer detaches this header and frees it.
341        Pin::new_unchecked(header_ref)
342    };
343    crate::runtime::service::reclaim::publish_deferred_coroutine_reclaim(header);
344}
345
346/// Polls the concrete future behind a type-erased header.
347///
348/// # Safety
349///
350/// `header` must denote a pinned `Coroutine<F>`, and the owner must provide
351/// exclusive access to its populated future slot for the duration of the call.
352unsafe fn poll_future<F>(header: *mut CoroutineHeader, context: &mut Context<'_>) -> Poll<()>
353where
354    F: Future<Output = ()>,
355{
356    let coroutine = header.cast::<Coroutine<F>>();
357    let future = unsafe {
358        // repr(C) places the header first. Only the UnsafeCell payload is mutably
359        // borrowed; concurrent raw-waker header access remains disjoint.
360        &mut *(*coroutine).future.get()
361    };
362    match future.as_mut() {
363        Some(future) => unsafe {
364            // The allocation never moves after publication.
365            Pin::new_unchecked(future).poll(context)
366        },
367        None => Poll::Ready(()),
368    }
369}
370
371/// Empties the concrete future slot behind a type-erased header.
372///
373/// # Safety
374///
375/// `header` must denote a pinned `Coroutine<F>`. The owner must call this once
376/// after completion or cancellation with no overlapping poll.
377unsafe fn drop_future<F>(header: *mut CoroutineHeader)
378where
379    F: Future<Output = ()>,
380{
381    let coroutine = header.cast::<Coroutine<F>>();
382    let future = unsafe {
383        // Owner-only completion serializes access to the UnsafeCell payload.
384        &mut *(*coroutine).future.get()
385    };
386    let future = future.take();
387    unsafe {
388        // The slot is empty before user destructor code runs. An owner or queue
389        // reference prevents reclamation until that destructor returns or fully
390        // unwinds, while this Release publishes emptiness to the later reaper.
391        (*header).state.fetch_or(FUTURE_EMPTY, Ordering::Release);
392    }
393    drop(future);
394}
395
396/// Reconstructs and frees the concrete coroutine allocation.
397///
398/// # Safety
399///
400/// `header` must be the first field of the original `Box<Coroutine<F>>`, have a
401/// zero reference count, and contain an empty future slot.
402unsafe fn deallocate<F>(header: *mut CoroutineHeader)
403where
404    F: Future<Output = ()>,
405{
406    unsafe {
407        // The owner already dropped F and published FUTURE_EMPTY. Reconstructing
408        // `Coroutine<F>` here could form a typed object after F's borrowing
409        // lifetime ended, so the reaper touches only the non-generic header field
410        // that requires destruction and then releases the raw allocation.
411        core::ptr::drop_in_place(core::ptr::addr_of_mut!((*header).executor));
412        dealloc(header.cast::<u8>(), Layout::new::<Coroutine<F>>());
413    }
414}