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    /// Reports physical execution-resource reclamation independently of logical exit.
147    pub fn execution_reclaimed(&self) -> bool {
148        self.core.execution_reclaimed.load(Ordering::Acquire)
149    }
150
151    /// Returns the most recently published lifecycle state.
152    pub fn state(&self) -> ThreadState {
153        self.core.state()
154    }
155
156    /// Creates a non-owning lifecycle observer.
157    pub fn downgrade(&self) -> WeakThreadHandle {
158        WeakThreadHandle {
159            core: Arc::downgrade(&self.core),
160        }
161    }
162
163    /// Creates a direct wake handle that does not consult the thread registry.
164    pub fn wake_handle(&self) -> ThreadWakeHandle {
165        ThreadWakeHandle::from_core(Arc::clone(&self.core))
166    }
167
168    /// Returns the physical CPU that must cross a scheduler boundary.
169    ///
170    /// Unlike direct wake placement, this snapshot remains on the source CPU
171    /// until switch tail releases the outgoing context. A task-context caller
172    /// can therefore publish state, take this snapshot, and rendezvous with the
173    /// returned CPU; either the thread is still active there or it crossed a
174    /// scheduler boundary after the publication.
175    pub fn scheduler_fence_cpu(&self) -> Option<CpuId> {
176        self.core.sched().scheduler_fence_cpu()
177    }
178
179    /// Returns Linux `task_cpu()`: the last committed runqueue assignment.
180    ///
181    /// This remains the previous CPU while a task sleeps and changes to the
182    /// destination when an rq-to-rq migration commits. Physical execution
183    /// ownership is exposed separately by [`Self::scheduler_fence_cpu`].
184    /// A wake-placement hint is never reported as scheduler placement.
185    pub fn assigned_cpu(&self) -> Option<CpuId> {
186        self.core.assigned_cpu()
187    }
188
189    pub(crate) fn extension_view(&self) -> Option<crate::thread::ThreadExtensionView> {
190        self.core.extension_view()
191    }
192}
193
194impl Eq for ThreadHandle {}
195
196impl PartialEq for ThreadHandle {
197    fn eq(&self, other: &Self) -> bool {
198        self.id() == other.id()
199    }
200}
201
202/// A non-owning thread observer for ordinary task context.
203#[derive(Clone, Debug)]
204pub struct WeakThreadHandle {
205    core: Weak<ThreadCore>,
206}
207
208impl WeakThreadHandle {
209    /// Attempts to acquire a strong reference while the thread header is alive.
210    pub fn upgrade(&self) -> Option<ThreadHandle> {
211        let core = self.core.upgrade()?;
212        if !core.try_enter_weak_upgrade() {
213            return None;
214        }
215        let handle = ThreadHandle::from_core(core);
216        handle.core.exit_weak_upgrade();
217        Some(handle)
218    }
219}
220
221/// A stable direct wake header reference.
222///
223/// [`Self::wake`] uses non-sleeping scheduler transactions and is safe in hard
224/// IRQ context. Creating, cloning, and dropping this owning reference are task-context
225/// operations. A coroutine whose last raw-waker reference is released in hard IRQ
226/// defers only that zero-reference allocation to the typed task-system reaper.
227#[derive(Debug)]
228pub struct ThreadWakeHandle {
229    pub(crate) core: ManuallyDrop<Arc<ThreadCore>>,
230    reap_signal: Arc<ThreadReapSignal>,
231}
232
233impl Drop for ThreadWakeHandle {
234    fn drop(&mut self) {
235        unsafe {
236            // SAFETY: identical ownership rule to ThreadHandle::drop above.
237            ManuallyDrop::drop(&mut self.core);
238        }
239        self.reap_signal.release_external_lease();
240    }
241}
242
243impl Clone for ThreadWakeHandle {
244    fn clone(&self) -> Self {
245        let core = Arc::clone(&self.core);
246        let reap_signal = Arc::clone(&self.reap_signal);
247        reap_signal.acquire_external_lease();
248        Self {
249            core: ManuallyDrop::new(core),
250            reap_signal,
251        }
252    }
253}
254
255impl ThreadWakeHandle {
256    pub(crate) fn from_core(core: Arc<ThreadCore>) -> Self {
257        let reap_signal = Arc::clone(&core.reap_signal);
258        reap_signal.acquire_external_lease();
259        Self {
260            core: ManuallyDrop::new(core),
261            reap_signal,
262        }
263    }
264
265    /// Directly wakes the thread without allocating, sleeping, or invoking callbacks.
266    ///
267    /// This IRQ-safe operation may acquire the thread scheduler lock and the
268    /// selected CPU's raw runqueue lock.
269    pub fn wake(&self) -> WakeResult {
270        self.core.wake(WakeIntent::Normal)
271    }
272
273    /// Wakes from task context when the caller expects to block shortly.
274    ///
275    /// This is Linux's `WF_SYNC` contract. It remains a scheduling hint: the
276    /// waker commits the destination runqueue activation before returning.
277    pub fn wake_sync(&self) -> WakeResult {
278        debug_assert!(!crate::runtime::task_runtime::in_hard_irq());
279        self.core.wake(WakeIntent::Sync)
280    }
281
282    pub(crate) fn deliver_wait_claim_from_task(
283        &self,
284        claim: &crate::thread::WaitWakeClaim,
285        intent: WakeIntent,
286    ) -> crate::thread::WaitWakeDelivery {
287        crate::runtime::context::wake_wait_claim_from_task(&self.core, claim, intent)
288    }
289
290    /// Returns the thread that owns this wake header.
291    pub fn thread_id(&self) -> ThreadId {
292        self.core.id
293    }
294}
295
296impl ThreadCore {
297    fn wake(self: &Arc<Self>, intent: WakeIntent) -> WakeResult {
298        crate::runtime::context::wake_thread_from_current_cpu(self, intent)
299    }
300}
301
302#[derive(Clone, Copy, Debug, Eq, PartialEq)]
303pub(crate) enum WakeIntent {
304    Normal,
305    Sync,
306}
307
308impl WakeIntent {
309    pub(crate) const fn is_sync(self) -> bool {
310        matches!(self, Self::Sync)
311    }
312}
313
314/// Result of an IRQ-safe wake publication.
315#[derive(Clone, Copy, Debug, Eq, PartialEq)]
316pub enum WakeResult {
317    /// This call completed a logical wake transaction. The thread is runnable,
318    /// retained a park notification, or has owner-CPU activation committed.
319    Notified,
320    /// An unresolved park-transition notification already represents this event.
321    AlreadyPending,
322    /// The destination thread has exited, so the late wake is ignored.
323    Exited,
324    /// No scheduler-ready CPU is currently reachable for wake delivery.
325    Unavailable,
326}
327
328/// Runqueue-coherent snapshot of one thread's charged CPU runtime.
329#[derive(Clone, Copy, Debug, Eq, PartialEq)]
330pub struct ThreadRuntimeSnapshot {
331    charged_runtime_ns: u64,
332    running: bool,
333}
334
335impl ThreadRuntimeSnapshot {
336    /// Returns cumulative CPU runtime, including the current running residual.
337    pub const fn charged_runtime_ns(self) -> u64 {
338        self.charged_runtime_ns
339    }
340
341    /// Returns whether the snapshot included a live running residual.
342    pub const fn is_running(self) -> bool {
343        self.running
344    }
345}
346
347#[derive(Debug)]
348struct ThreadReapSignal {
349    exited: AtomicBool,
350    external_leases: AtomicUsize,
351    task_work: Option<Arc<TaskWorkDoorbell>>,
352}
353
354#[must_use = "the scheduler activity guard serializes owner delivery against exit"]
355pub(crate) struct ThreadSchedulerActivity<'thread> {
356    core: &'thread ThreadCore,
357    preempt: PreemptGuardToken,
358    _not_send: PhantomData<*mut ()>,
359}
360
361impl Drop for ThreadSchedulerActivity<'_> {
362    fn drop(&mut self) {
363        self.core.finish_scheduler_activity();
364        release_scheduler_preempt(self.preempt);
365    }
366}
367
368#[must_use = "the owned scheduler exit guard closes new activity until exit commits"]
369pub(crate) struct OwnedThreadSchedulerExit {
370    core: Arc<ThreadCore>,
371    preempt: PreemptGuardToken,
372    sealed: bool,
373    _not_send: PhantomData<*mut ()>,
374}
375
376impl OwnedThreadSchedulerExit {
377    pub(crate) fn seal(&mut self) {
378        self.sealed = true;
379    }
380}
381
382impl Drop for OwnedThreadSchedulerExit {
383    fn drop(&mut self) {
384        if !self.sealed {
385            self.core.reopen_scheduler_activity();
386        }
387        release_scheduler_preempt(self.preempt);
388    }
389}
390
391fn release_scheduler_preempt(token: PreemptGuardToken) {
392    if token.is_none() {
393        return;
394    }
395    // SAFETY: scheduler activity and exit guards are !Send and consume the
396    // exact token returned on this execution context after publishing their
397    // final gate state.
398    unsafe { task_runtime::preempt_guard_exit(token) };
399}
400
401#[must_use = "dropping the delivery lease makes an exited thread reapable"]
402pub(crate) struct ThreadSchedulerInboxDelivery<'thread> {
403    core: &'thread ThreadCore,
404}
405
406impl Drop for ThreadSchedulerInboxDelivery<'_> {
407    fn drop(&mut self) {
408        self.core.finish_scheduler_inbox_delivery();
409    }
410}
411
412impl ThreadReapSignal {
413    fn new(task_work: Option<Arc<TaskWorkDoorbell>>) -> Self {
414        Self {
415            exited: AtomicBool::new(false),
416            external_leases: AtomicUsize::new(0),
417            task_work,
418        }
419    }
420
421    fn mark_exited(&self) {
422        self.exited.store(true, Ordering::Release);
423    }
424
425    fn publish(&self) {
426        if let Some(task_work) = &self.task_work {
427            task_work.publish();
428        }
429    }
430
431    fn acquire_external_lease(&self) {
432        self.external_leases
433            .try_update(Ordering::AcqRel, Ordering::Acquire, |leases| {
434                leases.checked_add(1)
435            })
436            .expect("thread external-lifetime lease count overflow");
437    }
438
439    fn release_external_lease(&self) {
440        let previous = self.external_leases.fetch_sub(1, Ordering::AcqRel);
441        assert!(previous != 0, "unbalanced thread external-lifetime lease");
442        if previous == 1 && self.exited.load(Ordering::Acquire) {
443            self.publish();
444        }
445    }
446
447    fn external_lease_count(&self) -> usize {
448        self.external_leases.load(Ordering::Acquire)
449    }
450}
451
452#[derive(Debug)]
453pub(crate) struct ThreadCore {
454    id: ThreadId,
455    sched: Arc<ThreadSchedCell>,
456    // Stable `mm` identity. Registration bits remain rq-owned and are
457    // refreshed explicitly by the membarrier synchronization protocol.
458    membarrier_identity: AtomicUsize,
459    runqueue_nodes: RunQueueNodeStorage,
460    pi_wait_nodes: PiWaitNodeStorage,
461    // Immutable after publication. Every handle retaining this copy also pins
462    // the registry-owned extension destructor through the reaper Arc contract.
463    extension: Option<ThreadExtensionView>,
464    pub(crate) execution: Option<Arc<crate::thread::execution::ThreadExecution>>,
465    pub(crate) execution_reclaimed: AtomicBool,
466    scheduler_tick_cpu_time: Option<Arc<SchedulerTickCpuTime>>,
467    scheduler_tick_work: Option<SchedulerTickWork>,
468    scheduler_tick_work_generation: AtomicU64,
469    scheduler_tick_observed_ns: AtomicU64,
470    scheduler_tick_work_node: InboxNode,
471    deadline_callback_node: InboxNode,
472    base_policy: AtomicPolicy,
473    effective_policy: AtomicPolicy,
474    effective_key_sequence: AtomicUsize,
475    effective_deadline_active: AtomicBool,
476    effective_deadline_ns: AtomicU64,
477    state: Arc<ThreadLifecycle>,
478    reap_signal: Arc<ThreadReapSignal>,
479    reap_gate: AtomicUsize,
480    scheduler_activity_gate: AtomicUsize,
481    scheduler_inbox_deliveries: AtomicUsize,
482    pub(super) affinity_completion: ThreadAffinityCompletion,
483    park_generation: AtomicU64,
484    park_sequence: AtomicU64,
485    rt_lock_depth: AtomicUsize,
486    ordinary_park_generation: AtomicU64,
487    wake_cpu_hint: AtomicU32,
488    wake_affinity: WakeAffinityState,
489    affinity_update_node: InboxNode,
490    deadline_refresh_node: InboxNode,
491    wake_batch_next: AtomicPtr<ThreadCore>,
492    wake_batch_linked: AtomicBool,
493    sleep_timer: TaskDeadlineNode,
494    deadline_cbs_timer: TaskDeadlineNode,
495    deadline_zero_lag_timer: TaskDeadlineNode,
496    sleep_timer_cpu: AtomicU32,
497    sleep_timer_generation: AtomicU64,
498    migration_node: InboxNode,
499    committed_runtime_ns: AtomicU64,
500    #[cfg(feature = "lockdep")]
501    held_locks: ThreadHeldLocks,
502    pi_wait_state: PiWaitState,
503}
504
505pub(crate) struct ThreadCoreInit {
506    pub(crate) id: ThreadId,
507    pub(crate) policy: SchedulePolicy,
508    pub(crate) sched: Arc<ThreadSchedCell>,
509    pub(crate) extension: Option<ThreadExtensionView>,
510    pub(crate) execution: Option<Arc<crate::thread::execution::ThreadExecution>>,
511    pub(crate) scheduler_tick_cpu_time: Option<Arc<SchedulerTickCpuTime>>,
512    pub(crate) scheduler_tick_work: Option<SchedulerTickWork>,
513    pub(crate) membarrier_identity: AddressSpaceMembarrierId,
514    pub(crate) task_work: Option<Arc<TaskWorkDoorbell>>,
515}
516
517impl ThreadCore {
518    pub(crate) fn new(init: ThreadCoreInit) -> Result<Self, TaskError> {
519        let ThreadCoreInit {
520            id,
521            policy,
522            sched,
523            extension,
524            execution,
525            scheduler_tick_cpu_time,
526            scheduler_tick_work,
527            membarrier_identity,
528            task_work,
529        } = init;
530        debug_assert_eq!(id, sched.id());
531        let lifecycle = Arc::clone(sched.lifecycle());
532        let reap_signal = crate::thread::allocation::try_arc(ThreadReapSignal::new(task_work))?;
533        Ok(Self {
534            id,
535            sched,
536            membarrier_identity: AtomicUsize::new(membarrier_identity.into_raw()),
537            runqueue_nodes: RunQueueNodeStorage::new()?,
538            pi_wait_nodes: PiWaitNodeStorage::new()?,
539            extension,
540            execution,
541            execution_reclaimed: AtomicBool::new(false),
542            scheduler_tick_cpu_time,
543            scheduler_tick_work,
544            scheduler_tick_work_generation: AtomicU64::new(0),
545            scheduler_tick_observed_ns: AtomicU64::new(0),
546            scheduler_tick_work_node: InboxNode::new(InboxKind::TaskWork),
547            deadline_callback_node: InboxNode::new(InboxKind::TaskWork),
548            base_policy: AtomicPolicy::new(policy),
549            effective_policy: AtomicPolicy::new(policy),
550            effective_key_sequence: AtomicUsize::new(0),
551            effective_deadline_active: AtomicBool::new(false),
552            effective_deadline_ns: AtomicU64::new(0),
553            state: lifecycle,
554            reap_signal,
555            reap_gate: AtomicUsize::new(0),
556            scheduler_activity_gate: AtomicUsize::new(0),
557            scheduler_inbox_deliveries: AtomicUsize::new(0),
558            affinity_completion: ThreadAffinityCompletion::new(1),
559            park_generation: AtomicU64::new(0),
560            park_sequence: AtomicU64::new(0),
561            rt_lock_depth: AtomicUsize::new(0),
562            ordinary_park_generation: AtomicU64::new(0),
563            wake_cpu_hint: AtomicU32::new(u32::MAX),
564            wake_affinity: WakeAffinityState::new(),
565            affinity_update_node: InboxNode::new(InboxKind::OwnerControl),
566            deadline_refresh_node: InboxNode::new(InboxKind::OwnerControl),
567            wake_batch_next: AtomicPtr::new(core::ptr::null_mut()),
568            wake_batch_linked: AtomicBool::new(false),
569            sleep_timer: TaskDeadlineNode::for_thread(id),
570            deadline_cbs_timer: TaskDeadlineNode::deadline_cbs_for_thread(id),
571            deadline_zero_lag_timer: TaskDeadlineNode::deadline_zero_lag_for_thread(id),
572            sleep_timer_cpu: AtomicU32::new(u32::MAX),
573            sleep_timer_generation: AtomicU64::new(0),
574            migration_node: InboxNode::new(InboxKind::OwnerControl),
575            committed_runtime_ns: AtomicU64::new(0),
576            #[cfg(feature = "lockdep")]
577            held_locks: ThreadHeldLocks::new(),
578            pi_wait_state: PiWaitState::new(),
579        })
580    }
581
582    pub(crate) const fn runqueue_nodes(&self) -> &RunQueueNodeStorage {
583        &self.runqueue_nodes
584    }
585
586    pub(crate) const fn pi_wait_nodes(&self) -> &PiWaitNodeStorage {
587        &self.pi_wait_nodes
588    }
589
590    pub(crate) fn membarrier_identity(&self) -> AddressSpaceMembarrierId {
591        let raw = self.membarrier_identity.load(Ordering::Acquire);
592        // SAFETY: the thread's runtime resources keep the corresponding
593        // address-space generation alive until this value is replaced inside
594        // the owner-rq address-space transition.
595        unsafe { AddressSpaceMembarrierId::from_raw(raw) }
596    }
597
598    pub(crate) fn publish_membarrier_identity(&self, identity: AddressSpaceMembarrierId) {
599        self.membarrier_identity
600            .store(identity.into_raw(), Ordering::Release);
601    }
602
603    /// Mutates lockdep state owned by this currently executing thread.
604    ///
605    /// # Safety
606    ///
607    /// The caller must prove that this is the current thread and prevent local
608    /// IRQ entry, migration, and scheduler replacement for the complete call.
609    #[cfg(feature = "lockdep")]
610    pub(crate) unsafe fn with_held_locks<R>(
611        &self,
612        operation: impl FnOnce(&mut crate::sync::lockdep::HeldLockStack) -> R,
613    ) -> R {
614        unsafe { self.held_locks.with_mut(operation) }
615    }
616}
617
618mod lifecycle;
619mod policy;
620mod runtime_accounting;
621mod wake_affinity;
622mod wake_state;
623
624use policy::AtomicPolicy;
625use wake_affinity::WakeAffinityState;
626
627mod control;