From fa58a7bef52dc828098ab54b7b1eb4cf3300f558 Mon Sep 17 00:00:00 2001 From: Stefanie Jane Date: Tue, 8 Sep 2026 08:23:31 -0700 Subject: [PATCH] test(windows): await native progress in surface reclamation The pressure fixture could exhaust its polling attempts while WARP had not completed either queued fence. Wait for native fence events between the two reclamation transitions instead of counting scheduling chances. Keep production reclamation, slot capacity, and concurrency unchanged. A stalled native queue still fails with its fence and slot diagnostics. --- crates/hypercolor-windows-capture/Cargo.toml | 1 + .../src/duplication/gpu_surface.rs | 39 +++++++++++++++++++ .../src/duplication/tests.rs | 8 +++- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/crates/hypercolor-windows-capture/Cargo.toml b/crates/hypercolor-windows-capture/Cargo.toml index 21fa19562..08ec19011 100644 --- a/crates/hypercolor-windows-capture/Cargo.toml +++ b/crates/hypercolor-windows-capture/Cargo.toml @@ -40,6 +40,7 @@ windows = { version = "0.62", default-features = false, features = [ "Win32_Graphics_Gdi", "Win32_Security", "Win32_System_LibraryLoader", + "Win32_System_Threading", ] } [[bench]] diff --git a/crates/hypercolor-windows-capture/src/duplication/gpu_surface.rs b/crates/hypercolor-windows-capture/src/duplication/gpu_surface.rs index 0bbf6660e..f14ab3248 100644 --- a/crates/hypercolor-windows-capture/src/duplication/gpu_surface.rs +++ b/crates/hypercolor-windows-capture/src/duplication/gpu_surface.rs @@ -2609,6 +2609,45 @@ pub(super) mod fixture { publication.shared.texture_handle.borrowed().as_raw() } + pub(crate) fn wait_for_surface_progress(plan: &PreparedGpuSurfacePlan) -> CaptureResult<()> { + use windows::Win32::Foundation::WAIT_OBJECT_0; + use windows::Win32::System::Threading::{CreateEventW, WaitForSingleObject}; + + for slot in plan.routes.iter().flat_map(|route| &route.slots) { + let value = match slot.publication.state.load(Ordering::Acquire) { + USE_UNCLAIMED => slot.publication.synchronization.producer_ready_value, + USE_RELEASE_QUEUED => slot.publication.synchronization.consumer_release_value, + _ => continue, + }; + // SAFETY: default security and no name create a private auto-reset event. + let event = OwnedHandle::new( + unsafe { CreateEventW(None, false, false, None) } + .map_err(|error| CaptureError::windows("create fixture fence event", error))?, + )?; + // SAFETY: the plan owns the fence and event remains live through the wait. + unsafe { + slot.publication + .shared + .fence + .SetEventOnCompletion(value, event.0) + } + .map_err(|error| CaptureError::windows("arm fixture fence event", error))?; + // SAFETY: event is a valid owned handle. The bound diagnoses a stalled + // native queue; it is not a publication cadence or polling budget. + let result = unsafe { WaitForSingleObject(event.0, 10_000) }; + if result != WAIT_OBJECT_0 { + return Err(CaptureError::windows( + "wait for fixture GPU progress", + format!( + "fence {value}, wait {result:?}, slots {:?}", + slot_diagnostics(plan) + ), + )); + } + } + Ok(()) + } + pub(crate) fn slot_diagnostics( plan: &PreparedGpuSurfacePlan, ) -> Vec<(usize, u8, u64, Option)> { diff --git a/crates/hypercolor-windows-capture/src/duplication/tests.rs b/crates/hypercolor-windows-capture/src/duplication/tests.rs index 47091569c..e8afe3977 100644 --- a/crates/hypercolor-windows-capture/src/duplication/tests.rs +++ b/crates/hypercolor-windows-capture/src/duplication/tests.rs @@ -894,7 +894,10 @@ fn abandoned_exact_gpu_surfaces_reclaim_under_sustained_pressure() { fixture.outcomes.clear(); for sequence in 42..74 { - let publication = (0..64) + // A busy slot crosses at most two GPU boundaries: producer readiness, + // then the abandoned release. Observe those fences before retrying so + // concurrent WARP scheduling cannot consume an arbitrary polling budget. + let publication = (0..3) .find_map(|_| { let outcomes = super::gpu_surface::fixture::republish(&mut fixture, sequence) .expect("abandoned slots remain safely reclaimable"); @@ -903,7 +906,8 @@ fn abandoned_exact_gpu_surfaces_reclaim_under_sustained_pressure() { crate::GpuSurfacePublishOutcome::Busy(_) => None, }); if publication.is_none() { - std::thread::sleep(std::time::Duration::from_millis(1)); + super::gpu_surface::fixture::wait_for_surface_progress(&fixture.plan) + .expect("queued native work completes before retrying publication"); } publication })