From beb57326f76b72c81b26379b500a46ee1cf3c3f0 Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Wed, 13 Aug 2025 17:37:40 +1200 Subject: [PATCH 1/2] macOS: Limit computation time to 50ms, per OS limits. --- .github/workflows/rust.yml | 4 ++-- src/rt_mach.rs | 23 ++++++++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e886bb5..283951b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -39,5 +39,5 @@ jobs: - name: Test shell: bash run: rustup run ${{ matrix.rust }} cargo test --all - # setrlimit64 error in the CI container, macOS not supported - if: matrix.os != 'ubuntu-24.04' && matrix.os != 'macos-14' + # setrlimit64 error in the CI container + if: matrix.os != 'ubuntu-24.04' diff --git a/src/rt_mach.rs b/src/rt_mach.rs index 0cce702..a1f2862 100644 --- a/src/rt_mach.rs +++ b/src/rt_mach.rs @@ -127,18 +127,31 @@ pub fn promote_current_thread_to_real_time_internal( rt_priority_handle.previous_time_constraint_policy = time_constraints; - let cb_duration = buffer_frames as f32 / (audio_samplerate_hz as f32) * 1000.; - // The multiplicators are somwhat arbitrary for now. - let mut timebase_info = mach_timebase_info_data_t { denom: 0, numer: 0 }; mach_timebase_info(&mut timebase_info); let ms2abs: f32 = ((timebase_info.denom as f32) / timebase_info.numer as f32) * 1000000.; - // Computation time is half of constraint, per macOS 12 behaviour. + // The time constraint calculations are somewhat arbitrary for now. + let cb_duration = buffer_frames as f32 / (audio_samplerate_hz as f32) * 1000.; + + // Computation time is half of constraint, per macOS 12 behaviour. And capped at 50ms per macOS limits: + // https://github.com/apple-oss-distributions/xnu/blob/e3723e1f17661b24996789d8afc084c0c3303b26/osfmk/kern/thread_policy.c#L408 + // https://github.com/apple-oss-distributions/xnu/blob/e3723e1f17661b24996789d8afc084c0c3303b26/osfmk/kern/sched_prim.c#L822 + const MAX_RT_QUANTUM: f32 = 50.0; + let computation = cb_duration / 2.0; + let computation = if computation > MAX_RT_QUANTUM { + info!( + "thread computation time capped at {MAX_RT_QUANTUM}ms ({computation}ms requested)." + ); + MAX_RT_QUANTUM + } else { + computation + }; + time_constraints = thread_time_constraint_policy_data_t { period: (cb_duration * ms2abs) as u32, - computation: (cb_duration / 2.0 * ms2abs) as u32, + computation: (computation * ms2abs) as u32, constraint: (cb_duration * ms2abs) as u32, preemptible: 1, // true }; From 75331e88982e315f4ded5327e97c586254d99264 Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Wed, 13 Aug 2025 18:01:09 +1200 Subject: [PATCH 2/2] macOS: Remove mach_sys.rs and use definitions from mach2/libc. --- src/lib.rs | 2 -- src/mach_sys.rs | 36 ------------------------------------ src/rt_mach.rs | 40 +++++++++------------------------------- 3 files changed, 9 insertions(+), 69 deletions(-) delete mode 100644 src/mach_sys.rs diff --git a/src/lib.rs b/src/lib.rs index 50b7c58..9b32097 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,8 +87,6 @@ impl Error for AudioThreadPriorityError { cfg_if! { if #[cfg(target_os = "macos")] { mod rt_mach; -#[allow(unused, non_camel_case_types, non_snake_case, non_upper_case_globals)] - mod mach_sys; extern crate mach2; extern crate libc; use rt_mach::promote_current_thread_to_real_time_internal; diff --git a/src/mach_sys.rs b/src/mach_sys.rs deleted file mode 100644 index f5798ad..0000000 --- a/src/mach_sys.rs +++ /dev/null @@ -1,36 +0,0 @@ -/* automatically generated by rust-bindgen */ - -pub const THREAD_EXTENDED_POLICY: u32 = 1; -pub const THREAD_TIME_CONSTRAINT_POLICY: u32 = 2; -pub const THREAD_PRECEDENCE_POLICY: u32 = 3; -pub type __darwin_natural_t = ::std::os::raw::c_uint; -pub type __darwin_mach_port_name_t = __darwin_natural_t; -pub type __darwin_mach_port_t = __darwin_mach_port_name_t; -pub type boolean_t = ::std::os::raw::c_uint; -pub type natural_t = __darwin_natural_t; -pub type integer_t = ::std::os::raw::c_int; -pub type mach_port_t = __darwin_mach_port_t; -pub type thread_t = mach_port_t; -pub type thread_policy_flavor_t = natural_t; -pub type thread_policy_t = *mut integer_t; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct thread_extended_policy { - pub timeshare: boolean_t, -} -pub type thread_extended_policy_data_t = thread_extended_policy; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct thread_time_constraint_policy { - pub period: u32, - pub computation: u32, - pub constraint: u32, - pub preemptible: boolean_t, -} -pub type thread_time_constraint_policy_data_t = thread_time_constraint_policy; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct thread_precedence_policy { - pub importance: integer_t, -} -pub type thread_precedence_policy_data_t = thread_precedence_policy; diff --git a/src/rt_mach.rs b/src/rt_mach.rs index a1f2862..99e8c9d 100644 --- a/src/rt_mach.rs +++ b/src/rt_mach.rs @@ -1,37 +1,15 @@ -use crate::mach_sys::*; use crate::AudioThreadPriorityError; -use libc::{pthread_self, pthread_t}; +use libc::{pthread_mach_thread_np, pthread_self, thread_policy_t}; use log::info; +use mach2::boolean::boolean_t; use mach2::kern_return::{kern_return_t, KERN_SUCCESS}; use mach2::mach_time::{mach_timebase_info, mach_timebase_info_data_t}; use mach2::message::mach_msg_type_number_t; use mach2::port::mach_port_t; -use std::mem::size_of; - -extern "C" { - fn pthread_mach_thread_np(tid: pthread_t) -> mach_port_t; - // Those functions are commented out in thread_policy.h but somehow it works just fine !? - fn thread_policy_set( - thread: thread_t, - flavor: thread_policy_flavor_t, - policy_info: thread_policy_t, - count: mach_msg_type_number_t, - ) -> kern_return_t; - fn thread_policy_get( - thread: thread_t, - flavor: thread_policy_flavor_t, - policy_info: thread_policy_t, - count: &mut mach_msg_type_number_t, - get_default: &mut boolean_t, - ) -> kern_return_t; -} - -// can't use size_of in const fn just now in stable, use a macro for now. -macro_rules! THREAD_TIME_CONSTRAINT_POLICY_COUNT { - () => { - (size_of::() / size_of::()) as u32 - }; -} +use mach2::thread_policy::{ + thread_policy_get, thread_policy_set, thread_time_constraint_policy_data_t, + THREAD_TIME_CONSTRAINT_POLICY, THREAD_TIME_CONSTRAINT_POLICY_COUNT, +}; #[derive(Debug)] pub struct RtPriorityHandleInternal { @@ -68,7 +46,7 @@ pub fn demote_current_thread_from_real_time_internal( h.tid, THREAD_TIME_CONSTRAINT_POLICY, (&mut h.previous_time_constraint_policy) as *mut _ as thread_policy_t, - THREAD_TIME_CONSTRAINT_POLICY_COUNT!(), + THREAD_TIME_CONSTRAINT_POLICY_COUNT, ); if rv != KERN_SUCCESS { return Err(AudioThreadPriorityError::new( @@ -110,7 +88,7 @@ pub fn promote_current_thread_to_real_time_internal( // returning, it means there are no current settings because of other factor, and the // default was returned instead. let mut get_default: boolean_t = 0; - let mut count: mach_msg_type_number_t = THREAD_TIME_CONSTRAINT_POLICY_COUNT!(); + let mut count: mach_msg_type_number_t = THREAD_TIME_CONSTRAINT_POLICY_COUNT; let mut rv: kern_return_t = thread_policy_get( tid, THREAD_TIME_CONSTRAINT_POLICY, @@ -160,7 +138,7 @@ pub fn promote_current_thread_to_real_time_internal( tid, THREAD_TIME_CONSTRAINT_POLICY, (&mut time_constraints) as *mut _ as thread_policy_t, - THREAD_TIME_CONSTRAINT_POLICY_COUNT!(), + THREAD_TIME_CONSTRAINT_POLICY_COUNT, ); if rv != KERN_SUCCESS { return Err(AudioThreadPriorityError::new(