Again
This commit is contained in:
@@ -554,6 +554,10 @@ fn load_icon() -> egui::IconData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> eframe::Result<()> {
|
fn main() -> eframe::Result<()> {
|
||||||
|
// Lock memory BEFORE any threads are spawned to prevent page faults in RT context
|
||||||
|
#[cfg(unix)]
|
||||||
|
cagire::engine::realtime::lock_memory();
|
||||||
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
let options = NativeOptions {
|
let options = NativeOptions {
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ use crossbeam_channel::{Receiver, RecvTimeoutError, Sender};
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::BinaryHeap;
|
use std::collections::BinaryHeap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use super::link::LinkState;
|
use super::link::LinkState;
|
||||||
use super::sequencer::{set_realtime_priority, AudioCommand, MidiCommand};
|
use super::realtime::{precise_sleep_us, set_realtime_priority};
|
||||||
|
use super::sequencer::{AudioCommand, MidiCommand};
|
||||||
use super::timing::SyncTime;
|
use super::timing::SyncTime;
|
||||||
|
|
||||||
/// A command scheduled for dispatch at a specific time.
|
/// A command scheduled for dispatch at a specific time.
|
||||||
@@ -103,23 +103,6 @@ pub fn dispatcher_loop(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// High-precision sleep using clock_nanosleep on Linux
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
fn precise_sleep(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"))]
|
|
||||||
fn precise_sleep(micros: u64) {
|
|
||||||
thread::sleep(Duration::from_micros(micros));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wait until the target time for dispatch.
|
/// Wait until the target time for dispatch.
|
||||||
/// With RT priority: spin-wait for precision
|
/// With RT priority: spin-wait for precision
|
||||||
@@ -136,7 +119,7 @@ fn wait_until_dispatch(target_us: SyncTime, link: &LinkState, has_rt: bool) {
|
|||||||
} else {
|
} else {
|
||||||
// Without RT priority: sleep (spin-waiting is counterproductive)
|
// Without RT priority: sleep (spin-waiting is counterproductive)
|
||||||
if remaining > 0 {
|
if remaining > 0 {
|
||||||
precise_sleep(remaining);
|
precise_sleep_us(remaining);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
mod audio;
|
mod audio;
|
||||||
mod dispatcher;
|
mod dispatcher;
|
||||||
mod link;
|
mod link;
|
||||||
|
pub mod realtime;
|
||||||
pub mod sequencer;
|
pub mod sequencer;
|
||||||
mod timing;
|
mod timing;
|
||||||
|
|
||||||
|
|||||||
114
src/engine/realtime.rs
Normal file
114
src/engine/realtime.rs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
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.
|
||||||
|
/// Returns true if mlockall succeeded, false otherwise (which is common without rtprio).
|
||||||
|
#[cfg(unix)]
|
||||||
|
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 {
|
||||||
|
// Get the actual error for better diagnostics
|
||||||
|
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(not(unix))]
|
||||||
|
pub fn lock_memory() -> bool {
|
||||||
|
// Windows: VirtualLock exists but isn't typically needed for audio
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if memory locking is active.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn is_memory_locked() -> bool {
|
||||||
|
MLOCKALL_SUCCESS.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(unix)]
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Try SCHED_FIFO first (requires CAP_SYS_NICE on Linux)
|
||||||
|
let fifo = ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo);
|
||||||
|
if set_thread_priority_and_policy(tid, ThreadPriority::Max, fifo).is_ok() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try SCHED_RR (round-robin realtime, sometimes works without caps)
|
||||||
|
let rr = ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::RoundRobin);
|
||||||
|
if set_thread_priority_and_policy(tid, ThreadPriority::Max, rr).is_ok() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to highest normal priority (SCHED_OTHER)
|
||||||
|
let _ = set_thread_priority_and_policy(
|
||||||
|
tid,
|
||||||
|
ThreadPriority::Max,
|
||||||
|
ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Also try nice -20 on Linux
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
unsafe {
|
||||||
|
libc::setpriority(libc::PRIO_PROCESS, 0, -20);
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
pub fn set_realtime_priority() -> bool {
|
||||||
|
use thread_priority::{set_current_thread_priority, ThreadPriority};
|
||||||
|
set_current_thread_priority(ThreadPriority::Max).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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));
|
||||||
|
}
|
||||||
@@ -6,13 +6,9 @@ use std::sync::atomic::AtomicU32;
|
|||||||
use std::sync::atomic::{AtomicI64, AtomicU64};
|
use std::sync::atomic::{AtomicI64, AtomicU64};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread::{self, JoinHandle};
|
use std::thread::{self, JoinHandle};
|
||||||
use std::time::Duration;
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
use thread_priority::set_current_thread_priority;
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use thread_priority::ThreadPriority;
|
|
||||||
|
|
||||||
use super::dispatcher::{dispatcher_loop, DispatchCommand, TimedCommand};
|
use super::dispatcher::{dispatcher_loop, DispatchCommand, TimedCommand};
|
||||||
|
use super::realtime::{precise_sleep_us, set_realtime_priority};
|
||||||
use super::{micros_until_next_substep, substeps_crossed, LinkState, StepTiming, SyncTime};
|
use super::{micros_until_next_substep, substeps_crossed, LinkState, StepTiming, SyncTime};
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
CcAccess, Dictionary, ExecutionTrace, Rng, ScriptEngine, StepContext, Value, Variables,
|
CcAccess, Dictionary, ExecutionTrace, Rng, ScriptEngine, StepContext, Value, Variables,
|
||||||
@@ -1188,74 +1184,6 @@ fn sequencer_loop(
|
|||||||
/// spinning is counterproductive and we sleep the entire duration.
|
/// spinning is counterproductive and we sleep the entire duration.
|
||||||
const SPIN_THRESHOLD_US: SyncTime = 100;
|
const SPIN_THRESHOLD_US: SyncTime = 100;
|
||||||
|
|
||||||
/// High-precision sleep using clock_nanosleep on Linux
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
fn precise_sleep(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"))]
|
|
||||||
fn precise_sleep(micros: u64) {
|
|
||||||
thread::sleep(Duration::from_micros(micros));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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(unix)]
|
|
||||||
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();
|
|
||||||
|
|
||||||
// Try SCHED_FIFO first (requires CAP_SYS_NICE on Linux)
|
|
||||||
let fifo = ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo);
|
|
||||||
if set_thread_priority_and_policy(tid, ThreadPriority::Max, fifo).is_ok() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try SCHED_RR (round-robin realtime, sometimes works without caps)
|
|
||||||
let rr = ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::RoundRobin);
|
|
||||||
if set_thread_priority_and_policy(tid, ThreadPriority::Max, rr).is_ok() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to highest normal priority (SCHED_OTHER)
|
|
||||||
let _ = set_thread_priority_and_policy(
|
|
||||||
tid,
|
|
||||||
ThreadPriority::Max,
|
|
||||||
ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Also try nice -20 on Linux
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
unsafe {
|
|
||||||
libc::setpriority(libc::PRIO_PROCESS, 0, -20);
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
pub fn set_realtime_priority() -> bool {
|
|
||||||
set_current_thread_priority(ThreadPriority::Max).is_ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Two-phase wait: sleep most of the time, optionally spin-wait for final precision.
|
/// Two-phase wait: sleep most of the time, optionally spin-wait for final precision.
|
||||||
/// With RT priority: sleep + spin for precision
|
/// With RT priority: sleep + spin for precision
|
||||||
@@ -1267,7 +1195,7 @@ fn wait_until(target_us: SyncTime, link: &LinkState, has_rt_priority: bool) {
|
|||||||
if has_rt_priority {
|
if has_rt_priority {
|
||||||
// With RT priority: sleep most, spin for final precision
|
// With RT priority: sleep most, spin for final precision
|
||||||
if remaining > SPIN_THRESHOLD_US {
|
if remaining > SPIN_THRESHOLD_US {
|
||||||
precise_sleep(remaining - SPIN_THRESHOLD_US);
|
precise_sleep_us(remaining - SPIN_THRESHOLD_US);
|
||||||
}
|
}
|
||||||
while (link.clock_micros() as SyncTime) < target_us {
|
while (link.clock_micros() as SyncTime) < target_us {
|
||||||
std::hint::spin_loop();
|
std::hint::spin_loop();
|
||||||
@@ -1275,7 +1203,7 @@ fn wait_until(target_us: SyncTime, link: &LinkState, has_rt_priority: bool) {
|
|||||||
} else {
|
} else {
|
||||||
// Without RT priority: sleep the entire time (spin-waiting is counterproductive)
|
// Without RT priority: sleep the entire time (spin-waiting is counterproductive)
|
||||||
if remaining > 0 {
|
if remaining > 0 {
|
||||||
precise_sleep(remaining);
|
precise_sleep_us(remaining);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ struct Args {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
fn main() -> io::Result<()> {
|
||||||
|
// Lock memory BEFORE any threads are spawned to prevent page faults in RT context
|
||||||
|
#[cfg(unix)]
|
||||||
|
engine::realtime::lock_memory();
|
||||||
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
let settings = Settings::load();
|
let settings = Settings::load();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user