Compare commits

..
Author SHA1 Message Date
Horizon agentandClaude Opus 5.5 8406d2c586 horizon-threads: the stack dump test samples only once the busy loop runs
The worker sent its handle and went straight into the loop, so the signal could land
while it was still waking the receiver (futex_wake in the mpsc send), and the busy
frame was not yet on its stack. It failed that way on CI (run 43194576407).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-12 14:38:30 +01:00
3 changed files with 11 additions and 18 deletions

No files matched your search

-13
View File
@@ -366,19 +366,6 @@ fn give_scratch(scratch: LightScratch) {
}
}
/// Frees the pooled scratch, once the streaming workers have had nothing to
/// do for a while (`jobs::Source::all_idle`): a full pool is some 50 MB on
/// twelve threads, and a loaded view lights nothing new until the player
/// moves on. The next job builds one again.
pub fn release_scratch() {
let pool = std::mem::take(
&mut *SCRATCH_POOL
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner),
);
drop(pool);
}
thread_local! {
static RELIGHT_SCRATCH: RefCell<RelightScratch> = RefCell::new(RelightScratch::new());
}
@@ -924,10 +924,10 @@ impl<G: ChunkGenerator> Source for Jobs<G> {
});
}
fn all_idle(&self) {
// No worker needs its kept meshes or light scratch: freed outside the pool lock.
// No worker needs retained mesh capacity now. Drop it outside the
// pool lock; the pool then asks the allocator to return freed pages.
let meshes = std::mem::take(&mut *self.shared.meshes.lock().unwrap());
drop(meshes);
crate::light::release_scratch();
self.generator.all_idle();
}
}
+9 -3
View File
@@ -183,8 +183,9 @@ mod tests {
/// (it resumes from the return address), while every caller's frame stays.
#[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))]
#[inline(never)]
fn busy_in_a_named_frame(stop: &AtomicBool) {
fn busy_in_a_named_frame(started: &AtomicBool, stop: &AtomicBool) {
let mut n = 0u64;
started.store(true, Ordering::Release);
while !stop.load(Ordering::Relaxed) {
n = std::hint::black_box(spin)(n);
}
@@ -193,16 +194,21 @@ mod tests {
#[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))]
#[test]
fn reads_the_stack_of_a_busy_thread_from_another() {
let started = Arc::new(AtomicBool::new(false));
let stop = Arc::new(AtomicBool::new(false));
let (tx, rx) = mpsc::channel();
let worker = {
let stop = stop.clone();
let (started, stop) = (started.clone(), stop.clone());
std::thread::spawn(move || {
tx.send(Watched::current()).unwrap();
busy_in_a_named_frame(&stop);
busy_in_a_named_frame(&started, &stop);
})
};
let watched = rx.recv().unwrap();
// The send can still be waking the receiver when it returns: sample only once the loop runs.
while !started.load(Ordering::Acquire) {
std::hint::spin_loop();
}
let stack = watched.stack(Duration::from_secs(10));
stop.store(true, Ordering::Relaxed);
worker.join().unwrap();