Skip to main content

ax_task/thread/handle/
mod.rs

1//! Strong, weak, and direct IRQ-wake handles.
2
3use alloc::sync::{Arc, Weak};
4#[cfg(feature = "lockdep")]
5use core::cell::UnsafeCell;
6use core::{
7    marker::PhantomData,
8    mem::ManuallyDrop,
9    sync::atomic::{AtomicBool, AtomicPtr, AtomicU8, AtomicU32, AtomicU64, AtomicUsize, Ordering},
10};
11
12mod wake_batch;
13pub use wake_batch::ThreadWakeBatch;
14
15use crate::{
16    runtime::{
17        cpu::PreemptGuardToken,
18        delivery::{
19            inbox::{InboxKind, InboxNode},
20            work::TaskWorkDoorbell,
21        },
22        resource::AddressSpaceMembarrierId,
23        service::SchedulerTickCpuTime,
24        task_runtime,
25    },
26    sched::{
27        CpuId, DeadlineFlags, DeadlinePolicy, FairMode, Nice, RtPriority, SchedulePolicy,
28        algorithm::RunQueueNodeStorage, system::ThreadSchedCell,
29    },
30    thread::{
31        ParkPublication, PiWaitNodeStorage, PiWaitState, SchedulerTickWork, SchedulerTickWorkClaim,
32        SchedulingKey, SchedulingUrgency, TaskError, ThreadAffinityCompletion, ThreadExtensionView,
33        ThreadId, ThreadLifecycle, ThreadState, WakePublication,
34    },
35    time::queue::TaskDeadlineNode,
36};
37
38const REAP_CLAIMED: usize = 1 << (usize::BITS - 1);
39const REAP_MAX_UPGRADE_READERS: usize = REAP_CLAIMED - 1;
40const SCHEDULER_ACTIVITY_CLOSED: usize = 1 << (usize::BITS - 1);
41const SCHEDULER_ACTIVITY_MAX_READERS: usize = SCHEDULER_ACTIVITY_CLOSED - 1;
42
43#[cfg(feature = "lockdep")]
44struct ThreadHeldLocks {
45    stack: UnsafeCell<crate::sync::lockdep::HeldLockStack>,
46}
47
48#[cfg(feature = "lockdep")]
49impl ThreadHeldLocks {
50    const fn new() -> Self {
51        Self {
52            stack: UnsafeCell::new(crate::sync::lockdep::HeldLockStack::new()),
53        }
54    }
55
56    unsafe fn with_mut<R>(
57        &self,
58        operation: impl FnOnce(&mut crate::sync::lockdep::HeldLockStack) -> R,
59    ) -> R {
60        // SAFETY: the caller owns the current-task and migration-exclusion
61        // contract documented on `ThreadCore::with_held_locks`.
62        unsafe { operation(&mut *self.stack.get()) }
63    }
64}
65
66#[cfg(feature = "lockdep")]
67impl core::fmt::Debug for ThreadHeldLocks {
68    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
69        formatter.write_str("ThreadHeldLocks(..)")
70    }
71}
72
73// SAFETY: the stack is accessed only by its currently executing task while
74// local IRQ exclusion prevents migration and scheduler replacement. Other
75// threads may retain `ThreadCore` references but cannot access this field.
76#[cfg(feature = "lockdep")]
77unsafe impl Sync for ThreadHeldLocks {}
78
79/// A strong reference used to inspect and control a live thread.
80#[derive(Debug)]
81pub struct ThreadHandle {
82    pub(crate) core: ManuallyDrop<Arc<ThreadCore>>,
83    reap_signal: Arc<ThreadReapSignal>,
84}
85
86impl Drop for ThreadHandle {
87    fn drop(&mut self) {
88        unsafe {
89            // SAFETY: `core` is wrapped solely so this destructor can release
90            // the strong count before publishing the reaper retry. It is
91            // dropped exactly once here and never accessed afterwards.
92            ManuallyDrop::drop(&mut self.core);
93        }
94        self.reap_signal.release_external_lease();
95    }
96}
97
98impl Clone for ThreadHandle {
99    fn clone(&self) -> Self {
100        let core = Arc::clone(&self.core);
101        let reap_signal = Arc::clone(&self.reap_signal);
102        reap_signal.acquire_external_lease();
103        Self {
104            core: ManuallyDrop::new(core),
105            reap_signal,
106        }
107    }
108}
109
110impl ThreadHandle {
111    pub(crate) fn from_core(core: Arc<ThreadCore>) -> Self {
112        let reap_signal = Arc::clone(&core.reap_signal);
113        reap_signal.acquire_external_lease();
114        Self {
115            core: ManuallyDrop::new(core),
116            reap_signal,
117        }
118    }
119
120    /// Returns the immutable runtime publication for this live scheduler
121    /// thread.
122    #[doc(hidden)]
123    pub fn runtime_publication(&self) -> crate::runtime::switch::CurrentThreadPublication {
124        crate::runtime::switch::CurrentThreadPublication::from_core(self.id(), &self.core)
125    }
126
127    pub(crate) fn runtime_core_arc(&self) -> &Arc<ThreadCore> {
128        &self.core
129    }
130
131    /// Returns the generation-checked registry identity.
132    pub fn id(&self) -> ThreadId {
133        self.core.id
134    }
135
136    /// Returns the thread's base scheduling policy.
137    pub fn base_policy(&self) -> SchedulePolicy {
138        self.core.base_policy.load()
139    }
140
141    /// Returns the policy after priority-inheritance donation is applied.
142    pub fn effective_policy(&self) -> SchedulePolicy {
143        self.core.effective_policy.load()
144    }
145
146    /// Returns the most recently published lifecycle state.
147    pub fn state(&self) -> ThreadState {
148        self.core.state()
149    }
150
151    /// Creates a non-owning lifecycle observer.
152    pub fn downgrade(&self) -> WeakThreadHandle {
153        WeakThreadHandle {
154            core: Arc::downgrade(&self.core),
155        }
156    }
157
158    /// Creates a direct wake handle that does not consult the thread registry.
159    pub fn wake_handle(&self) -> ThreadWakeHandle {
160        ThreadWakeHandle::from_core(Arc::clone(&self.core))
161    }
162
163    /// Returns the physical CPU that must cross a scheduler boundary.
164    ///
165    /// Unlike direct wake placement, this snapshot remains on the source CPU
166    /// until switch tail releases the outgoing context. A task-context caller
167    /// can therefore publish state, take this snapshot, and rendezvous with the
168    /// returned CPU; either the thread is still active there or it crossed a
169    /// scheduler boundary after the publication.
170    pub fn scheduler_fence_cpu(&self) -> Option<CpuId> {
171        self.core.sched().scheduler_fence_cpu()
172    }
173
174    /// Returns Linux `task_cpu()`: the last committed runqueue assignment.
175    ///
176    /// This remains the previous CPU while a task sleeps and changes to the
177    /// destination when an rq-to-rq migration commits. Physical execution
178    /// ownership is exposed separately by [`Self::scheduler_fence_cpu`].
179    /// A wake-placement hint is never reported as scheduler placement.
180    pub fn assigned_cpu(&self) -> Option<CpuId> {
181        self.core.assigned_cpu()
182    }
183
184    pub(crate) fn extension_view(&self) -> Option<crate::thread::ThreadExtensionView> {
185        self.core.extension_view()
186    }
187}
188
189impl Eq for ThreadHandle {}
190
191impl PartialEq for ThreadHandle {
192    fn eq(&self, other: &Self) -> bool {
193        self.id() == other.id()
194    }
195}
196
197/// A non-owning thread observer for ordinary task context.
198#[derive(Clone, Debug)]
199pub struct WeakThreadHandle {
200    core: Weak<ThreadCore>,
201}
202
203impl WeakThreadHandle {
204    /// Attempts to acquire a strong reference while the thread header is alive.
205    pub fn upgrade(&self) -> Option<ThreadHandle> {
206        let core = self.core.upgrade()?;
207        if !core.try_enter_weak_upgrade() {
208            return None;
209        }
210        let handle = ThreadHandle::from_core(core);
211        handle.core.exit_weak_upgrade();
212        Some(handle)
213    }
214}
215
216/// A stable direct wake header reference.
217///
218/// [`Self::wake`] performs only bounded atomic operations and is safe in hard IRQ
219/// context. Creating, cloning, and dropping this owning reference are task-context
220/// operations. A coroutine whose last raw-waker reference is released in hard IRQ
221/// defers only that zero-reference allocation to the typed task-system reaper.
222#[derive(Debug)]
223pub struct ThreadWakeHandle {
224    pub(crate) core: ManuallyDrop<Arc<ThreadCore>>,
225    reap_signal: Arc<ThreadReapSignal>,
226}
227
228impl Drop for ThreadWakeHandle {
229    fn drop(&mut self) {
230        unsafe {
231            // SAFETY: identical ownership rule to ThreadHandle::drop above.
232            ManuallyDrop::drop(&mut self.core);
233        }
234        self.reap_signal.release_external_lease();
235    }
236}
237
238impl Clone for ThreadWakeHandle {
239    fn clone(&self) -> Self {
240        let core = Arc::clone(&self.core);
241        let reap_signal = Arc::clone(&self.reap_signal);
242        reap_signal.acquire_external_lease();
243        Self {
244            core: ManuallyDrop::new(core),
245            reap_signal,
246        }
247    }
248}
249
250impl ThreadWakeHandle {
251    pub(crate) fn from_core(core: Arc<ThreadCore>) -> Self {
252        let reap_signal = Arc::clone(&core.reap_signal);
253        reap_signal.acquire_external_lease();
254        Self {
255            core: ManuallyDrop::new(core),
256            reap_signal,
257        }
258    }
259
260    /// Directly wakes the thread without allocating, sleeping, or invoking callbacks.
261    ///
262    /// This IRQ-safe operation may acquire the thread scheduler lock and the
263    /// selected CPU's raw runqueue lock.
264    pub fn wake(&self) -> WakeResult {
265        self.core.wake(WakeIntent::Normal)
266    }
267
268    /// Wakes from task context when the caller expects to block shortly.
269    ///
270    /// This is Linux's `WF_SYNC` contract. It remains a scheduling hint: the
271    /// waker commits the destination runqueue activation before returning.
272    pub fn wake_sync(&self) -> WakeResult {
273        debug_assert!(!crate::runtime::task_runtime::in_hard_irq());
274        self.core.wake(WakeIntent::Sync)
275    }
276
277    pub(crate) fn deliver_wait_claim_from_task(
278        &self,
279        claim: &crate::thread::WaitWakeClaim,
280        intent: WakeIntent,
281    ) -> crate::thread::WaitWakeDelivery {
282        crate::runtime::context::wake_wait_claim_from_task(&self.core, claim, intent)
283    }
284
285    /// Returns the thread that owns this wake header.
286    pub fn thread_id(&self) -> ThreadId {
287        self.core.id
288    }
289}
290
291impl ThreadCore {
292    fn wake(self: &Arc<Self>, intent: WakeIntent) -> WakeResult {
293        crate::runtime::context::wake_thread_from_current_cpu(self, intent)
294    }
295}
296
297#[derive(Clone, Copy, Debug, Eq, PartialEq)]
298pub(crate) enum WakeIntent {
299    Normal,
300    Sync,
301}
302
303impl WakeIntent {
304    pub(crate) const fn is_sync(self) -> bool {
305        matches!(self, Self::Sync)
306    }
307}
308
309/// Result of an IRQ-safe wake publication.
310#[derive(Clone, Copy, Debug, Eq, PartialEq)]
311pub enum WakeResult {
312    /// This call completed a logical wake transaction. The thread is runnable,
313    /// retained a park notification, or has owner-CPU activation committed.
314    Notified,
315    /// An unresolved park-transition notification already represents this event.
316    AlreadyPending,
317    /// The destination thread has exited, so the late wake is ignored.
318    Exited,
319    /// No scheduler-ready CPU is currently reachable for wake delivery.
320    Unavailable,
321}
322
323/// Runqueue-coherent snapshot of one thread's charged CPU runtime.
324#[derive(Clone, Copy, Debug, Eq, PartialEq)]
325pub struct ThreadRuntimeSnapshot {
326    charged_runtime_ns: u64,
327    running: bool,
328}
329
330impl ThreadRuntimeSnapshot {
331    /// Returns cumulative CPU runtime, including the current running residual.
332    pub const fn charged_runtime_ns(self) -> u64 {
333        self.charged_runtime_ns
334    }
335
336    /// Returns whether the snapshot included a live running residual.
337    pub const fn is_running(self) -> bool {
338        self.running
339    }
340}
341
342#[derive(Debug)]
343struct ThreadReapSignal {
344    exited: AtomicBool,
345    external_leases: AtomicUsize,
346    task_work: Option<Arc<TaskWorkDoorbell>>,
347}
348
349#[must_use = "the scheduler activity guard serializes owner delivery against exit"]
350pub(crate) struct ThreadSchedulerActivity<'thread> {
351    core: &'thread ThreadCore,
352    preempt: PreemptGuardToken,
353    _not_send: PhantomData<*mut ()>,
354}
355
356impl Drop for ThreadSchedulerActivity<'_> {
357    fn drop(&mut self) {
358        self.core.finish_scheduler_activity();
359        release_scheduler_preempt(self.preempt);
360    }
361}
362
363#[must_use = "the owned scheduler exit guard closes new activity until exit commits"]
364pub(crate) struct OwnedThreadSchedulerExit {
365    core: Arc<ThreadCore>,
366    preempt: PreemptGuardToken,
367    sealed: bool,
368    _not_send: PhantomData<*mut ()>,
369}
370
371impl OwnedThreadSchedulerExit {
372    pub(crate) fn seal(&mut self) {
373        self.sealed = true;
374    }
375}
376
377impl Drop for OwnedThreadSchedulerExit {
378    fn drop(&mut self) {
379        if !self.sealed {
380            self.core.reopen_scheduler_activity();
381        }
382        release_scheduler_preempt(self.preempt);
383    }
384}
385
386fn release_scheduler_preempt(token: PreemptGuardToken) {
387    if token.is_none() {
388        return;
389    }
390    // SAFETY: scheduler activity and exit guards are !Send and consume the
391    // exact token returned on this execution context after publishing their
392    // final gate state.
393    unsafe { task_runtime::preempt_guard_exit(token) };
394}
395
396#[must_use = "dropping the delivery lease makes an exited thread reapable"]
397pub(crate) struct ThreadSchedulerInboxDelivery<'thread> {
398    core: &'thread ThreadCore,
399}
400
401impl Drop for ThreadSchedulerInboxDelivery<'_> {
402    fn drop(&mut self) {
403        self.core.finish_scheduler_inbox_delivery();
404    }
405}
406
407impl ThreadReapSignal {
408    fn new(task_work: Option<Arc<TaskWorkDoorbell>>) -> Self {
409        Self {
410            exited: AtomicBool::new(false),
411            external_leases: AtomicUsize::new(0),
412            task_work,
413        }
414    }
415
416    fn mark_exited(&self) {
417        self.exited.store(true, Ordering::Release);
418    }
419
420    fn publish(&self) {
421        if let Some(task_work) = &self.task_work {
422            task_work.publish();
423        }
424    }
425
426    fn acquire_external_lease(&self) {
427        self.external_leases
428            .try_update(Ordering::AcqRel, Ordering::Acquire, |leases| {
429                leases.checked_add(1)
430            })
431            .expect("thread external-lifetime lease count overflow");
432    }
433
434    fn release_external_lease(&self) {
435        let previous = self.external_leases.fetch_sub(1, Ordering::AcqRel);
436        assert!(previous != 0, "unbalanced thread external-lifetime lease");
437        if previous == 1 && self.exited.load(Ordering::Acquire) {
438            self.publish();
439        }
440    }
441
442    fn external_lease_count(&self) -> usize {
443        self.external_leases.load(Ordering::Acquire)
444    }
445}
446
447#[derive(Debug)]
448pub(crate) struct ThreadCore {
449    id: ThreadId,
450    sched: Arc<ThreadSchedCell>,
451    // Stable `mm` identity. Registration bits remain rq-owned and are
452    // refreshed explicitly by the membarrier synchronization protocol.
453    membarrier_identity: AtomicUsize,
454    runqueue_nodes: RunQueueNodeStorage,
455    pi_wait_nodes: PiWaitNodeStorage,
456    // Immutable after publication. Every handle retaining this copy also pins
457    // the registry-owned extension destructor through the reaper Arc contract.
458    extension: Option<ThreadExtensionView>,
459    scheduler_tick_cpu_time: Option<Arc<SchedulerTickCpuTime>>,
460    scheduler_tick_work: Option<SchedulerTickWork>,
461    scheduler_tick_work_generation: AtomicU64,
462    scheduler_tick_observed_ns: AtomicU64,
463    scheduler_tick_work_node: InboxNode,
464    deadline_callback_node: InboxNode,
465    base_policy: AtomicPolicy,
466    effective_policy: AtomicPolicy,
467    effective_key_sequence: AtomicUsize,
468    effective_deadline_active: AtomicBool,
469    effective_deadline_ns: AtomicU64,
470    state: Arc<ThreadLifecycle>,
471    reap_signal: Arc<ThreadReapSignal>,
472    reap_gate: AtomicUsize,
473    scheduler_activity_gate: AtomicUsize,
474    scheduler_inbox_deliveries: AtomicUsize,
475    pub(super) affinity_completion: ThreadAffinityCompletion,
476    park_generation: AtomicU64,
477    wake_cpu_hint: AtomicU32,
478    wake_affinity: WakeAffinityState,
479    affinity_update_node: InboxNode,
480    deadline_refresh_node: InboxNode,
481    wake_batch_next: AtomicPtr<ThreadCore>,
482    wake_batch_linked: AtomicBool,
483    sleep_timer: TaskDeadlineNode,
484    deadline_cbs_timer: TaskDeadlineNode,
485    deadline_zero_lag_timer: TaskDeadlineNode,
486    sleep_timer_cpu: AtomicU32,
487    sleep_timer_generation: AtomicU64,
488    migration_node: InboxNode,
489    committed_runtime_ns: AtomicU64,
490    #[cfg(feature = "lockdep")]
491    held_locks: ThreadHeldLocks,
492    pi_wait_state: PiWaitState,
493}
494
495pub(crate) struct ThreadCoreInit {
496    pub(crate) id: ThreadId,
497    pub(crate) policy: SchedulePolicy,
498    pub(crate) sched: Arc<ThreadSchedCell>,
499    pub(crate) extension: Option<ThreadExtensionView>,
500    pub(crate) scheduler_tick_cpu_time: Option<Arc<SchedulerTickCpuTime>>,
501    pub(crate) scheduler_tick_work: Option<SchedulerTickWork>,
502    pub(crate) membarrier_identity: AddressSpaceMembarrierId,
503    pub(crate) task_work: Option<Arc<TaskWorkDoorbell>>,
504}
505
506impl ThreadCore {
507    pub(crate) fn new(init: ThreadCoreInit) -> Self {
508        let ThreadCoreInit {
509            id,
510            policy,
511            sched,
512            extension,
513            scheduler_tick_cpu_time,
514            scheduler_tick_work,
515            membarrier_identity,
516            task_work,
517        } = init;
518        debug_assert_eq!(id, sched.id());
519        let lifecycle = Arc::clone(sched.lifecycle());
520        let reap_signal = Arc::new(ThreadReapSignal::new(task_work));
521        Self {
522            id,
523            sched,
524            membarrier_identity: AtomicUsize::new(membarrier_identity.into_raw()),
525            runqueue_nodes: RunQueueNodeStorage::new(),
526            pi_wait_nodes: PiWaitNodeStorage::new(),
527            extension,
528            scheduler_tick_cpu_time,
529            scheduler_tick_work,
530            scheduler_tick_work_generation: AtomicU64::new(0),
531            scheduler_tick_observed_ns: AtomicU64::new(0),
532            scheduler_tick_work_node: InboxNode::new(InboxKind::TaskWork),
533            deadline_callback_node: InboxNode::new(InboxKind::TaskWork),
534            base_policy: AtomicPolicy::new(policy),
535            effective_policy: AtomicPolicy::new(policy),
536            effective_key_sequence: AtomicUsize::new(0),
537            effective_deadline_active: AtomicBool::new(false),
538            effective_deadline_ns: AtomicU64::new(0),
539            state: lifecycle,
540            reap_signal,
541            reap_gate: AtomicUsize::new(0),
542            scheduler_activity_gate: AtomicUsize::new(0),
543            scheduler_inbox_deliveries: AtomicUsize::new(0),
544            affinity_completion: ThreadAffinityCompletion::new(1),
545            park_generation: AtomicU64::new(0),
546            wake_cpu_hint: AtomicU32::new(u32::MAX),
547            wake_affinity: WakeAffinityState::new(),
548            affinity_update_node: InboxNode::new(InboxKind::OwnerControl),
549            deadline_refresh_node: InboxNode::new(InboxKind::OwnerControl),
550            wake_batch_next: AtomicPtr::new(core::ptr::null_mut()),
551            wake_batch_linked: AtomicBool::new(false),
552            sleep_timer: TaskDeadlineNode::for_thread(id),
553            deadline_cbs_timer: TaskDeadlineNode::deadline_cbs_for_thread(id),
554            deadline_zero_lag_timer: TaskDeadlineNode::deadline_zero_lag_for_thread(id),
555            sleep_timer_cpu: AtomicU32::new(u32::MAX),
556            sleep_timer_generation: AtomicU64::new(0),
557            migration_node: InboxNode::new(InboxKind::OwnerControl),
558            committed_runtime_ns: AtomicU64::new(0),
559            #[cfg(feature = "lockdep")]
560            held_locks: ThreadHeldLocks::new(),
561            pi_wait_state: PiWaitState::new(),
562        }
563    }
564
565    pub(crate) const fn runqueue_nodes(&self) -> &RunQueueNodeStorage {
566        &self.runqueue_nodes
567    }
568
569    pub(crate) const fn pi_wait_nodes(&self) -> &PiWaitNodeStorage {
570        &self.pi_wait_nodes
571    }
572
573    pub(crate) fn membarrier_identity(&self) -> AddressSpaceMembarrierId {
574        let raw = self.membarrier_identity.load(Ordering::Acquire);
575        // SAFETY: the thread's runtime resources keep the corresponding
576        // address-space generation alive until this value is replaced inside
577        // the owner-rq address-space transition.
578        unsafe { AddressSpaceMembarrierId::from_raw(raw) }
579    }
580
581    pub(crate) fn publish_membarrier_identity(&self, identity: AddressSpaceMembarrierId) {
582        self.membarrier_identity
583            .store(identity.into_raw(), Ordering::Release);
584    }
585
586    /// Mutates lockdep state owned by this currently executing thread.
587    ///
588    /// # Safety
589    ///
590    /// The caller must prove that this is the current thread and prevent local
591    /// IRQ entry, migration, and scheduler replacement for the complete call.
592    #[cfg(feature = "lockdep")]
593    pub(crate) unsafe fn with_held_locks<R>(
594        &self,
595        operation: impl FnOnce(&mut crate::sync::lockdep::HeldLockStack) -> R,
596    ) -> R {
597        unsafe { self.held_locks.with_mut(operation) }
598    }
599}
600
601mod lifecycle;
602mod policy;
603mod runtime_accounting;
604mod wake_affinity;
605mod wake_state;
606
607use policy::AtomicPolicy;
608use wake_affinity::WakeAffinityState;
609
610mod control;