169 lines
5.4 KiB
Rust
169 lines
5.4 KiB
Rust
#[cfg(target_os = "linux")]
|
|
mod memory {
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
static MLOCKALL_CALLED: AtomicBool = AtomicBool::new(false);
|
|
static MLOCKALL_SUCCESS: AtomicBool = AtomicBool::new(false);
|
|
|
|
/// Locks all current and future memory pages to prevent page faults during RT execution.
|
|
/// Must be called BEFORE spawning any threads for maximum effectiveness.
|
|
pub fn lock_memory() -> bool {
|
|
if MLOCKALL_CALLED.swap(true, Ordering::SeqCst) {
|
|
return MLOCKALL_SUCCESS.load(Ordering::SeqCst);
|
|
}
|
|
|
|
let result = unsafe { libc::mlockall(libc::MCL_CURRENT | libc::MCL_FUTURE) };
|
|
|
|
if result == 0 {
|
|
MLOCKALL_SUCCESS.store(true, Ordering::SeqCst);
|
|
true
|
|
} else {
|
|
let errno = std::io::Error::last_os_error();
|
|
eprintln!("[cagire] mlockall failed: {errno}");
|
|
eprintln!("[cagire] Memory locking disabled. For best RT performance on Linux:");
|
|
eprintln!("[cagire] 1. Add user to 'audio' group: sudo usermod -aG audio $USER");
|
|
eprintln!("[cagire] 2. Add to /etc/security/limits.conf:");
|
|
eprintln!("[cagire] @audio - memlock unlimited");
|
|
eprintln!("[cagire] 3. Log out and back in");
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
pub use memory::lock_memory;
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
pub fn lock_memory() -> bool {
|
|
true
|
|
}
|
|
|
|
/// Attempts to set realtime scheduling priority for the current thread.
|
|
/// Returns true if RT priority was successfully set, false otherwise.
|
|
#[cfg(target_os = "macos")]
|
|
pub fn set_realtime_priority() -> bool {
|
|
// macOS: use THREAD_TIME_CONSTRAINT_POLICY for true RT scheduling.
|
|
// This is the same mechanism CoreAudio uses for its audio threads.
|
|
// SCHED_FIFO/RR require root on macOS, but time constraint policy does not.
|
|
unsafe {
|
|
let thread = libc::pthread_self();
|
|
|
|
#[repr(C)]
|
|
struct ThreadTimeConstraintPolicy {
|
|
period: u32,
|
|
computation: u32,
|
|
constraint: u32,
|
|
preemptible: i32,
|
|
}
|
|
|
|
const THREAD_TIME_CONSTRAINT_POLICY_ID: u32 = 2;
|
|
const THREAD_TIME_CONSTRAINT_POLICY_COUNT: u32 = 4;
|
|
|
|
// ~1ms period at ~1GHz mach_absolute_time ticks (typical for audio)
|
|
let policy = ThreadTimeConstraintPolicy {
|
|
period: 1_000_000,
|
|
computation: 500_000,
|
|
constraint: 1_000_000,
|
|
preemptible: 1,
|
|
};
|
|
|
|
extern "C" {
|
|
fn thread_policy_set(
|
|
thread: libc::pthread_t,
|
|
flavor: u32,
|
|
policy_info: *const ThreadTimeConstraintPolicy,
|
|
count: u32,
|
|
) -> i32;
|
|
}
|
|
|
|
let result = thread_policy_set(
|
|
thread,
|
|
THREAD_TIME_CONSTRAINT_POLICY_ID,
|
|
&policy,
|
|
THREAD_TIME_CONSTRAINT_POLICY_COUNT,
|
|
);
|
|
result == 0
|
|
}
|
|
}
|
|
|
|
/// Attempts to set realtime scheduling priority for the current thread.
|
|
/// Returns true if RT priority was successfully set, false otherwise.
|
|
///
|
|
/// On Linux, this requires either:
|
|
/// - CAP_SYS_NICE capability, or
|
|
/// - Configured rtprio limits in /etc/security/limits.conf:
|
|
/// @audio - rtprio 95
|
|
/// @audio - memlock unlimited
|
|
#[cfg(all(target_os = "linux", feature = "cli"))]
|
|
pub fn set_realtime_priority() -> bool {
|
|
use thread_priority::unix::{
|
|
set_thread_priority_and_policy, thread_native_id, NormalThreadSchedulePolicy,
|
|
RealtimeThreadSchedulePolicy, ThreadSchedulePolicy,
|
|
};
|
|
use thread_priority::ThreadPriority;
|
|
|
|
let tid = thread_native_id();
|
|
|
|
let fifo = ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo);
|
|
if set_thread_priority_and_policy(tid, ThreadPriority::Max, fifo).is_ok() {
|
|
return true;
|
|
}
|
|
|
|
let rr = ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::RoundRobin);
|
|
if set_thread_priority_and_policy(tid, ThreadPriority::Max, rr).is_ok() {
|
|
return true;
|
|
}
|
|
|
|
let _ = set_thread_priority_and_policy(
|
|
tid,
|
|
ThreadPriority::Max,
|
|
ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other),
|
|
);
|
|
|
|
unsafe {
|
|
libc::setpriority(libc::PRIO_PROCESS, 0, -20);
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
#[cfg(all(target_os = "linux", not(feature = "cli")))]
|
|
pub fn set_realtime_priority() -> bool {
|
|
false
|
|
}
|
|
|
|
#[cfg(not(any(unix, target_os = "windows")))]
|
|
pub fn set_realtime_priority() -> bool {
|
|
false
|
|
}
|
|
|
|
#[cfg(all(target_os = "windows", feature = "cli"))]
|
|
pub fn set_realtime_priority() -> bool {
|
|
use thread_priority::{set_current_thread_priority, ThreadPriority};
|
|
set_current_thread_priority(ThreadPriority::Max).is_ok()
|
|
}
|
|
|
|
#[cfg(all(target_os = "windows", not(feature = "cli")))]
|
|
pub fn set_realtime_priority() -> bool {
|
|
false
|
|
}
|
|
|
|
/// High-precision sleep using clock_nanosleep on Linux.
|
|
/// Uses monotonic clock for jitter-free sleeping.
|
|
#[cfg(target_os = "linux")]
|
|
pub fn precise_sleep_us(micros: u64) {
|
|
let duration_ns = micros * 1000;
|
|
let ts = libc::timespec {
|
|
tv_sec: (duration_ns / 1_000_000_000) as i64,
|
|
tv_nsec: (duration_ns % 1_000_000_000) as i64,
|
|
};
|
|
unsafe {
|
|
libc::clock_nanosleep(libc::CLOCK_MONOTONIC, 0, &ts, std::ptr::null_mut());
|
|
}
|
|
}
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
pub fn precise_sleep_us(micros: u64) {
|
|
std::thread::sleep(std::time::Duration::from_micros(micros));
|
|
}
|