From fa21ce2cb4d40058b72255d9a65170145836f2aa Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 2 Oct 2026 00:37:39 -0400 Subject: [PATCH] M6.8: fix media pool deadlock (rayon worker blocking on an in-flight decode; prefetch on its own thread) A rayon worker running frame_at could wait on the in-flight condvar for a frame whose decode sat lower on its own stack (work stealing during the decoder's parallel join), hanging footage playback and the media tests. Co-Authored-By: Claude Opus 5.5 --- crates/media/src/pool.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/media/src/pool.rs b/crates/media/src/pool.rs index 773f78e9..91a4c76c 100644 --- a/crates/media/src/pool.rs +++ b/crates/media/src/pool.rs @@ -398,7 +398,15 @@ impl Inner { if !c.inflight.contains(&loc.key) { break; } - // another thread (or the read-ahead) is decoding this frame + // Another thread (or the read-ahead) is decoding this frame. Never block a rayon + // worker on that: while the decoder waits on its own parallel join, the worker can + // steal this very job, and waiting here would then wait on a decode further down + // its own stack (deadlock). Decode a private copy instead. + if rayon::current_thread_index().is_some() { + self.misses.fetch_add(1, Ordering::Relaxed); + drop(c); + return Ok(Arc::new(self.decode(&loc, footage)?)); + } c = self.done.wait(c).unwrap_or_else(|e| e.into_inner()); } self.misses.fetch_add(1, Ordering::Relaxed); @@ -414,7 +422,8 @@ impl Inner { fn prefetch(self: &Arc, footage: &Footage, t: Tick) { let (me, f) = (self.clone(), footage.clone()); self.prefetched.fetch_add(1, Ordering::Relaxed); - rayon::spawn(move || { + // A plain thread, not a rayon job: the decode below parallelises with rayon itself. + std::thread::spawn(move || { let _ = me.frame_at(&f, t, false); }); }