Merge pull request #1006 from Silvertree2010/fix/886-audit-the-audio-callback-blocks

Render strips serially on the audio thread instead of using rayon
This commit is contained in:
FredApps authored and GitHub committed 2026-11-08 02:21:27 +01:00
commit 76cc2aeac6
1 file changed
+26 -10
+26 -10
View File
@@ -33,10 +33,10 @@ fn as_audio_thread(audio: bool, f: impl FnOnce()) {
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn run_strips<T: Send>(work: &mut [T], f: impl Fn(&mut T) + Sync + Send) {
use rayon::prelude::*;
if work.len() >= 4 {
// The workers render on behalf of this thread: on the audio thread, they are it too.
let audio = on_audio_thread();
work.par_iter_mut().for_each(|w| as_audio_thread(audio, || f(w)));
// The audio thread is not a rayon worker, so it would park on a latch waiting for the shared
// pool (also busy with offline work): it renders its strips itself.
if work.len() >= 4 && !on_audio_thread() {
work.par_iter_mut().for_each(|w| as_audio_thread(false, || f(w)));
} else {
work.iter_mut().for_each(f);
}
@@ -47,7 +47,7 @@ pub(crate) fn run_strips<T: Send>(work: &mut [T], f: impl Fn(&mut T) + Sync + Se
work.iter_mut().for_each(f);
}
#[cfg(test)]
#[cfg(all(test, not(target_arch = "wasm32")))]
mod audio_thread_tests {
use super::*;
@@ -67,7 +67,7 @@ mod audio_thread_tests {
/// What `on_audio_thread` says inside each strip job, for strips run from a thread that is
/// (or is not) the audio thread.
fn seen_by_strips(audio: bool) -> Vec<bool> {
fn seen_by_strips(audio: bool) -> Result<Vec<bool>, &'static str> {
std::thread::spawn(move || {
if audio {
mark_audio_thread();
@@ -78,15 +78,31 @@ mod audio_thread_tests {
std::thread::sleep(std::time::Duration::from_millis(1));
*w = Some(on_audio_thread());
});
work.into_iter().map(|w| w.unwrap()).collect()
work.into_iter().collect::<Option<Vec<_>>>().ok_or("a strip did not run")
})
.join()
.unwrap()
.map_err(|_| "the strip render thread panicked")?
}
#[test]
fn strips_rendered_for_the_audio_thread_count_as_the_audio_thread_and_only_then() {
assert!(seen_by_strips(true).iter().all(|&rt| rt), "a worker rendering for the audio thread is on it");
assert!(seen_by_strips(false).iter().all(|&rt| !rt), "the workers don't stay marked afterwards");
assert_eq!(seen_by_strips(true), Ok(vec![true; 64]), "strips rendered for the audio thread are on it");
assert_eq!(seen_by_strips(false), Ok(vec![false; 64]), "the workers don't stay marked afterwards");
}
#[test]
fn the_audio_thread_never_hands_strips_to_the_rayon_pool() {
let on_pool = std::thread::spawn(|| {
mark_audio_thread();
let mut work = vec![false; 8];
run_strips(&mut work, |w| {
std::thread::sleep(std::time::Duration::from_millis(1));
*w = rayon::current_thread_index().is_some();
});
work.into_iter().filter(|&p| p).count()
})
.join()
.unwrap();
assert_eq!(on_pool, 0, "the audio thread must not block on the rayon pool");
}
}