Still searching...
This commit is contained in:
@@ -9,13 +9,11 @@ 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::{
|
||||
micros_until_next_substep, substeps_crossed, LinkState, StepTiming, SyncTime,
|
||||
ACTIVE_WAIT_THRESHOLD_US,
|
||||
};
|
||||
use super::{micros_until_next_substep, substeps_crossed, LinkState, StepTiming, SyncTime};
|
||||
use crate::model::{
|
||||
CcAccess, Dictionary, ExecutionTrace, Rng, ScriptEngine, StepContext, Value, Variables,
|
||||
};
|
||||
@@ -1039,39 +1037,17 @@ fn sequencer_loop(
|
||||
) {
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use thread_priority::unix::{
|
||||
set_thread_priority_and_policy, thread_native_id, NormalThreadSchedulePolicy,
|
||||
RealtimeThreadSchedulePolicy, ThreadSchedulePolicy,
|
||||
};
|
||||
let has_rt = set_realtime_priority();
|
||||
|
||||
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_err() {
|
||||
// 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_err() {
|
||||
// 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 via libc on Linux
|
||||
#[cfg(target_os = "linux")]
|
||||
unsafe {
|
||||
libc::setpriority(libc::PRIO_PROCESS, 0, -20);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
let _ = set_current_thread_priority(ThreadPriority::Max);
|
||||
#[cfg(target_os = "linux")]
|
||||
if !has_rt {
|
||||
eprintln!("[cagire] Warning: Could not set realtime priority for sequencer thread.");
|
||||
eprintln!("[cagire] For best performance on Linux, configure rtprio limits:");
|
||||
eprintln!("[cagire] Add your user to 'audio' group: sudo usermod -aG audio $USER");
|
||||
eprintln!("[cagire] Edit /etc/security/limits.conf and add:");
|
||||
eprintln!("[cagire] @audio - rtprio 95");
|
||||
eprintln!("[cagire] @audio - memlock unlimited");
|
||||
eprintln!("[cagire] Then log out and back in.");
|
||||
}
|
||||
|
||||
let mut seq_state = SequencerState::new(variables, dict, rng, cc_access);
|
||||
@@ -1203,47 +1179,26 @@ fn sequencer_loop(
|
||||
};
|
||||
|
||||
let target_time_us = current_time_us + next_event_us;
|
||||
wait_until(target_time_us, &link);
|
||||
wait_until(target_time_us, &link, has_rt);
|
||||
}
|
||||
}
|
||||
|
||||
/// High-precision sleep using timerfd on Linux, falling back to thread::sleep elsewhere
|
||||
/// Spin-wait threshold in microseconds. With RT priority, we sleep most of the
|
||||
/// wait time and spin-wait for the final portion for precision. Without RT priority,
|
||||
/// spinning is counterproductive and we sleep the entire duration.
|
||||
const SPIN_THRESHOLD_US: SyncTime = 100;
|
||||
|
||||
/// High-precision sleep using clock_nanosleep on Linux
|
||||
#[cfg(target_os = "linux")]
|
||||
fn precise_sleep(micros: u64) {
|
||||
use nix::sys::timerfd::{ClockId, Expiration, TimerFd, TimerFlags, TimerSetTimeFlags};
|
||||
use std::os::fd::AsRawFd;
|
||||
|
||||
thread_local! {
|
||||
static TIMER: std::cell::RefCell<Option<TimerFd>> = const { std::cell::RefCell::new(None) };
|
||||
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());
|
||||
}
|
||||
|
||||
TIMER.with(|timer_cell| {
|
||||
let mut timer_ref = timer_cell.borrow_mut();
|
||||
let timer = timer_ref.get_or_insert_with(|| {
|
||||
TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty())
|
||||
.expect("Failed to create timerfd")
|
||||
});
|
||||
|
||||
let duration = Duration::from_micros(micros);
|
||||
if timer
|
||||
.set(Expiration::OneShot(duration), TimerSetTimeFlags::empty())
|
||||
.is_ok()
|
||||
{
|
||||
// Use poll to wait for the timer instead of read to avoid allocating
|
||||
let mut pollfd = libc::pollfd {
|
||||
fd: timer.as_raw_fd(),
|
||||
events: libc::POLLIN,
|
||||
revents: 0,
|
||||
};
|
||||
unsafe {
|
||||
libc::poll(&mut pollfd, 1, -1);
|
||||
}
|
||||
// Clear the timer by reading (discard the count)
|
||||
let _ = timer.wait();
|
||||
} else {
|
||||
thread::sleep(duration);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
@@ -1251,18 +1206,77 @@ fn precise_sleep(micros: u64) {
|
||||
thread::sleep(Duration::from_micros(micros));
|
||||
}
|
||||
|
||||
/// Two-phase wait: bulk sleep followed by active spin-wait for final precision.
|
||||
fn wait_until(target_us: SyncTime, link: &LinkState) {
|
||||
/// 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.
|
||||
/// With RT priority: sleep + spin for precision
|
||||
/// Without RT priority: sleep only (spinning wastes CPU without benefit)
|
||||
fn wait_until(target_us: SyncTime, link: &LinkState, has_rt_priority: bool) {
|
||||
let current = link.clock_micros() as SyncTime;
|
||||
let remaining = target_us.saturating_sub(current);
|
||||
|
||||
if remaining > ACTIVE_WAIT_THRESHOLD_US {
|
||||
precise_sleep(remaining - ACTIVE_WAIT_THRESHOLD_US);
|
||||
}
|
||||
|
||||
// Active wait for final precision
|
||||
while (link.clock_micros() as SyncTime) < target_us {
|
||||
std::hint::spin_loop();
|
||||
if has_rt_priority {
|
||||
// With RT priority: sleep most, spin for final precision
|
||||
if remaining > SPIN_THRESHOLD_US {
|
||||
precise_sleep(remaining - SPIN_THRESHOLD_US);
|
||||
}
|
||||
while (link.clock_micros() as SyncTime) < target_us {
|
||||
std::hint::spin_loop();
|
||||
}
|
||||
} else {
|
||||
// Without RT priority: sleep the entire time (spin-waiting is counterproductive)
|
||||
if remaining > 0 {
|
||||
precise_sleep(remaining);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user