From aaeb86b1327df1b1363d7c5077ef6b946fa40e2d Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 18 Aug 2026 07:08:47 -0400 Subject: [PATCH] fix(worker-tests): decouple FakeRuntimeSession dispatch safety-net from the inspection window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The BlockDispatch branch waited 5 real seconds and then proceeded regardless — it does not branch on the wait's result. The long-in-flight test's inspection loop is bounded by elapsed time and a frame floor, so on a loaded box (the documented 4-5x slowdown class) it can plausibly outrun that 5 s. When it does, the reply is emitted mid-window, AssertNotWorkerFault waves it past, and the reply leg then waits for a reply already gone by — failing at the 20 s cancellation with no message, on exactly the loaded-box run the widened windows exist to survive. The wait is a pure safety net: nothing asserts on it firing, and every test that blocks dispatch releases it explicitly (ReleaseDispatch, or a WorkerShutdown envelope, both of which Set the event) — none reaches the timeout on a healthy run. Named it BlockedDispatchSafetyNet and raised it to 30 s, above any window a test opens and above the 20 s cancellation those tests arm, so a wedged test always fails on its own token with its own message. Dispose still releases the wait, so teardown never waits on it either. Inline rationale in the test now states the decoupling and what a close pairing would cost, rather than asserting the window stays inside a 5 s ceiling. Nothing else changed. --- .../Ipc/WorkerPipeSessionTests.cs | 9 ++++++--- .../TestSupport/FakeRuntimeSession.cs | 13 ++++++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerPipeSessionTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerPipeSessionTests.cs index 1bef756..dca5936 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerPipeSessionTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerPipeSessionTests.cs @@ -1525,9 +1525,12 @@ public sealed class WorkerPipeSessionTests // activity timestamp stopped advancing would accumulate staleness past the ceiling and // fault — and require the beats to have actually flowed while it ran, so an inspection // that saw a couple of frames and timed out cannot pass for a clean window. None may be a - // WorkerFault while activity is continuously refreshed. The window stays well inside - // FakeRuntimeSession's 5 s dispatch-block ceiling, so the command is still in flight - // throughout. + // WorkerFault while activity is continuously refreshed. Nothing here is racing + // FakeRuntimeSession's blocked-dispatch backstop: that wait is a safety net sized far above + // any window a test opens (and above this test's own cancellation), so the command stays in + // flight for however long a loaded box stretches the loop. Were the two close together, a + // slow run would take the reply mid-window and then fail waiting for a reply already gone + // by — a cancellation at teardown, naming nothing. TimeSpan inspectionWindow = TimeSpan.FromSeconds(2); const int minimumFramesInspected = 30; Stopwatch inspection = Stopwatch.StartNew(); diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/TestSupport/FakeRuntimeSession.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/TestSupport/FakeRuntimeSession.cs index c7b9ebf..0f89658 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/TestSupport/FakeRuntimeSession.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/TestSupport/FakeRuntimeSession.cs @@ -20,6 +20,17 @@ namespace ZB.MOM.WW.MxGateway.Worker.Tests.TestSupport; /// internal sealed class FakeRuntimeSession : IWorkerRuntimeSession { + /// + /// Backstop on the wait so a test that never releases leaves no + /// thread parked forever. It is a safety net, never a scenario's timing budget: nothing + /// asserts on it firing, and a test whose blocked window outruns it silently gets its reply + /// mid-window, which then fails as an opaque cancellation somewhere later. Kept far above + /// any test's window — and above the 20 s cancellation those tests arm — so the test's own + /// token always fails first, with its own message. releases the wait + /// regardless, so teardown never depends on this elapsing. + /// + private static readonly TimeSpan BlockedDispatchSafetyNet = TimeSpan.FromSeconds(30); + private readonly ManualResetEventSlim releaseDispatch = new(false); private readonly object gate = new(); private readonly Queue events = new(); @@ -91,7 +102,7 @@ internal sealed class FakeRuntimeSession : IWorkerRuntimeSession if (BlockDispatch) { - releaseDispatch.Wait(TimeSpan.FromSeconds(5)); + releaseDispatch.Wait(BlockedDispatchSafetyNet); } SetSnapshot(new WorkerRuntimeHeartbeatSnapshot(