Skip to main content

qualia_core_db/platform/
platform_scheduler.rs

1//! Platform-aware thread QoS and core-affinity binding.
2//!
3//! Binds the calling thread to the most appropriate processor class for its
4//! workload so the OS scheduler makes the right placement decision:
5//!
6//! | Class              | macOS QoS                  | Linux             | Windows                    |
7//! |--------------------|----------------------------|-------------------|----------------------------|
8//! | UserInteractive    | QOS_CLASS_USER_INTERACTIVE | P-cores (affinity)| THREAD_PRIORITY_HIGHEST    |
9//! | UserInitiated      | QOS_CLASS_USER_INITIATED   | P-cores           | THREAD_PRIORITY_ABOVE_NORMAL|
10//! | Default            | QOS_CLASS_DEFAULT          | any               | THREAD_PRIORITY_NORMAL     |
11//! | Utility            | QOS_CLASS_UTILITY          | any               | THREAD_PRIORITY_BELOW_NORMAL|
12//! | Background         | QOS_CLASS_BACKGROUND       | E-cores (affinity)| THREAD_PRIORITY_IDLE       |
13//!
14//! **Apple Silicon AMP** (P-cores + E-cores) is the primary target.
15//! On Intel/AMD/ARM64 the distinction collapses to thread priority.
16
17#![cfg(not(target_arch = "wasm32"))]
18
19// ──────────────────────────────────────────────────────────────────────────────
20// Darwin QoS FFI
21// ──────────────────────────────────────────────────────────────────────────────
22
23#[cfg(target_os = "macos")]
24mod darwin_qos {
25    /// QoS class values from <sys/qos.h> (Darwin 18+).
26    pub const QOS_CLASS_USER_INTERACTIVE: u32 = 0x21; // 33
27    pub const QOS_CLASS_USER_INITIATED: u32 = 0x19; // 25
28    pub const QOS_CLASS_DEFAULT: u32 = 0x15; // 21
29    pub const QOS_CLASS_UTILITY: u32 = 0x11; // 17
30    pub const QOS_CLASS_BACKGROUND: u32 = 0x09; // 9
31
32    extern "C" {
33        /// Set the QoS class of the calling thread.
34        /// `relative_priority` must be in `[QOS_MIN_RELATIVE_PRIORITY, 0]`
35        /// where `QOS_MIN_RELATIVE_PRIORITY = -15`.
36        pub fn pthread_set_qos_class_self_np(
37            qos_class: u32,
38            relative_priority: libc::c_int,
39        ) -> libc::c_int;
40
41        /// Read back the QoS class of any thread (NULL → calling thread).
42        pub fn pthread_get_qos_class_np(
43            thread: libc::pthread_t,
44            qos_class_out: *mut u32,
45            relative_priority_out: *mut libc::c_int,
46        ) -> libc::c_int;
47    }
48}
49
50// ──────────────────────────────────────────────────────────────────────────────
51// Public API
52// ──────────────────────────────────────────────────────────────────────────────
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum QosClass {
56    /// LLM inference, active SPARQL queries, Phase 8 decode loop.
57    /// → Apple P-cores / Windows HIGHEST / Linux P-core affinity.
58    UserInteractive,
59    /// Active user requests, graph engine hot path.
60    UserInitiated,
61    /// Default — no preference.
62    Default,
63    /// Background sync, slow I/O.
64    Utility,
65    /// WAL flush, Merkle root computation, ambient orchestration.
66    /// → Apple E-cores / Windows IDLE / Linux E-core affinity.
67    Background,
68}
69
70#[derive(Debug)]
71pub enum SchedulerError {
72    Unsupported(String),
73    OsError(i32),
74}
75
76impl std::fmt::Display for SchedulerError {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        match self {
79            SchedulerError::Unsupported(m) => write!(f, "unsupported: {m}"),
80            SchedulerError::OsError(e) => write!(f, "OS error {e}"),
81        }
82    }
83}
84
85/// Bind the **calling thread** to `class`.
86///
87/// Returns `Ok(())` if the platform supports QoS binding, or
88/// `Err(SchedulerError::Unsupported)` on platforms that have no thread
89/// priority API (rare).
90pub fn bind_current_thread(class: QosClass) -> Result<(), SchedulerError> {
91    bind_macos(class)
92        .or_else(|_| bind_linux(class))
93        .or_else(|_| bind_windows(class))
94}
95
96/// Convenience wrapper: bind to UserInteractive (P-core / max priority).
97/// Call from the LLM inference thread and active SPARQL query thread.
98pub fn bind_inference_thread() {
99    let _ = bind_current_thread(QosClass::UserInteractive);
100}
101
102/// Convenience wrapper: bind to Background (E-core / idle priority).
103/// Call from WAL flush, Merkle DAG, ambient orchestration threads.
104pub fn bind_background_thread() {
105    let _ = bind_current_thread(QosClass::Background);
106}
107
108// ──────────────────────────────────────────────────────────────────────────────
109// macOS implementation
110// ──────────────────────────────────────────────────────────────────────────────
111
112fn bind_macos(#[allow(unused_variables)] class: QosClass) -> Result<(), SchedulerError> {
113    #[cfg(target_os = "macos")]
114    {
115        use darwin_qos::*;
116        let qos = match class {
117            QosClass::UserInteractive => QOS_CLASS_USER_INTERACTIVE,
118            QosClass::UserInitiated => QOS_CLASS_USER_INITIATED,
119            QosClass::Default => QOS_CLASS_DEFAULT,
120            QosClass::Utility => QOS_CLASS_UTILITY,
121            QosClass::Background => QOS_CLASS_BACKGROUND,
122        };
123        // SAFETY: pthread_set_qos_class_self_np is safe to call from any thread.
124        let rc = unsafe { pthread_set_qos_class_self_np(qos, 0) };
125        if rc == 0 {
126            return Ok(());
127        }
128        return Err(SchedulerError::OsError(rc));
129    }
130    #[cfg(not(target_os = "macos"))]
131    Err(SchedulerError::Unsupported(
132        "macOS QoS not available on this platform".into(),
133    ))
134}
135
136// ──────────────────────────────────────────────────────────────────────────────
137// Linux implementation — core_affinity for asymmetric multiprocessing
138// ──────────────────────────────────────────────────────────────────────────────
139
140fn bind_linux(#[allow(unused_variables)] class: QosClass) -> Result<(), SchedulerError> {
141    #[cfg(target_os = "linux")]
142    {
143        use core_affinity::CoreId;
144
145        let all_cores = core_affinity::get_core_ids()
146            .ok_or_else(|| SchedulerError::Unsupported("core_affinity unavailable".into()))?;
147
148        if all_cores.is_empty() {
149            return Err(SchedulerError::Unsupported("no cores found".into()));
150        }
151
152        // Heuristic for big.LITTLE / Alder Lake / Sapphire Rapids:
153        // Lower-numbered cores are P-cores; higher-numbered are E-cores.
154        // We split at the midpoint as a conservative estimate.
155        let mid = all_cores.len() / 2;
156        let target = match class {
157            QosClass::UserInteractive | QosClass::UserInitiated => {
158                // P-cores: first half (or all if symmetric)
159                &all_cores[..mid.max(1)]
160            }
161            QosClass::Background | QosClass::Utility => {
162                // E-cores: second half (or all if symmetric)
163                &all_cores[mid..]
164            }
165            QosClass::Default => &all_cores[..],
166        };
167
168        // Pin to the first core in the target set; real production code would
169        // use `sched_setaffinity` with a full mask, but core_affinity exposes
170        // single-core pinning which is sufficient for the inference split.
171        if let Some(core) = target.first() {
172            if core_affinity::set_for_current(*core) {
173                return Ok(());
174            }
175        }
176
177        // Also set Linux thread nice / scheduling policy.
178        let nice_val: libc::c_int = match class {
179            QosClass::UserInteractive => -10,
180            QosClass::UserInitiated => -5,
181            QosClass::Default => 0,
182            QosClass::Utility => 5,
183            QosClass::Background => 19,
184        };
185        // SAFETY: getpid() always succeeds; setpriority is safe with valid args.
186        unsafe {
187            libc::setpriority(libc::PRIO_PROCESS, 0, nice_val);
188        }
189        return Ok(());
190    }
191    #[cfg(not(target_os = "linux"))]
192    Err(SchedulerError::Unsupported(
193        "Linux sched_setaffinity not available".into(),
194    ))
195}
196
197// ──────────────────────────────────────────────────────────────────────────────
198// Windows implementation — SetThreadPriority
199// ──────────────────────────────────────────────────────────────────────────────
200
201fn bind_windows(class: QosClass) -> Result<(), SchedulerError> {
202    #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
203    {
204        use windows::Win32::System::Threading::{
205            GetCurrentThread, SetThreadPriority, THREAD_PRIORITY_ABOVE_NORMAL,
206            THREAD_PRIORITY_BELOW_NORMAL, THREAD_PRIORITY_HIGHEST, THREAD_PRIORITY_IDLE,
207            THREAD_PRIORITY_NORMAL,
208        };
209        let priority = match class {
210            QosClass::UserInteractive => THREAD_PRIORITY_HIGHEST,
211            QosClass::UserInitiated => THREAD_PRIORITY_ABOVE_NORMAL,
212            QosClass::Default => THREAD_PRIORITY_NORMAL,
213            QosClass::Utility => THREAD_PRIORITY_BELOW_NORMAL,
214            QosClass::Background => THREAD_PRIORITY_IDLE,
215        };
216        // SAFETY: GetCurrentThread() returns a pseudo-handle that is always valid.
217        let ok = unsafe { SetThreadPriority(GetCurrentThread(), priority) };
218        return if ok.is_ok() {
219            Ok(())
220        } else {
221            Err(SchedulerError::OsError(
222                unsafe { windows::Win32::Foundation::GetLastError() }.0 as i32,
223            ))
224        };
225    }
226    #[cfg(not(all(target_os = "windows", target_arch = "x86_64")))]
227    Err(SchedulerError::Unsupported(
228        "Windows SetThreadPriority not available".into(),
229    ))
230}
231
232// ──────────────────────────────────────────────────────────────────────────────
233// Query current QoS (macOS only)
234// ──────────────────────────────────────────────────────────────────────────────
235
236/// Read the QoS class of the calling thread.  Returns `None` on non-macOS.
237pub fn current_qos_class() -> Option<u32> {
238    #[cfg(target_os = "macos")]
239    {
240        let mut cls: u32 = 0;
241        let mut rel: libc::c_int = 0;
242        // SAFETY: pthread_get_qos_class_np with null thread → calling thread.
243        let rc = unsafe {
244            darwin_qos::pthread_get_qos_class_np(libc::pthread_self(), &mut cls, &mut rel)
245        };
246        if rc == 0 {
247            Some(cls)
248        } else {
249            None
250        }
251    }
252    #[cfg(not(target_os = "macos"))]
253    None
254}
255
256// ──────────────────────────────────────────────────────────────────────────────
257// Tests
258// ──────────────────────────────────────────────────────────────────────────────
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263
264    #[test]
265    fn test_bind_does_not_panic() {
266        // On every platform this must complete without panic.
267        // It may return Err(Unsupported) on platforms with no QoS API.
268        let _ = bind_current_thread(QosClass::UserInteractive);
269        let _ = bind_current_thread(QosClass::Background);
270    }
271
272    #[test]
273    fn test_inference_background_helpers() {
274        bind_inference_thread();
275        bind_background_thread();
276    }
277
278    #[test]
279    fn test_current_qos_no_panic() {
280        let _ = current_qos_class(); // May return None on non-macOS
281    }
282}