From 5f4ecabc8ac530b823c9dd0dfe77d0af241b4d89 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 14 Jul 2026 05:35:05 -0400 Subject: [PATCH] test(flaky): decouple fake-worker CreateAsync anti-hang net from the semantic timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nightly (run #47) portable job failed CreateAsync_WhenFakeWorkerNeverSendsReady _TimesOutAndKillsWorker: it asserts the factory's TimeoutException carries 'did not complete startup' (thrown at the session's 1s startup timeout), but the test's .WaitAsync(TestTimeout=5s) anti-hang net tripped first under CI load and surfaced .NET's generic 'The operation has timed out.' — same commit passed on the push run (#46), so it's a load-sensitive timeout race, not a regression. Raising runner capacity to 4 makes concurrent-load flakes like this more likely. Fix: give the two CreateAsync failure/timeout tests a dedicated 30s HangGuardTimeout, far above the 1s semantic timeout under test, so the factory's own exception is always the one observed. The net still catches a genuine hang. Note: this class cannot run on macOS (named pipes -> Unix domain sockets fail on the pipe path); verification is via CI Linux. --- .../SessionWorkerClientFactoryFakeWorkerTests.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionWorkerClientFactoryFakeWorkerTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionWorkerClientFactoryFakeWorkerTests.cs index 13534a0..ed808bc 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionWorkerClientFactoryFakeWorkerTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionWorkerClientFactoryFakeWorkerTests.cs @@ -15,6 +15,13 @@ public sealed class SessionWorkerClientFactoryFakeWorkerTests : IAsyncDisposable { private static readonly TimeSpan TestTimeout = TimeSpan.FromSeconds(5); + // Anti-hang guard for the CreateAsync failure/timeout tests. It only exists to stop a genuine + // hang from wedging the run, so it must be far larger than any in-test semantic timeout (the + // factory startup timeout being exercised) — otherwise, under CI load, this WaitAsync net can + // trip before the factory's own timeout propagates and the test observes .NET's generic + // "The operation has timed out." instead of the asserted "did not complete startup". + private static readonly TimeSpan HangGuardTimeout = TimeSpan.FromSeconds(30); + private readonly List _launchers = []; /// @@ -79,7 +86,7 @@ public sealed class SessionWorkerClientFactoryFakeWorkerTests : IAsyncDisposable GatewaySession session = CreateSession(); WorkerClientException exception = await Assert.ThrowsAsync( - async () => await factory.CreateAsync(session, CancellationToken.None).WaitAsync(TestTimeout)); + async () => await factory.CreateAsync(session, CancellationToken.None).WaitAsync(HangGuardTimeout)); Assert.Equal(WorkerClientErrorCode.ProtocolViolation, exception.ErrorCode); Assert.True(launcher.Process.IsDisposed); @@ -100,7 +107,7 @@ public sealed class SessionWorkerClientFactoryFakeWorkerTests : IAsyncDisposable GatewaySession session = CreateSession(startupTimeout: TimeSpan.FromSeconds(1)); TimeoutException exception = await Assert.ThrowsAsync( - async () => await factory.CreateAsync(session, CancellationToken.None).WaitAsync(TestTimeout)); + async () => await factory.CreateAsync(session, CancellationToken.None).WaitAsync(HangGuardTimeout)); Assert.Contains("did not complete startup", exception.Message); Assert.Equal(1, launcher.Process.KillCount);