Skip to main content

ax_task/sched/system/task_system/
model.rs

1//! Scheduler ownership model and bounded work accounting.
2
3use super::*;
4
5/// Failure returned by [`TaskSystem::reap_thread_handle`].
6///
7/// A failed registry transition returns ownership of the strong handle, keeping
8/// the registry generation pinned while the caller handles the error.
9#[derive(Debug, thiserror::Error)]
10#[error("{error}")]
11pub struct OwnedThreadReapError {
12    error: TaskError,
13    handle: ThreadHandle,
14}
15
16impl OwnedThreadReapError {
17    pub(super) const fn new(error: TaskError, handle: ThreadHandle) -> Self {
18        Self { error, handle }
19    }
20
21    /// Returns the underlying scheduler error.
22    pub const fn task_error(&self) -> TaskError {
23        self.error
24    }
25
26    /// Returns the still-valid handle.
27    pub fn into_retry_handle(self) -> ThreadHandle {
28        self.handle
29    }
30}
31
32/// One bounded pass performed by the dedicated task-work service thread.
33#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
34pub struct DeferredTaskWorkBatch {
35    pub(super) deadline_events: usize,
36    pub(super) deadline_callbacks: usize,
37    pub(super) scheduler_tick_events: usize,
38    pub(super) scheduler_tick_callbacks: usize,
39    pub(super) exit_callbacks: usize,
40    pub(super) reaped_threads: usize,
41    pub(super) coroutine_reclaims: usize,
42    pub(super) address_space_reclaims: usize,
43}
44
45impl DeferredTaskWorkBatch {
46    /// Returns the number of queue entries or resources consumed by this pass.
47    pub const fn processed(self) -> usize {
48        self.deadline_events
49            + self.scheduler_tick_events
50            + self.exit_callbacks
51            + self.reaped_threads
52            + self.coroutine_reclaims
53            + self.address_space_reclaims
54    }
55
56    /// Returns the number of Deadline extension callbacks invoked.
57    pub const fn deadline_callbacks(self) -> usize {
58        self.deadline_callbacks
59    }
60
61    /// Returns the number of scheduler-tick extension callbacks invoked.
62    pub const fn scheduler_tick_callbacks(self) -> usize {
63        self.scheduler_tick_callbacks
64    }
65
66    /// Returns zero-reference coroutine allocations reclaimed after hard IRQ.
67    pub const fn coroutine_reclaims(self) -> usize {
68        self.coroutine_reclaims
69    }
70
71    /// Returns active-mm ownership tokens whose final CPU lease was released.
72    pub const fn address_space_reclaims(self) -> usize {
73        self.address_space_reclaims
74    }
75
76    /// Returns whether another pass should run before the worker parks.
77    pub const fn made_progress(self) -> bool {
78        self.processed() != 0
79    }
80
81    /// Returns whether this pass consumed the complete shared caller budget.
82    pub const fn saturated(self, limit: usize) -> bool {
83        let capped_limit = if limit < crate::runtime::config::DEFAULT_BATCH_LIMIT {
84            limit
85        } else {
86            crate::runtime::config::DEFAULT_BATCH_LIMIT
87        };
88        capped_limit != 0 && self.processed() == capped_limit
89    }
90}
91
92/// Complete OS-independent scheduler instance.
93///
94/// No instance is stored globally. A runtime owns one pinned `TaskSystem` and
95/// passes explicit object references to the scheduler or exposes them through its
96/// trait-FFI facade.
97///
98/// IRQ and remote producers wake through
99/// [`ThreadWakeHandle::wake`](crate::thread::ThreadWakeHandle::wake). The wake path
100/// serializes thread state, selects an online destination, and activates the
101/// thread under that destination's IRQ-safe runqueue lock. Like Linux
102/// PREEMPT_RT with `TTWU_QUEUE` disabled, a remote waker waits for switch tail's
103/// `on_cpu` release before completing that direct activation.
104#[derive(Debug)]
105pub struct TaskSystem {
106    pub(super) config: TaskSystemConfig,
107    pub(super) cpu_remotes: Vec<Arc<CpuRemote>>,
108    // Cold-path order is registry/PI/admission -> root domain -> thread cell.
109    // Wake and placement hot paths lock thread state before the target runqueue.
110    pub(super) state: PreemptTicketLock<TaskSystemState>,
111    pub(super) root_domain: RootDomain,
112    pub(super) deferred_coroutine_reclaims: SchedulerInbox,
113    pub(super) deferred_deadline_callbacks: SchedulerInbox,
114    pub(super) deferred_scheduler_ticks: SchedulerInbox,
115    pub(super) task_work: Arc<TaskWorkDoorbell>,
116}
117
118#[derive(Clone, Copy, Debug, Eq, PartialEq)]
119pub(super) enum BalanceReason {
120    RtDeadlinePush,
121    IdlePull,
122    FairPeriodic,
123}
124
125/// Result of one opportunistic owner-to-owner balance attempt.
126///
127/// `Retry` means the transfer transaction observed a concurrent affinity,
128/// hotplug, or publication change and restored every local ownership record.
129/// It is not a failure of the already committed local scheduling decision.
130#[derive(Clone, Copy, Debug, Eq, PartialEq)]
131pub(super) enum BalanceTransferOutcome {
132    Migrated(ThreadId),
133    NoCandidate,
134    Retry,
135}
136
137impl BalanceTransferOutcome {
138    pub(super) const fn migrated(self) -> Option<ThreadId> {
139        match self {
140            Self::Migrated(thread) => Some(thread),
141            Self::NoCandidate | Self::Retry => None,
142        }
143    }
144}
145
146#[derive(Clone, Copy, Debug, Eq, PartialEq)]
147pub(super) enum FairBalanceResult {
148    Migrated(ThreadId),
149    Balanced,
150    Constrained,
151}
152
153impl FairBalanceResult {
154    pub(super) const fn migrated(self) -> Option<ThreadId> {
155        match self {
156            Self::Migrated(thread) => Some(thread),
157            Self::Balanced | Self::Constrained => None,
158        }
159    }
160}
161
162pub(super) const FAIR_BALANCE_BALANCED_BACKOFF_FACTOR: u64 = 2;
163pub(super) const FAIR_BALANCE_CONSTRAINED_BACKOFF_FACTOR: u64 = 64;
164
165#[derive(Clone, Copy, Debug, Eq, PartialEq)]
166pub(super) enum DeferredTaskWorkClass {
167    Deadline,
168    SchedulerTick,
169    Exit,
170    Reap,
171    Reclaim,
172}
173
174impl DeferredTaskWorkClass {
175    pub(super) const COUNT: usize = 5;
176
177    pub(super) const fn next(self) -> Self {
178        match self {
179            Self::Deadline => Self::SchedulerTick,
180            Self::SchedulerTick => Self::Exit,
181            Self::Exit => Self::Reap,
182            Self::Reap => Self::Reclaim,
183            Self::Reclaim => Self::Deadline,
184        }
185    }
186}
187
188/// Owns the unprocessed suffix of one already-detached owner inbox batch.
189///
190/// `SchedulerInbox::drain` releases every intrusive node before the caller
191/// interprets any message. If processing one message fails, this guard still
192/// consumes every later raw `Arc` payload and its scheduler-delivery lease.
193pub(super) struct DetachedOwnerMessageBatch<'batch> {
194    pub(super) messages: &'batch [InboxMessage],
195    pub(super) next: usize,
196}
197
198impl<'batch> DetachedOwnerMessageBatch<'batch> {
199    pub(super) const fn new(messages: &'batch [InboxMessage]) -> Self {
200        Self { messages, next: 0 }
201    }
202
203    pub(super) fn next(&mut self) -> Option<InboxMessage> {
204        let message = self.messages.get(self.next).copied()?;
205        self.next += 1;
206        Some(message)
207    }
208
209    pub(super) fn release(message: InboxMessage) {
210        if message.payload() == 0 {
211            return;
212        }
213        let core = unsafe {
214            // SAFETY: every non-zero owner message transfers exactly one
215            // `ThreadCore` Arc count into its payload. This detached batch owns
216            // that count even when normal message processing aborts early.
217            Arc::from_raw(ptr::with_exposed_provenance::<ThreadCore>(
218                message.payload(),
219            ))
220        };
221        let _delivery = core.accept_scheduler_inbox_delivery();
222    }
223}
224
225impl Drop for DetachedOwnerMessageBatch<'_> {
226    fn drop(&mut self) {
227        for &message in &self.messages[self.next..] {
228            Self::release(message);
229        }
230    }
231}
232
233#[derive(Clone, Copy, Debug, Eq, PartialEq)]
234pub(super) struct FairPolicyPlacement {
235    pub(super) source_virtual_time: u64,
236    pub(super) destination_virtual_time: u64,
237}