Skip to main content

ax_task/thread/
tick_work.rs

1//! Scheduler-tick-gated extension work executed in ordinary task context.
2
3use alloc::sync::Arc;
4use core::sync::atomic::{AtomicU64, Ordering};
5
6use super::ThreadId;
7
8/// Execution mode sampled by the periodic scheduler tick.
9///
10/// This is the OS-independent equivalent of Linux's `user_mode(regs)` result.
11/// The IRQ entry passes the saved-context classification into the scheduler
12/// tick so syscall boundaries do not need to publish a mirrored mode.
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum SchedulerTickMode {
15    /// The thread is executing userspace.
16    User,
17    /// The thread is executing kernel code.
18    System,
19}
20
21/// IRQ-safe tick-sampled user/system CPU time for one scheduler thread.
22///
23/// The object is retained by both the OS task and [`super::ThreadExtension`].
24/// Only the current CPU samples it from the periodic tick's saved trap origin.
25#[derive(Debug)]
26pub struct SchedulerTickCpuTime {
27    user_ns: AtomicU64,
28    system_ns: AtomicU64,
29}
30
31impl SchedulerTickCpuTime {
32    /// Creates an empty accounting stream.
33    pub const fn new() -> Self {
34        Self {
35            user_ns: AtomicU64::new(0),
36            system_ns: AtomicU64::new(0),
37        }
38    }
39
40    /// Returns the raw tick-accounted totals.
41    pub fn snapshot(&self) -> SchedulerTickCpuTimeSnapshot {
42        SchedulerTickCpuTimeSnapshot {
43            user_ns: self.user_ns.load(Ordering::Acquire),
44            system_ns: self.system_ns.load(Ordering::Acquire),
45        }
46    }
47
48    pub(crate) fn sample(&self, mode: SchedulerTickMode, tick_ns: u64) {
49        let total = match mode {
50            SchedulerTickMode::User => &self.user_ns,
51            SchedulerTickMode::System => &self.system_ns,
52        };
53        total
54            .try_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
55                Some(current.saturating_add(tick_ns))
56            })
57            .expect("infallible scheduler-tick CPU-time update failed");
58    }
59}
60
61impl Default for SchedulerTickCpuTime {
62    fn default() -> Self {
63        Self::new()
64    }
65}
66
67/// Coherent-enough raw totals from independent monotonic tick counters.
68#[derive(Clone, Copy, Debug, Eq, PartialEq)]
69pub struct SchedulerTickCpuTimeSnapshot {
70    user_ns: u64,
71    system_ns: u64,
72}
73
74impl SchedulerTickCpuTimeSnapshot {
75    /// Returns tick-sampled userspace CPU time.
76    pub const fn user_ns(self) -> u64 {
77        self.user_ns
78    }
79
80    /// Returns tick-sampled kernel CPU time.
81    pub const fn system_ns(self) -> u64 {
82        self.system_ns
83    }
84}
85
86/// Shared process or subsystem interest in scheduler-tick task work.
87///
88/// An operating system may share one gate across every scheduler thread that
89/// belongs to the same higher-level accounting domain. The hard-IRQ path only
90/// observes this atomic gate; it never invokes the associated callback.
91#[derive(Debug)]
92pub struct SchedulerTickGate {
93    state: AtomicU64,
94}
95
96impl SchedulerTickGate {
97    const ENABLED: u64 = 1;
98    const GENERATION_STEP: u64 = 2;
99
100    /// Creates a disabled scheduler-tick gate.
101    pub const fn new() -> Self {
102        Self {
103            state: AtomicU64::new(0),
104        }
105    }
106
107    /// Publishes whether scheduler ticks should enqueue deferred extension work.
108    ///
109    /// A disable transition invalidates every queued publication from the
110    /// previous enabled generation. It does not wait for a callback that an
111    /// ordinary-context consumer already claimed.
112    pub fn set_enabled(&self, enabled: bool) {
113        let mut observed = self.state.load(Ordering::Acquire);
114        loop {
115            if (observed & Self::ENABLED != 0) == enabled {
116                return;
117            }
118            let generation = observed
119                .checked_add(Self::GENERATION_STEP)
120                .expect("scheduler tick gate generation overflow");
121            let updated = (generation & !Self::ENABLED) | u64::from(enabled);
122            match self.state.compare_exchange_weak(
123                observed,
124                updated,
125                Ordering::AcqRel,
126                Ordering::Acquire,
127            ) {
128                Ok(_) => return,
129                Err(current) => observed = current,
130            }
131        }
132    }
133
134    fn enabled_generation(&self) -> Option<u64> {
135        let state = self.state.load(Ordering::Acquire);
136        (state & Self::ENABLED != 0).then_some(state)
137    }
138
139    fn generation_is_enabled(&self, generation: u64) -> bool {
140        self.state.load(Ordering::Acquire) == generation
141    }
142}
143
144impl Default for SchedulerTickGate {
145    fn default() -> Self {
146        Self::new()
147    }
148}
149
150/// Task-context callback selected by one scheduler-tick publication.
151///
152/// `observed_ns` is the latest monotonic scheduler-tick timestamp coalesced
153/// into this publication. It lets the callback account the carrier thread up
154/// to the IRQ observation boundary without running OS code in hard IRQ.
155///
156/// The callback returns [`SchedulerTickWorkDisposition::Retry`] only when a
157/// transient task-context serialization boundary prevented it from consuming
158/// the publication. The task system then republishes the same generation
159/// instead of spinning in one worker pass.
160#[derive(Clone, Copy, Debug, Eq, PartialEq)]
161pub enum SchedulerTickWorkDisposition {
162    /// The callback consumed the publication.
163    Complete,
164    /// The callback made no state change and needs a later task-context retry.
165    Retry,
166}
167
168/// Scheduler-tick task-work callback.
169pub type SchedulerTickTaskWork = unsafe extern "Rust" fn(
170    data: usize,
171    thread: ThreadId,
172    observed_ns: u64,
173) -> SchedulerTickWorkDisposition;
174
175#[derive(Clone, Debug)]
176pub(crate) struct SchedulerTickWork {
177    gate: Arc<SchedulerTickGate>,
178    callback: SchedulerTickTaskWork,
179}
180
181impl SchedulerTickWork {
182    pub(crate) const fn new(gate: Arc<SchedulerTickGate>, callback: SchedulerTickTaskWork) -> Self {
183        Self { gate, callback }
184    }
185
186    pub(crate) fn enabled_generation(&self) -> Option<u64> {
187        self.gate.enabled_generation()
188    }
189
190    pub(crate) fn generation_is_enabled(&self, generation: u64) -> bool {
191        self.gate.generation_is_enabled(generation)
192    }
193
194    pub(crate) unsafe fn invoke(
195        &self,
196        data: usize,
197        thread: ThreadId,
198        observed_ns: u64,
199    ) -> SchedulerTickWorkDisposition {
200        unsafe { (self.callback)(data, thread, observed_ns) }
201    }
202}
203
204/// One detached scheduler-tick publication owned by the task-work consumer.
205#[derive(Debug)]
206pub(crate) struct SchedulerTickWorkClaim {
207    work: SchedulerTickWork,
208    generation: u64,
209    observed_ns: u64,
210}
211
212impl SchedulerTickWorkClaim {
213    pub(crate) const fn new(work: SchedulerTickWork, generation: u64, observed_ns: u64) -> Self {
214        Self {
215            work,
216            generation,
217            observed_ns,
218        }
219    }
220
221    pub(crate) const fn generation(&self) -> u64 {
222        self.generation
223    }
224
225    pub(crate) fn generation_is_enabled(&self) -> bool {
226        self.work.generation_is_enabled(self.generation)
227    }
228
229    pub(crate) unsafe fn invoke(
230        &self,
231        data: usize,
232        thread: ThreadId,
233    ) -> SchedulerTickWorkDisposition {
234        unsafe { self.work.invoke(data, thread, self.observed_ns) }
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241
242    #[test]
243    fn periodic_tick_samples_only_the_published_execution_mode() {
244        let accounting = SchedulerTickCpuTime::new();
245
246        accounting.sample(SchedulerTickMode::User, 10);
247        accounting.sample(SchedulerTickMode::System, 10);
248
249        assert_eq!(
250            accounting.snapshot(),
251            SchedulerTickCpuTimeSnapshot {
252                user_ns: 10,
253                system_ns: 10,
254            }
255        );
256    }
257}