use thread_priority::{set_current_thread_priority, ThreadPriority, ThreadPriorityValue};
use tracing::info;
const DEFAULT_THREAD_PRIORITY: u8 = 70;
pub fn callback_thread_priority() -> ThreadPriorityValue {
parse_thread_priority(std::env::var("MTRACK_THREAD_PRIORITY").ok().as_deref())
}
fn parse_thread_priority(value: Option<&str>) -> ThreadPriorityValue {
value
.and_then(|v| {
let n = v.parse::<u8>().ok()?;
(n < 100).then(|| ThreadPriorityValue::try_from(n).ok())?
})
.unwrap_or_else(|| ThreadPriorityValue::try_from(DEFAULT_THREAD_PRIORITY).unwrap())
}
pub(crate) fn env_flag(name: &str) -> bool {
std::env::var(name)
.ok()
.map(|v| {
v == "1"
|| v.eq_ignore_ascii_case("true")
|| v.eq_ignore_ascii_case("yes")
|| v.eq_ignore_ascii_case("on")
})
.unwrap_or(false)
}
pub fn rt_audio_enabled() -> bool {
!env_flag("MTRACK_DISABLE_RT_AUDIO")
}
pub fn promote_to_realtime(
priority: ThreadPriorityValue,
rt_enabled: bool,
priority_set: &mut bool,
) {
if *priority_set {
return;
}
let tp = ThreadPriority::Crossplatform(priority);
let _ = set_current_thread_priority(tp);
#[cfg(unix)]
if rt_enabled {
use thread_priority::unix::{
set_thread_priority_and_policy, thread_native_id, RealtimeThreadSchedulePolicy,
ThreadSchedulePolicy,
};
#[cfg(target_os = "macos")]
let tp = {
let raw: u8 = priority.into();
let clamped = raw.clamp(15, 47);
ThreadPriority::Crossplatform(ThreadPriorityValue::try_from(clamped).unwrap())
};
let tid = thread_native_id();
match set_thread_priority_and_policy(
tid,
tp,
ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo),
) {
Ok(()) => {
info!("Enabled RT SCHED_FIFO for real-time thread");
}
Err(e) => {
#[cfg(target_os = "macos")]
tracing::debug!(
error = %e,
"SCHED_FIFO unavailable (expected on macOS CoreAudio threads)"
);
#[cfg(not(target_os = "macos"))]
tracing::warn!(
error = %e,
"Failed to set RT SCHED_FIFO for real-time thread"
);
}
}
}
*priority_set = true;
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn env_flag_true_values() {
for val in &[
"1", "true", "TRUE", "True", "yes", "YES", "Yes", "on", "ON", "On",
] {
std::env::set_var("MTRACK_TEST_FLAG", val);
assert!(env_flag("MTRACK_TEST_FLAG"), "expected true for {:?}", val);
}
std::env::remove_var("MTRACK_TEST_FLAG");
}
#[test]
fn env_flag_false_values() {
for val in &["0", "false", "no", "off", "", "maybe"] {
std::env::set_var("MTRACK_TEST_FLAG_F", val);
assert!(
!env_flag("MTRACK_TEST_FLAG_F"),
"expected false for {:?}",
val
);
}
std::env::remove_var("MTRACK_TEST_FLAG_F");
}
#[test]
fn env_flag_unset() {
std::env::remove_var("MTRACK_TEST_FLAG_UNSET");
assert!(!env_flag("MTRACK_TEST_FLAG_UNSET"));
}
#[test]
fn parse_priority_default() {
let prio = parse_thread_priority(None);
assert_eq!(
prio,
ThreadPriorityValue::try_from(DEFAULT_THREAD_PRIORITY).unwrap()
);
}
#[test]
fn parse_priority_custom() {
let prio = parse_thread_priority(Some("50"));
assert_eq!(prio, ThreadPriorityValue::try_from(50u8).unwrap());
}
#[test]
fn parse_priority_zero() {
let prio = parse_thread_priority(Some("0"));
assert_eq!(prio, ThreadPriorityValue::try_from(0u8).unwrap());
}
#[test]
fn parse_priority_max_valid() {
let prio = parse_thread_priority(Some("99"));
assert_eq!(prio, ThreadPriorityValue::try_from(99u8).unwrap());
}
#[test]
fn parse_priority_out_of_range() {
let prio = parse_thread_priority(Some("100"));
assert_eq!(
prio,
ThreadPriorityValue::try_from(DEFAULT_THREAD_PRIORITY).unwrap()
);
}
#[test]
fn parse_priority_invalid_string() {
let prio = parse_thread_priority(Some("not_a_number"));
assert_eq!(
prio,
ThreadPriorityValue::try_from(DEFAULT_THREAD_PRIORITY).unwrap()
);
}
#[test]
fn parse_priority_empty_string() {
let prio = parse_thread_priority(Some(""));
assert_eq!(
prio,
ThreadPriorityValue::try_from(DEFAULT_THREAD_PRIORITY).unwrap()
);
}
#[test]
fn promote_to_realtime_idempotent() {
let prio = ThreadPriorityValue::try_from(47u8).unwrap();
let mut priority_set = false;
promote_to_realtime(prio, false, &mut priority_set);
assert!(priority_set);
promote_to_realtime(prio, false, &mut priority_set);
assert!(priority_set);
}
#[test]
fn callback_thread_priority_respects_env() {
std::env::remove_var("MTRACK_THREAD_PRIORITY");
let _prio = callback_thread_priority();
std::env::set_var("MTRACK_THREAD_PRIORITY", "42");
let prio = callback_thread_priority();
std::env::remove_var("MTRACK_THREAD_PRIORITY");
assert_eq!(prio, ThreadPriorityValue::try_from(42u8).unwrap());
}
#[test]
fn rt_audio_enabled_respects_env() {
std::env::remove_var("MTRACK_DISABLE_RT_AUDIO");
assert!(rt_audio_enabled(), "should be enabled when env var unset");
std::env::set_var("MTRACK_DISABLE_RT_AUDIO", "1");
assert!(!rt_audio_enabled(), "should be disabled when env var is 1");
std::env::remove_var("MTRACK_DISABLE_RT_AUDIO");
}
#[test]
fn promote_with_rt_enabled() {
let prio = ThreadPriorityValue::try_from(47u8).unwrap();
let mut priority_set = false;
promote_to_realtime(prio, true, &mut priority_set);
assert!(priority_set);
}
}