diff options
Diffstat (limited to '')
4 files changed, 207 insertions, 41 deletions
diff --git a/third_party/rust/audio_thread_priority/.cargo-checksum.json b/third_party/rust/audio_thread_priority/.cargo-checksum.json index e1a42d3fe7..fc49e6d1a8 100644 --- a/third_party/rust/audio_thread_priority/.cargo-checksum.json +++ b/third_party/rust/audio_thread_priority/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"6bceb8b41d1646bd255b39c13dcca5bbcc0984f0f2aca73decb7ee5627443387","LICENSE":"32ee9dbf6196874fc9d406c54a888a6c4cbb9aa4a7f35b46befeaff43a78fe85","Makefile":"0f9a771cfb30c7c4b9961d82fdca4e9e229a955bb2e636474a4101389e18e938","README.md":"c123692b3b50dd621b896a8269814d609cbf1e532b461bf4a77854ddd607eb7a","atp_test.cpp":"8075a040941a65fb9e3f7cbf0535853ca6661c3ac442ec35569b42b24bbec797","audio_thread_priority.h":"f0ecaf1b674f794cde0dc834028e074d4e4675d22ae96acf08b2ae1dceb3474e","generate_osx_bindings.sh":"06e4e03450f788ced18d31fff5660919e6f6ec1119ddace363ffeb82f0518a71","src/lib.rs":"1d5c629d1ee2dab80f7bf422a3add07eaca203f5c1557fdd379361d632e14224","src/mach_sys.rs":"352560fcb9b41d877cff92e5b3b04d6dc68b1f30508ce4b9aed78940120a883e","src/rt_linux.rs":"e77db6d66fb76772dde6c12850ef2c50730cfe00decc0c67211e63f3e8a59c9f","src/rt_mach.rs":"29f8c0397f14cecbac1f76394c2abfe0e05903b54486cf735f9a94a10c168643","src/rt_win.rs":"f2ba097cebf65252c27d9d6dcfbe1fcc041c4b312724bd98e080e114a4de3bb6"},"package":"17fd24082f432a11ad4fde564af75fc9a0beef2f4199e7691b090ff0e065c4d0"}
\ No newline at end of file +{"files":{"Cargo.toml":"2dd36097338035819ea7fdb1de6d37009571d761fb659194423f7143a3ba25c7","LICENSE":"32ee9dbf6196874fc9d406c54a888a6c4cbb9aa4a7f35b46befeaff43a78fe85","Makefile":"0f9a771cfb30c7c4b9961d82fdca4e9e229a955bb2e636474a4101389e18e938","README.md":"c123692b3b50dd621b896a8269814d609cbf1e532b461bf4a77854ddd607eb7a","atp_test.cpp":"8075a040941a65fb9e3f7cbf0535853ca6661c3ac442ec35569b42b24bbec797","audio_thread_priority.h":"f0ecaf1b674f794cde0dc834028e074d4e4675d22ae96acf08b2ae1dceb3474e","generate_osx_bindings.sh":"06e4e03450f788ced18d31fff5660919e6f6ec1119ddace363ffeb82f0518a71","src/lib.rs":"142a4f40a0deba0544673c5c8cb4398525a7ead2cc5addb4e69dd9a81e771ded","src/mach_sys.rs":"352560fcb9b41d877cff92e5b3b04d6dc68b1f30508ce4b9aed78940120a883e","src/rt_linux.rs":"e77db6d66fb76772dde6c12850ef2c50730cfe00decc0c67211e63f3e8a59c9f","src/rt_mach.rs":"29f8c0397f14cecbac1f76394c2abfe0e05903b54486cf735f9a94a10c168643","src/rt_win.rs":"0c85d771c544e4bb2b8fb60bdffe5a3080c112896f42360eb92cd3b85c010ca0"},"package":"e3632611da7e79f8fc8fd75840f1ccfa7792dbf1e25d00791344a4450dd8834f"}
\ No newline at end of file diff --git a/third_party/rust/audio_thread_priority/Cargo.toml b/third_party/rust/audio_thread_priority/Cargo.toml index 30861cdafd..e1fa1a342f 100644 --- a/third_party/rust/audio_thread_priority/Cargo.toml +++ b/third_party/rust/audio_thread_priority/Cargo.toml @@ -12,7 +12,7 @@ [package] edition = "2018" name = "audio_thread_priority" -version = "0.31.0" +version = "0.32.0" authors = ["Paul Adenot <paul@paul.cx>"] description = "Bump a thread to real-time priority, for audio work, on Linux, Windows and macOS" readme = "README.md" @@ -61,5 +61,5 @@ version = "0.3" version = "0.52" features = [ "Win32_Foundation", - "Win32_System_Threading", + "Win32_System_LibraryLoader", ] diff --git a/third_party/rust/audio_thread_priority/src/lib.rs b/third_party/rust/audio_thread_priority/src/lib.rs index eaae33d9d1..6006f7966e 100644 --- a/third_party/rust/audio_thread_priority/src/lib.rs +++ b/third_party/rust/audio_thread_priority/src/lib.rs @@ -647,6 +647,15 @@ mod tests { // automatically deallocated, but not demoted until the thread exits. } } + + #[test] + fn it_works_in_different_threads() { + let handles: Vec<_> = (0..32).map(|_| std::thread::spawn(it_works)).collect(); + for handle in handles { + handle.join().unwrap() + } + } + cfg_if! { if #[cfg(target_os = "linux")] { use nix::unistd::*; diff --git a/third_party/rust/audio_thread_priority/src/rt_win.rs b/third_party/rust/audio_thread_priority/src/rt_win.rs index 3a2bbf29d9..480c5bd75c 100644 --- a/third_party/rust/audio_thread_priority/src/rt_win.rs +++ b/third_party/rust/audio_thread_priority/src/rt_win.rs @@ -1,14 +1,11 @@ -use windows_sys::s; -use windows_sys::Win32::Foundation::GetLastError; -use windows_sys::Win32::Foundation::FALSE; -use windows_sys::Win32::Foundation::HANDLE; -use windows_sys::Win32::System::Threading::{ - AvRevertMmThreadCharacteristics, AvSetMmThreadCharacteristicsA, -}; - +use self::avrt_lib::AvRtLibrary; use crate::AudioThreadPriorityError; - use log::info; +use std::sync::OnceLock; +use windows_sys::{ + w, + Win32::Foundation::{HANDLE, WIN32_ERROR}, +}; #[derive(Debug)] pub struct RtPriorityHandleInternal { @@ -17,7 +14,7 @@ pub struct RtPriorityHandleInternal { } impl RtPriorityHandleInternal { - pub fn new(mmcss_task_index: u32, task_handle: HANDLE) -> RtPriorityHandleInternal { + fn new(mmcss_task_index: u32, task_handle: HANDLE) -> RtPriorityHandleInternal { RtPriorityHandleInternal { mmcss_task_index, task_handle, @@ -25,45 +22,205 @@ impl RtPriorityHandleInternal { } } +fn avrt() -> Result<&'static AvRtLibrary, AudioThreadPriorityError> { + static AV_RT_LIBRARY: OnceLock<Result<AvRtLibrary, WIN32_ERROR>> = OnceLock::new(); + AV_RT_LIBRARY + .get_or_init(AvRtLibrary::try_new) + .as_ref() + .map_err(|win32_error| { + AudioThreadPriorityError::new(&format!("Unable to load avrt.dll ({win32_error})")) + }) +} + +pub fn promote_current_thread_to_real_time_internal( + _audio_buffer_frames: u32, + _audio_samplerate_hz: u32, +) -> Result<RtPriorityHandleInternal, AudioThreadPriorityError> { + avrt()? + .set_mm_thread_characteristics(w!("Audio")) + .map(|(mmcss_task_index, task_handle)| { + info!("task {mmcss_task_index} bumped to real time priority."); + RtPriorityHandleInternal::new(mmcss_task_index, task_handle) + }) + .map_err(|win32_error| { + AudioThreadPriorityError::new(&format!( + "Unable to bump the thread priority ({win32_error})" + )) + }) +} + pub fn demote_current_thread_from_real_time_internal( rt_priority_handle: RtPriorityHandleInternal, ) -> Result<(), AudioThreadPriorityError> { - let rv = unsafe { AvRevertMmThreadCharacteristics(rt_priority_handle.task_handle) }; - if rv == FALSE { - return Err(AudioThreadPriorityError::new(&format!( - "Unable to restore the thread priority ({:?})", - unsafe { GetLastError() } - ))); + let RtPriorityHandleInternal { + mmcss_task_index, + task_handle, + } = rt_priority_handle; + avrt()? + .revert_mm_thread_characteristics(task_handle) + .map(|_| { + info!("task {mmcss_task_index} priority restored."); + }) + .map_err(|win32_error| { + AudioThreadPriorityError::new(&format!( + "Unable to restore the thread priority for task {mmcss_task_index} ({win32_error})" + )) + }) +} + +mod avrt_lib { + use super::win32_utils::{win32_error_if, OwnedLibrary}; + use std::sync::Once; + use windows_sys::{ + core::PCWSTR, + s, w, + Win32::Foundation::{BOOL, FALSE, HANDLE, WIN32_ERROR}, + }; + + type AvSetMmThreadCharacteristicsWFn = unsafe extern "system" fn(PCWSTR, *mut u32) -> HANDLE; + type AvRevertMmThreadCharacteristicsFn = unsafe extern "system" fn(HANDLE) -> BOOL; + + #[derive(Debug)] + pub(super) struct AvRtLibrary { + // This field is never read because only used for its Drop behavior + #[allow(dead_code)] + module: OwnedLibrary, + + av_set_mm_thread_characteristics_w: AvSetMmThreadCharacteristicsWFn, + av_revert_mm_thread_characteristics: AvRevertMmThreadCharacteristicsFn, } - info!( - "task {} priority restored.", - rt_priority_handle.mmcss_task_index - ); + impl AvRtLibrary { + pub(super) fn try_new() -> Result<Self, WIN32_ERROR> { + let module = OwnedLibrary::try_new(w!("avrt.dll"))?; + let av_set_mm_thread_characteristics_w = unsafe { + std::mem::transmute::<_, AvSetMmThreadCharacteristicsWFn>( + module.get_proc(s!("AvSetMmThreadCharacteristicsW"))?, + ) + }; + let av_revert_mm_thread_characteristics = unsafe { + std::mem::transmute::<_, AvRevertMmThreadCharacteristicsFn>( + module.get_proc(s!("AvRevertMmThreadCharacteristics"))?, + ) + }; + Ok(Self { + module, + av_set_mm_thread_characteristics_w, + av_revert_mm_thread_characteristics, + }) + } + + pub(super) fn set_mm_thread_characteristics( + &self, + task_name: PCWSTR, + ) -> Result<(u32, HANDLE), WIN32_ERROR> { + // Ensure that the first call never runs in parallel with other calls. This + // seems necessary to guarantee the success of these other calls. We saw them + // fail with an error code of ERROR_PATH_NOT_FOUND in tests, presumably on a + // machine where the MMCSS service was initially inactive. + static FIRST_CALL: Once = Once::new(); + let mut first_call_result = None; + FIRST_CALL.call_once(|| { + first_call_result = Some(self.set_mm_thread_characteristics_internal(task_name)) + }); + first_call_result + .unwrap_or_else(|| self.set_mm_thread_characteristics_internal(task_name)) + } + + fn set_mm_thread_characteristics_internal( + &self, + task_name: PCWSTR, + ) -> Result<(u32, HANDLE), WIN32_ERROR> { + let mut mmcss_task_index = 0u32; + let task_handle = unsafe { + (self.av_set_mm_thread_characteristics_w)(task_name, &mut mmcss_task_index) + }; + win32_error_if(task_handle == 0)?; + Ok((mmcss_task_index, task_handle)) + } - Ok(()) + pub(super) fn revert_mm_thread_characteristics( + &self, + handle: HANDLE, + ) -> Result<(), WIN32_ERROR> { + let rv = unsafe { (self.av_revert_mm_thread_characteristics)(handle) }; + win32_error_if(rv == FALSE) + } + } } -pub fn promote_current_thread_to_real_time_internal( - _audio_buffer_frames: u32, - _audio_samplerate_hz: u32, -) -> Result<RtPriorityHandleInternal, AudioThreadPriorityError> { - let mut task_index = 0u32; +mod win32_utils { + use windows_sys::{ + core::{PCSTR, PCWSTR}, + Win32::{ + Foundation::{FreeLibrary, GetLastError, HMODULE, WIN32_ERROR}, + System::LibraryLoader::{GetProcAddress, LoadLibraryW}, + }, + }; + + pub(super) fn win32_error_if(condition: bool) -> Result<(), WIN32_ERROR> { + if condition { + Err(unsafe { GetLastError() }) + } else { + Ok(()) + } + } + + #[derive(Debug)] + pub(super) struct OwnedLibrary(HMODULE); + + impl OwnedLibrary { + pub(super) fn try_new(lib_file_name: PCWSTR) -> Result<Self, WIN32_ERROR> { + let module = unsafe { LoadLibraryW(lib_file_name) }; + win32_error_if(module == 0)?; + Ok(Self(module)) + } - let handle = unsafe { AvSetMmThreadCharacteristicsA(s!("Audio"), &mut task_index) }; - let handle = RtPriorityHandleInternal::new(task_index, handle); + fn raw(&self) -> HMODULE { + self.0 + } - if handle.task_handle == 0 { - return Err(AudioThreadPriorityError::new(&format!( - "Unable to restore the thread priority ({:?})", - unsafe { GetLastError() } - ))); + /// SAFETY: The caller must transmute the value wrapped in a Ok(_) to the correct + /// function type, with the correct extern specifier. + pub(super) unsafe fn get_proc( + &self, + proc_name: PCSTR, + ) -> Result<unsafe extern "system" fn() -> isize, WIN32_ERROR> { + let proc = unsafe { GetProcAddress(self.raw(), proc_name) }; + win32_error_if(proc.is_none())?; + Ok(proc.unwrap()) + } } - info!( - "task {} bumped to real time priority.", - handle.mmcss_task_index - ); + impl Drop for OwnedLibrary { + fn drop(&mut self) { + unsafe { + FreeLibrary(self.raw()); + } + } + } +} - Ok(handle) +#[cfg(test)] +mod tests { + use super::{ + avrt, demote_current_thread_from_real_time_internal, + promote_current_thread_to_real_time_internal, + }; + + #[test] + fn test_successful_avrt_library_load() { + assert!(avrt().is_ok()) + } + + #[test] + fn test_successful_api_use() { + let handle = promote_current_thread_to_real_time_internal(512, 44100); + println!("handle: {handle:?}"); + assert!(handle.is_ok()); + + let result = demote_current_thread_from_real_time_internal(handle.unwrap()); + println!("result: {result:?}"); + assert!(result.is_ok()); + } } |