Skip to main content

aria_primitives/
thread.rs

1//! A thread that must keep up with a realtime producer.
2//!
3//! An audio callback fills a bounded ring from a realtime thread. At the
4//! default class, a saturated CPU leaves the thread draining it unscheduled
5//! for longer than the ring holds.
6
7/// Lifts the calling thread to the user-interactive class. Failure is
8/// logged, not returned: a quiet machine keeps up anyway.
9#[cfg(target_vendor = "apple")]
10pub fn raise_scheduling_class() {
11    if let Err(error) = set_user_interactive() {
12        tracing::warn!(%error, "could not raise the thread's scheduling class");
13    }
14}
15
16/// This platform has no unprivileged way to lift a thread.
17#[cfg(not(target_vendor = "apple"))]
18pub fn raise_scheduling_class() {}
19
20/// A QoS band, not a time-constraint policy: the thread may also run
21/// inference and disk writes, which a realtime budget would punish.
22#[cfg(target_vendor = "apple")]
23#[allow(unsafe_code, reason = "pthread QoS has no safe binding")]
24fn set_user_interactive() -> std::io::Result<()> {
25    use libc::qos_class_t::QOS_CLASS_USER_INTERACTIVE;
26
27    /// Relative priority runs from 0 (top of band) down to
28    /// `QOS_MIN_RELATIVE_PRIORITY` (-15).
29    const TOP_OF_BAND: libc::c_int = 0;
30
31    // SAFETY: the call takes its arguments by value and changes only the
32    // calling thread's QoS.
33    let errno =
34        unsafe { libc::pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, TOP_OF_BAND) };
35    if errno == 0 {
36        Ok(())
37    } else {
38        Err(std::io::Error::from_raw_os_error(errno))
39    }
40}
41
42#[cfg(all(test, target_vendor = "apple"))]
43mod tests {
44    use libc::qos_class_t::{QOS_CLASS_UNSPECIFIED, QOS_CLASS_USER_INTERACTIVE};
45
46    use super::*;
47
48    #[test]
49    #[allow(unsafe_code, reason = "reading the QoS back has no safe binding")]
50    fn sets_the_calling_thread_to_user_interactive() {
51        std::thread::spawn(|| {
52            set_user_interactive().expect("set the QoS");
53            let mut class = QOS_CLASS_UNSPECIFIED;
54            let mut relative_priority = 0;
55            // SAFETY: the out-pointers address locals that outlive the call,
56            // and the thread reports the class it just set, a `qos_class_t`
57            // variant.
58            let errno = unsafe {
59                libc::pthread_get_qos_class_np(
60                    libc::pthread_self(),
61                    &mut class,
62                    &mut relative_priority,
63                )
64            };
65            assert_eq!(errno, 0);
66            assert_eq!(class as u32, QOS_CLASS_USER_INTERACTIVE as u32);
67        })
68        .join()
69        .expect("thread panicked");
70    }
71}