From 706ccda677a40dce8c5d2688648e83f4ecb7028a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 7 May 2026 00:32:41 +0000 Subject: [PATCH] fix(nests-tests): per-method relay reset + 2 s catalog timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two further hardenings on top of the catalog-retry fix to drive full-suite stability: - `NativeMoqRelayHarness.resetShared()` — tears down the current shared relay subprocess and lets the next caller spawn a fresh one. ~500 ms cost per call (cargo binaries cached, only relay boot + UDP bind paid). - `HangInteropTest.@BeforeTest` calls `resetShared()` so each scenario gets a clean relay; eliminates the per-subscriber- forward-queue + announce-table state that was accumulating across the 11 sequential tests in one JVM run. - hang-listen catalog read per-attempt timeout bumped from 500 ms → 2 s. Under concurrent-test load the wire round- trip can exceed 500 ms; longer per-attempt budget keeps the happy path fast (resolves on the first attempt) while tolerating slow handshakes. Suite wallclock cost: ~5–6 s added (one fresh relay boot per scenario), bringing a typical run to ~2:30. Stability gain is the trade. Note: full-suite stability isn't reverified in this commit — running the suite repeatedly under three concurrent agent worktrees (I7 reconnect, Phase 4 browser, I6 multi-listener) caused massive resource contention. Will re-verify once the parallel agents have completed and pushed. https://claude.ai/code/session_01ERJPUYfdLPwZ99pr5EcEcV --- .../interop/native/HangInteropTest.kt | 9 ++++++++ .../interop/native/NativeMoqRelayHarness.kt | 23 +++++++++++++++++++ .../hang-interop/hang-listen/src/main.rs | 16 ++++++++----- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/native/HangInteropTest.kt b/nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/native/HangInteropTest.kt index 34daef679..74ca7fd82 100644 --- a/nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/native/HangInteropTest.kt +++ b/nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/native/HangInteropTest.kt @@ -89,6 +89,15 @@ class HangInteropTest { @BeforeTest fun gate() { NativeMoqRelayHarness.assumeHangInterop() + // Reset the shared relay subprocess between scenarios. + // Sharing across all 11 methods in one JVM run means the + // relay's per-subscriber forward queues + announce + // tables accumulate state from prior tests, manifesting + // as intermittent catalog-cancel + sample-count flakes + // that don't reproduce in isolation. Per-method reboot + // costs ~500 ms (cargo binaries are cached) — acceptable + // for the stability gain. + NativeMoqRelayHarness.resetShared() } /** I1: 5 s 440 Hz mono sine, asserted via FFT peak + ZCR. */ diff --git a/nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/native/NativeMoqRelayHarness.kt b/nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/native/NativeMoqRelayHarness.kt index 4b2fd6798..586991507 100644 --- a/nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/native/NativeMoqRelayHarness.kt +++ b/nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/native/NativeMoqRelayHarness.kt @@ -153,6 +153,29 @@ class NativeMoqRelayHarness private constructor( } } + /** + * Tear down the current shared relay subprocess and start a + * fresh one. Used as a JUnit `@Before` hook by tests that + * need clean per-method relay state — under accumulated + * cross-test broadcasts / connections the relay's per- + * subscriber forward queues drift, manifesting as + * intermittent catalog-cancel and sample-count flakes that + * don't reproduce in isolation. + * + * Cost: ~500 ms per call (cargo binaries are cached, only + * the subprocess boot + UDP bind + first client handshake + * are paid). At 11 scenarios × 500 ms that's ~5.5 s added + * to the suite wallclock — acceptable trade for stability. + */ + fun resetShared() { + synchronized(sharedLock) { + shared?.let { + runCatching { it.close() } + } + shared = null + } + } + private fun doStart(): NativeMoqRelayHarness { check(isEnabled()) { "NativeMoqRelayHarness.shared() called without -D$ENABLE_PROPERTY=true." diff --git a/nestsClient/tests/hang-interop/hang-listen/src/main.rs b/nestsClient/tests/hang-interop/hang-listen/src/main.rs index 63faa0795..89b5d33e7 100644 --- a/nestsClient/tests/hang-interop/hang-listen/src/main.rs +++ b/nestsClient/tests/hang-interop/hang-listen/src/main.rs @@ -168,11 +168,15 @@ async fn listen( // Subscribe to the catalog and read the first published // version. The catalog hook in Amethyst's // `MoqLiteNestsSpeaker` (`setOnNewSubscriber`) can race the - // SUBSCRIBE bidi mid-suite — under accumulated state the - // first try sometimes resolves with a "cancelled" stream - // before the catalog frame arrives. Retry up to twice with - // a fresh subscribe; total wallclock ≤ 1 s, well inside - // the test's broadcast window. + // SUBSCRIBE bidi mid-suite — under accumulated relay state + // the first try sometimes resolves with "cancelled" before + // the catalog frame arrives. Retry up to 3 times with a + // 2 s per-attempt timeout (6 s total worst case). Under + // concurrent load (multiple jvmTest workers contending for + // the relay) the first attempt's wire round-trip can + // exceed 500 ms; the longer per-attempt budget keeps the + // happy-path fast (resolves on the first attempt) while + // tolerating slow handshakes. let info = { let mut last_err: Option = None; let mut decoded: Option = None; @@ -181,7 +185,7 @@ async fn listen( .subscribe_track(&hang::Catalog::default_track()) .context("subscribe catalog")?; let mut catalog = hang::CatalogConsumer::new(catalog_track); - match tokio::time::timeout(Duration::from_millis(500), catalog.next()).await { + match tokio::time::timeout(Duration::from_secs(2), catalog.next()).await { Ok(Ok(Some(c))) => { decoded = Some(c); break;