fix(nests-tests): per-method relay reset + 2 s catalog timeout
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
This commit is contained in:
+9
@@ -89,6 +89,15 @@ class HangInteropTest {
|
|||||||
@BeforeTest
|
@BeforeTest
|
||||||
fun gate() {
|
fun gate() {
|
||||||
NativeMoqRelayHarness.assumeHangInterop()
|
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. */
|
/** I1: 5 s 440 Hz mono sine, asserted via FFT peak + ZCR. */
|
||||||
|
|||||||
+23
@@ -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 {
|
private fun doStart(): NativeMoqRelayHarness {
|
||||||
check(isEnabled()) {
|
check(isEnabled()) {
|
||||||
"NativeMoqRelayHarness.shared() called without -D$ENABLE_PROPERTY=true."
|
"NativeMoqRelayHarness.shared() called without -D$ENABLE_PROPERTY=true."
|
||||||
|
|||||||
@@ -168,11 +168,15 @@ async fn listen(
|
|||||||
// Subscribe to the catalog and read the first published
|
// Subscribe to the catalog and read the first published
|
||||||
// version. The catalog hook in Amethyst's
|
// version. The catalog hook in Amethyst's
|
||||||
// `MoqLiteNestsSpeaker` (`setOnNewSubscriber`) can race the
|
// `MoqLiteNestsSpeaker` (`setOnNewSubscriber`) can race the
|
||||||
// SUBSCRIBE bidi mid-suite — under accumulated state the
|
// SUBSCRIBE bidi mid-suite — under accumulated relay state
|
||||||
// first try sometimes resolves with a "cancelled" stream
|
// the first try sometimes resolves with "cancelled" before
|
||||||
// before the catalog frame arrives. Retry up to twice with
|
// the catalog frame arrives. Retry up to 3 times with a
|
||||||
// a fresh subscribe; total wallclock ≤ 1 s, well inside
|
// 2 s per-attempt timeout (6 s total worst case). Under
|
||||||
// the test's broadcast window.
|
// 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 info = {
|
||||||
let mut last_err: Option<anyhow::Error> = None;
|
let mut last_err: Option<anyhow::Error> = None;
|
||||||
let mut decoded: Option<hang::Catalog> = None;
|
let mut decoded: Option<hang::Catalog> = None;
|
||||||
@@ -181,7 +185,7 @@ async fn listen(
|
|||||||
.subscribe_track(&hang::Catalog::default_track())
|
.subscribe_track(&hang::Catalog::default_track())
|
||||||
.context("subscribe catalog")?;
|
.context("subscribe catalog")?;
|
||||||
let mut catalog = hang::CatalogConsumer::new(catalog_track);
|
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))) => {
|
Ok(Ok(Some(c))) => {
|
||||||
decoded = Some(c);
|
decoded = Some(c);
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user