From e45d5dca5cbe8b4505fdfee1878b24ec8074864f Mon Sep 17 00:00:00 2001 From: Fabian <68284158+Faabyy@users.noreply.github.com> Date: Mon, 19 Oct 2026 19:03:13 +0200 Subject: [PATCH] Tests: the parser-cancel test guards against a hang, not a 2 s limit (#1970) (#3462) waiting_worker_can_cancel_before_initializer_returns gave its threads 2 s to start, and a busy FreeBSD VM in CI took longer. Its waits only guard against a hang: the initializer is held until the end of the test, so a waiter that can't cancel never returns, however long the guard is. They now allow 600 s, like the hang guard in page_contents_and_nested_forms_share_one_budget (#2812), which already covers the second test named in the issue. The test polls with a 1 ms sleep instead of yield_now, and the held initializer no longer panics a second time when the test fails. Checked by hand: with a 3 s delay at both threads' start the old test fails at its deadline and the new one passes; with the waiter's condvar wait made untimed (a real stall) the new test fails at its guard. Fixes #1970 Co-authored-by: Claude --- crates/render/src/raster.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/crates/render/src/raster.rs b/crates/render/src/raster.rs index 44d9be124..45641178d 100644 --- a/crates/render/src/raster.rs +++ b/crates/render/src/raster.rs @@ -1334,6 +1334,10 @@ mod tests { assert!(parser.acquire(|| panic!("must not retry"), || false).unwrap().is_none()); } + /// The waits here only guard against a hang; they don't time anything. The initializer is + /// held until the end of the test, so a waiter that can't cancel waits forever and fails the + /// guard however long it is, and a correct one returns as soon as its thread runs. Two + /// seconds was too short for a busy FreeBSD VM in CI to start the threads (#1970). #[cfg(not(target_arch = "wasm32"))] #[test] fn waiting_worker_can_cancel_before_initializer_returns() { @@ -1341,6 +1345,7 @@ mod tests { sync::{atomic::AtomicUsize, mpsc}, time::Duration, }; + const HANG_GUARD: Duration = Duration::from_secs(600); let parser = Arc::new(PoolParser::default()); let (started_tx, started_rx) = mpsc::channel(); let (release_tx, release_rx) = mpsc::channel(); @@ -1350,14 +1355,16 @@ mod tests { parser.acquire( || { started_tx.send(()).unwrap(); - release_rx.recv().unwrap(); + // A failed test drops the sender: that ends the hold, it is not a + // second failure. + let _ = release_rx.recv(); Pdf::new(ONE_PAGE.to_vec()).ok() }, || false, ) }) }; - started_rx.recv_timeout(Duration::from_secs(2)).unwrap(); + started_rx.recv_timeout(HANG_GUARD).expect("the initializer starts"); let cancelled = Arc::new(AtomicBool::new(false)); let checks = Arc::new(AtomicUsize::new(0)); let (done_tx, done_rx) = mpsc::channel(); @@ -1374,13 +1381,13 @@ mod tests { done_tx.send(result.unwrap().is_none()).unwrap(); }) }; - let deadline = std::time::Instant::now() + Duration::from_secs(2); + let deadline = std::time::Instant::now() + HANG_GUARD; while checks.load(Ordering::Relaxed) == 0 { - assert!(std::time::Instant::now() < deadline); - std::thread::yield_now(); + assert!(std::time::Instant::now() < deadline, "the waiter never checked for cancellation"); + std::thread::sleep(Duration::from_millis(1)); } cancelled.store(true, Ordering::Release); - assert!(done_rx.recv_timeout(Duration::from_secs(2)).unwrap()); + assert!(done_rx.recv_timeout(HANG_GUARD).expect("the waiter cancels while the initializer is held")); waiter.join().unwrap(); release_tx.send(()).unwrap(); assert!(initializer.join().unwrap().unwrap().is_some());