test(flaky): deflake SessionManager fail-fast timing assertions
CI (portable, run #38) intermittently failed SessionManagerTests .InvokeAsync_WhenTimeoutZero_FailsFastUnchanged with 'expected immediate fail-fast but took 155ms'. The fail-fast paths throw synchronously under the lock (GatewaySession.GetReadyWorkerClientAsync) and never enter the poll loop, so the absolute <100ms wall-clock bound measured only host load, not behavior, and flaked under CI contention. - WhenWorkerFaulted_FailsFastWithBothStates: anchor the bound to a large (5000ms) ready-wait timeout and assert fail-fast returns in < timeout/3 — a regression that burned the timeout is still caught, but scheduling jitter can't trip it. - WhenTimeoutZero_FailsFastUnchanged: drop the wall-clock assertion entirely (a zero timeout has no wait window to burn); the error code, both-states message, and InvokeCount == 0 already pin the immediate fail-fast. Verified: the 3 SessionManager timing tests pass (net10.0).
This commit is contained in:
@@ -420,10 +420,16 @@ public sealed class SessionManagerTests
|
||||
[Fact]
|
||||
public async Task InvokeAsync_WhenWorkerFaulted_FailsFastWithBothStates()
|
||||
{
|
||||
// Use a deliberately large ready-wait timeout so fail-fast is unambiguous: a terminal
|
||||
// worker must surface immediately instead of burning it. The timing assertion below is
|
||||
// anchored to a small fraction of this timeout (not an absolute ~100ms wall-clock bound,
|
||||
// which flaked under CI load) so a regression that waited out the timeout is caught
|
||||
// while ordinary scheduling jitter never trips it.
|
||||
const int readyWaitTimeoutMs = 5000;
|
||||
FakeWorkerClient workerClient = new() { State = WorkerClientState.Faulted };
|
||||
SessionManager manager = CreateManager(
|
||||
new FakeSessionWorkerClientFactory(workerClient),
|
||||
options: CreateOptions(workerReadyWaitTimeoutMs: 500));
|
||||
options: CreateOptions(workerReadyWaitTimeoutMs: readyWaitTimeoutMs));
|
||||
GatewaySession session = await manager.OpenSessionAsync(CreateOpenRequest(), "client-1", ownerKeyId: null, CancellationToken.None);
|
||||
Assert.Equal(SessionState.Ready, session.State);
|
||||
|
||||
@@ -435,7 +441,9 @@ public sealed class SessionManagerTests
|
||||
CancellationToken.None));
|
||||
stopwatch.Stop();
|
||||
|
||||
Assert.True(stopwatch.ElapsedMilliseconds < 100, $"Expected immediate fail-fast but took {stopwatch.ElapsedMilliseconds}ms.");
|
||||
Assert.True(
|
||||
stopwatch.ElapsedMilliseconds < readyWaitTimeoutMs / 3,
|
||||
$"Expected fail-fast well under the {readyWaitTimeoutMs}ms ready-wait timeout but took {stopwatch.ElapsedMilliseconds}ms.");
|
||||
Assert.Equal(SessionManagerErrorCode.SessionNotReady, exception.ErrorCode);
|
||||
Assert.Contains("Session state is Ready", exception.Message);
|
||||
Assert.Contains("worker state is Faulted", exception.Message);
|
||||
@@ -487,15 +495,17 @@ public sealed class SessionManagerTests
|
||||
GatewaySession session = await manager.OpenSessionAsync(CreateOpenRequest(), "client-1", ownerKeyId: null, CancellationToken.None);
|
||||
Assert.Equal(SessionState.Ready, session.State);
|
||||
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
// A zero timeout has no wait window to burn, so there is no wall-clock timing to assert
|
||||
// (the previous absolute ~100ms bound only measured host load and flaked under CI). The
|
||||
// error code, the byte-for-byte both-states message, and InvokeCount == 0 fully pin the
|
||||
// immediate fail-fast — a regression that started polling would still surface the same
|
||||
// outcome, not a timing difference worth a flaky assertion.
|
||||
SessionManagerException exception = await Assert.ThrowsAsync<SessionManagerException>(
|
||||
async () => await manager.InvokeAsync(
|
||||
session.SessionId,
|
||||
CreateCommand(MxCommandKind.Ping),
|
||||
CancellationToken.None));
|
||||
stopwatch.Stop();
|
||||
|
||||
Assert.True(stopwatch.ElapsedMilliseconds < 100, $"Expected immediate fail-fast but took {stopwatch.ElapsedMilliseconds}ms.");
|
||||
Assert.Equal(SessionManagerErrorCode.SessionNotReady, exception.ErrorCode);
|
||||
Assert.Contains("Session state is Ready", exception.Message);
|
||||
Assert.Contains("worker state is Handshaking", exception.Message);
|
||||
|
||||
Reference in New Issue
Block a user