Skip to main content

ax_task/sched/system/task_system/
outcome.rs

1//! Typed results published across the scheduler/runtime boundary.
2
3use super::super::thread_sched::DeadlineActivity;
4use crate::{
5    runtime::switch::{RuntimeSwitchPlan, ThreadRuntimeBinding},
6    sched::{CpuId, SchedulePolicy},
7    thread::{SwitchReason, ThreadCore, ThreadExtensionView, ThreadId},
8};
9
10/// Result of one scheduler safe-point decision.
11#[derive(Debug)]
12pub struct ScheduleDecision {
13    pub(super) previous: Option<ThreadId>,
14    pub(super) next: ThreadId,
15    pub(super) runtime_switch_plan: Option<RuntimeSwitchPlan>,
16    pub(super) switch_reason: SwitchReason,
17    pub(super) timestamp_ns: u64,
18}
19
20/// Result of an explicit scheduler yield.
21#[derive(Debug)]
22pub enum YieldOutcome {
23    /// The current scheduling class kept the same dispatch selected.
24    Unchanged,
25    /// The yield selected a different execution context.
26    Switch(ScheduleDecision),
27}
28
29impl YieldOutcome {
30    pub(crate) const fn decision_mut(&mut self) -> Option<&mut ScheduleDecision> {
31        match self {
32            Self::Unchanged => None,
33            Self::Switch(decision) => Some(decision),
34        }
35    }
36}
37
38/// Callback work that becomes valid only after the incoming thread is current.
39///
40/// The facade completes this work after releasing runqueue locks and its
41/// CPU-local borrow, while retaining the scheduler's local IRQ exclusion.
42#[doc(hidden)]
43pub struct SwitchInCompletion {
44    thread: Option<ThreadId>,
45    policy: Option<SchedulePolicy>,
46    extension: Option<ThreadExtensionView>,
47    charged_runtime_ns: u64,
48    trace_wake: Option<fn()>,
49}
50
51impl SwitchInCompletion {
52    pub(crate) const NONE: Self = Self {
53        thread: None,
54        policy: None,
55        extension: None,
56        charged_runtime_ns: 0,
57        trace_wake: None,
58    };
59
60    pub(crate) fn for_core(
61        core: &ThreadCore,
62        policy: SchedulePolicy,
63        charged_runtime_ns: u64,
64    ) -> Self {
65        Self {
66            thread: Some(core.id()),
67            policy: Some(policy),
68            extension: core.extension_view(),
69            charged_runtime_ns,
70            trace_wake: None,
71        }
72    }
73
74    pub(crate) fn with_trace_wake(mut self, wake: Option<fn()>) -> Self {
75        self.trace_wake = wake;
76        self
77    }
78
79    #[doc(hidden)]
80    pub fn finish(self) {
81        if let (Some(thread), Some(policy), Some(extension)) =
82            (self.thread, self.policy, self.extension)
83        {
84            // SAFETY: TaskSystem creates this token after current publication,
85            // previous-binding withdrawal and handoff consumption. The facade
86            // drops its CpuLocal borrow before finishing this token, while
87            // retaining the scheduler IRQ baton.
88            unsafe {
89                (extension.ops().on_switch_in)(
90                    extension.data(),
91                    thread,
92                    policy,
93                    self.charged_runtime_ns,
94                )
95            };
96        }
97        // Kernel-only incoming threads must also complete the notification.
98        // Capture retained no task pointer; this static callback may now wake
99        // its service thread without recursively acquiring the outgoing rq.
100        if let Some(wake) = self.trace_wake {
101            wake();
102        }
103    }
104}
105
106/// Result of one bounded scheduler safe point.
107///
108/// This type deliberately keeps lifecycle deferral and bounded owner work
109/// separate from a scheduling decision. Callers must not infer either state
110/// from a boolean `need_resched` value or an absent decision.
111#[derive(Debug)]
112pub enum SchedulerOutcome {
113    /// No context switch or owner-only work remains from this pass.
114    Quiescent,
115    /// The current thread owns an in-flight park token and must finish it.
116    ParkingDeferred,
117    /// One bounded owner batch completed, with more work retained.
118    OwnerWorkPending,
119    /// The scheduler selected a next thread.
120    Decision(ScheduleDecision),
121}
122
123impl SchedulerOutcome {
124    /// Returns the scheduler decision, if this pass selected a thread.
125    pub const fn decision(&self) -> Option<&ScheduleDecision> {
126        match self {
127            Self::Decision(decision) => Some(decision),
128            Self::Quiescent | Self::ParkingDeferred | Self::OwnerWorkPending => None,
129        }
130    }
131
132    pub(crate) const fn decision_mut(&mut self) -> Option<&mut ScheduleDecision> {
133        match self {
134            Self::Decision(decision) => Some(decision),
135            Self::Quiescent | Self::ParkingDeferred | Self::OwnerWorkPending => None,
136        }
137    }
138
139    /// Returns whether the caller must finish a pending park handshake before
140    /// scheduler task-work callbacks may execute.
141    pub const fn parking_deferred(&self) -> bool {
142        matches!(self, Self::ParkingDeferred)
143    }
144
145    /// Returns whether more owner-only work remains for a later bounded safe point.
146    pub const fn owner_work_pending(&self) -> bool {
147        matches!(self, Self::OwnerWorkPending)
148    }
149}
150
151impl ScheduleDecision {
152    /// Returns the thread that stopped running, if any.
153    pub const fn previous(&self) -> Option<ThreadId> {
154        self.previous
155    }
156
157    /// Returns the selected thread or CPU idle thread.
158    pub const fn next(&self) -> ThreadId {
159        self.next
160    }
161
162    /// Returns why the previous thread relinquished the CPU.
163    pub const fn switch_reason(&self) -> SwitchReason {
164        self.switch_reason
165    }
166
167    /// Returns the runqueue timestamp that committed this decision.
168    pub const fn timestamp_ns(&self) -> u64 {
169        self.timestamp_ns
170    }
171
172    /// Returns whether the architecture execution context must change.
173    pub fn requires_context_switch(&self) -> bool {
174        self.previous() != Some(self.next())
175    }
176
177    pub(crate) fn take_runtime_switch_plan(&mut self) -> Option<RuntimeSwitchPlan> {
178        self.runtime_switch_plan.take()
179    }
180}
181
182#[derive(Clone, Copy, Debug)]
183pub(crate) struct SwitchEndpoint {
184    thread: ThreadId,
185    binding: ThreadRuntimeBinding,
186    address_space_identity: crate::runtime::resource::AddressSpaceMembarrierId,
187}
188
189impl SwitchEndpoint {
190    pub(crate) const fn new(
191        thread: ThreadId,
192        binding: ThreadRuntimeBinding,
193        address_space_identity: crate::runtime::resource::AddressSpaceMembarrierId,
194    ) -> Self {
195        Self {
196            thread,
197            binding,
198            address_space_identity,
199        }
200    }
201
202    pub(crate) const fn thread(self) -> ThreadId {
203        self.thread
204    }
205
206    pub(crate) const fn binding(self) -> ThreadRuntimeBinding {
207        self.binding
208    }
209
210    pub(crate) const fn address_space_identity(
211        self,
212    ) -> crate::runtime::resource::AddressSpaceMembarrierId {
213        self.address_space_identity
214    }
215}
216
217/// Result of charging one scheduler dispatch.
218#[derive(Clone, Copy, Debug, Eq, PartialEq)]
219pub struct ChargeOutcome {
220    pub(super) slice_expired: bool,
221    pub(super) deadline_overrun: bool,
222}
223
224/// Snapshot of one Deadline reservation's CBS and PI state.
225#[derive(Clone, Copy, Debug, Eq, PartialEq)]
226pub struct DeadlineRuntimeSnapshot {
227    pub(super) remaining_runtime_ns: u64,
228    pub(super) overruns: u64,
229    pub(super) pi_boosted: bool,
230    pub(super) donor: Option<ThreadId>,
231}
232
233/// Snapshot of a Deadline thread's GRUB ownership and zero-lag state.
234#[derive(Clone, Copy, Debug, Eq, PartialEq)]
235pub struct DeadlineActivitySnapshot {
236    pub(super) activity: DeadlineActivity,
237    pub(super) bandwidth_cpu: Option<CpuId>,
238    pub(super) zero_lag_ns: Option<u64>,
239}
240
241impl DeadlineActivitySnapshot {
242    /// Returns the GRUB state.
243    pub const fn activity(self) -> DeadlineActivity {
244        self.activity
245    }
246
247    /// Returns the runqueue owning this reservation's `this_bw` contribution.
248    pub const fn bandwidth_cpu(self) -> Option<CpuId> {
249        self.bandwidth_cpu
250    }
251
252    /// Returns the pending zero-lag boundary.
253    pub const fn zero_lag_ns(self) -> Option<u64> {
254        self.zero_lag_ns
255    }
256}
257
258impl DeadlineRuntimeSnapshot {
259    /// Returns the remaining CBS runtime.
260    pub const fn remaining_runtime_ns(self) -> u64 {
261        self.remaining_runtime_ns
262    }
263
264    /// Returns observed CBS overruns.
265    pub const fn overruns(self) -> u64 {
266        self.overruns
267    }
268
269    /// Reports whether the task currently executes with a donated Deadline
270    /// reservation, equivalent to Linux `is_dl_boosted()`.
271    pub const fn pi_boosted(self) -> bool {
272        self.pi_boosted
273    }
274
275    /// Returns the original Deadline reservation currently donated to the thread.
276    pub const fn donor(self) -> Option<ThreadId> {
277        self.donor
278    }
279}
280
281/// Result of one bounded owner-control drain.
282#[derive(Clone, Copy, Debug, Eq, PartialEq)]
283pub struct OwnerControlDrain {
284    pub(super) drained: usize,
285    pub(super) pending: bool,
286}
287
288impl OwnerControlDrain {
289    /// Returns the number of detached control messages consumed.
290    pub const fn drained(self) -> usize {
291        self.drained
292    }
293
294    /// Returns whether another bounded drain is required.
295    pub const fn pending(self) -> bool {
296        self.pending
297    }
298}
299
300impl ChargeOutcome {
301    /// Returns whether RR, fair service, or CBS budget reached its boundary.
302    pub const fn slice_expired(self) -> bool {
303        self.slice_expired
304    }
305
306    /// Returns whether CBS exhaustion entered a PI-critical rescue section.
307    pub const fn deadline_overrun(self) -> bool {
308        self.deadline_overrun
309    }
310}