zerodds_rt_linux/scheduler.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Public scheduler API. Platform routing by `target_os`.
5//!
6//! On Linux this module delegates to the internal `syscalls` module, where the
7//! `unsafe { libc::syscall(...) }` blocks are each documented individually.
8//! On other targets every function returns `Unsupported`,
9//! but the API compiles — the workspace builds on macOS/Windows.
10
11use std::io;
12
13/// Scheduler policy. Linux-specific (see `sched(7)`).
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum SchedulerProfile {
16 /// Linux `SCHED_OTHER` (CFS) — the default for all threads.
17 Default,
18 /// Linux `SCHED_FIFO` — strictly priority-based, no quantum.
19 /// `priority` is the value of `sched_priority` (1..=99, higher
20 /// beats lower; 0 is not allowed for FIFO/RR with
21 /// non-empty queues, is accepted by the kernel but treated as
22 /// a SCHED_OTHER fallback).
23 ///
24 /// Privileges: `CAP_SYS_NICE` from priority > 0 on; depending on
25 /// `RLIMIT_RTPRIO` also for priority 0.
26 RealtimeFifo {
27 /// Value of `sched_priority` (1..=99).
28 priority: u8,
29 },
30 /// Linux `SCHED_RR` — like FIFO, but with a round-robin quantum
31 /// per priority level.
32 RealtimeRoundRobin {
33 /// Value of `sched_priority` (1..=99).
34 priority: u8,
35 },
36 /// Linux `SCHED_DEADLINE` (CBS+EDF) — hard guarantees via a
37 /// (`runtime`, `deadline`, `period`) triple in nanoseconds.
38 /// See `sched_setattr(2)` for the spec. Conditions:
39 ///
40 /// * `runtime <= deadline <= period`
41 /// * The kernel computes a bandwidth reservation. EBUSY if
42 /// the global reservation blows the limit (default 95%).
43 ///
44 /// Privileges: always `CAP_SYS_NICE`. Forks must not
45 /// inherit (otherwise `EBUSY`).
46 Deadline {
47 /// Worst-case execution time per period (ns).
48 runtime_ns: u64,
49 /// Soft deadline from the start of the period (ns).
50 deadline_ns: u64,
51 /// Repetition period (ns).
52 period_ns: u64,
53 },
54}
55
56impl SchedulerProfile {
57 /// Applies the profile to the **calling thread**.
58 ///
59 /// # Errors
60 /// * `EPERM` (PermissionDenied) if the privileges are missing.
61 /// * `EINVAL` (InvalidInput) on inconsistent deadline values.
62 /// * `Unsupported` on non-Linux targets.
63 pub fn apply_to_current_thread(&self) -> io::Result<()> {
64 #[cfg(target_os = "linux")]
65 {
66 crate::syscalls::apply_scheduler(self)
67 }
68 #[cfg(not(target_os = "linux"))]
69 {
70 let _ = self;
71 Err(io::Error::new(
72 io::ErrorKind::Unsupported,
73 "SchedulerProfile::apply_to_current_thread requires Linux",
74 ))
75 }
76 }
77}
78
79/// Description of an active scheduling configuration.
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub struct RunningSchedulerInfo {
82 /// Selected policy.
83 pub kind: SchedulerKind,
84 /// `sched_priority`. Only relevant for FIFO/RR.
85 pub priority: u8,
86 /// `sched_runtime` (ns). Only relevant for Deadline.
87 pub runtime_ns: u64,
88 /// `sched_deadline` (ns). Only relevant for Deadline.
89 pub deadline_ns: u64,
90 /// `sched_period` (ns). Only relevant for Deadline.
91 pub period_ns: u64,
92}
93
94/// Classification of the Linux scheduler policy.
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub enum SchedulerKind {
97 /// CFS (`SCHED_OTHER`/`SCHED_BATCH`/`SCHED_IDLE`).
98 Other,
99 /// `SCHED_FIFO`.
100 Fifo,
101 /// `SCHED_RR`.
102 RoundRobin,
103 /// `SCHED_DEADLINE`.
104 Deadline,
105}
106
107/// Reads the current scheduler configuration of the calling thread.
108///
109/// Privilege-free.
110///
111/// # Errors
112/// Kernel error from `sched_getattr(2)`.
113/// `Unsupported` on non-Linux targets.
114pub fn current_scheduler() -> io::Result<RunningSchedulerInfo> {
115 #[cfg(target_os = "linux")]
116 {
117 crate::syscalls::read_scheduler()
118 }
119 #[cfg(not(target_os = "linux"))]
120 {
121 Err(io::Error::new(
122 io::ErrorKind::Unsupported,
123 "current_scheduler() requires Linux",
124 ))
125 }
126}
127
128#[cfg(test)]
129#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn profile_is_send_sync_clone_eq() {
135 fn assert_traits<T: Send + Sync + Clone + Copy + PartialEq>() {}
136 assert_traits::<SchedulerProfile>();
137 assert_traits::<RunningSchedulerInfo>();
138 assert_traits::<SchedulerKind>();
139 }
140
141 #[test]
142 fn profile_default_is_distinct_from_fifo() {
143 assert_ne!(
144 SchedulerProfile::Default,
145 SchedulerProfile::RealtimeFifo { priority: 1 }
146 );
147 }
148
149 #[test]
150 #[cfg(not(target_os = "linux"))]
151 fn apply_returns_unsupported_off_linux() {
152 let err = SchedulerProfile::Default
153 .apply_to_current_thread()
154 .unwrap_err();
155 assert_eq!(err.kind(), io::ErrorKind::Unsupported);
156 }
157
158 #[test]
159 #[cfg(not(target_os = "linux"))]
160 fn current_scheduler_returns_unsupported_off_linux() {
161 let err = current_scheduler().unwrap_err();
162 assert_eq!(err.kind(), io::ErrorKind::Unsupported);
163 }
164
165 #[test]
166 #[cfg(target_os = "linux")]
167 fn linux_default_apply_round_trips_via_getattr() {
168 SchedulerProfile::Default
169 .apply_to_current_thread()
170 .expect("apply default");
171 let info = current_scheduler().expect("read");
172 assert_eq!(info.kind, SchedulerKind::Other);
173 }
174
175 #[test]
176 #[cfg(target_os = "linux")]
177 fn linux_eperm_for_deadline_without_caps() {
178 // Without CAP_SYS_NICE, DEADLINE must return EPERM or EINVAL/EBUSY;
179 // under no circumstances a panic.
180 let res = SchedulerProfile::Deadline {
181 runtime_ns: 1_000_000,
182 deadline_ns: 5_000_000,
183 period_ns: 10_000_000,
184 }
185 .apply_to_current_thread();
186 if let Err(e) = res {
187 assert!(matches!(
188 e.kind(),
189 io::ErrorKind::PermissionDenied
190 | io::ErrorKind::InvalidInput
191 | io::ErrorKind::ResourceBusy
192 | io::ErrorKind::Other,
193 ));
194 }
195 }
196}