Fix hanging and timing-fragile WorkerClient event-channel tests

The Server-032 change made event-channel overflow wait
EventChannelFullModeTimeout before faulting, instead of faulting
instantly. Two pre-existing overflow tests were not updated and left
EventChannelFullModeTimeout at its 5s default, which races the 5s
TestTimeout: ReadLoop_WhenEventQueueOverflows_FaultsClient and
ReadLoop_WhenClientFaults_KillsOwnedWorkerProcess. Pin it to 50ms in
both so overflow faults promptly.

EnqueueWorkerEvent_WhenChannelFullPastTimeout_FaultsWithRichDiagnostic
wrote 6 events into a 4-slot channel, but the worker client faults
while reading the 5th and its read loop then stops — the 6th event is
never drained and the test's pipe write for it blocks forever on a
full OS pipe buffer, hanging the test host. Write exactly 5 (4 to fill
plus 1 to overflow) as the test comment already intends, and bound the
post-fault event drain with TestTimeout so a future regression fails
instead of hanging.

No production change: the Server-031/032 WorkerClient logic is correct
— these were test-only defects.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-21 15:19:59 -04:00
parent 0a54fa5e35
commit bf73985481
@@ -128,6 +128,7 @@ public sealed class WorkerClientTests
new WorkerClientOptions
{
EventChannelCapacity = 1,
EventChannelFullModeTimeout = TimeSpan.FromMilliseconds(50),
HeartbeatGrace = TimeSpan.FromSeconds(30),
HeartbeatCheckInterval = TimeSpan.FromSeconds(30),
});
@@ -163,6 +164,7 @@ public sealed class WorkerClientTests
new WorkerClientOptions
{
EventChannelCapacity = 1,
EventChannelFullModeTimeout = TimeSpan.FromMilliseconds(50),
HeartbeatGrace = TimeSpan.FromSeconds(30),
HeartbeatCheckInterval = TimeSpan.FromSeconds(30),
},
@@ -483,10 +485,13 @@ public sealed class WorkerClientTests
});
await CompleteHandshakeAsync(client, pipePair);
// Fill the channel plus one to force the overflow path. The gateway
// never opens a StreamEvents consumer so the events stay in the
// bounded channel.
for (ulong sequence = 1; sequence <= 6; sequence++)
// Fill the 4-slot channel and write exactly one more to force the
// overflow path. The gateway never opens a StreamEvents consumer, so
// the events stay buffered. Exactly five events are written: the
// worker client faults while reading the fifth, after which its read
// loop stops — a sixth event would never be drained and its pipe
// write would block forever on a full OS pipe buffer.
for (ulong sequence = 1; sequence <= 5; sequence++)
{
await pipePair.WorkerWriter.WriteAsync(
CreateEventEnvelope(sequence: sequence, MxEventFamily.OnDataChange));
@@ -499,10 +504,13 @@ public sealed class WorkerClientTests
Assert.Equal(WorkerClientState.Faulted, client.State);
// Reading the events channel after fault throws the propagated
// WorkerClientException carrying the rich diagnostic message.
// WorkerClientException carrying the rich diagnostic message. The
// drain is bounded by TestTimeout so a regression that leaves the
// channel uncompleted fails the test instead of hanging it.
using CancellationTokenSource drainTimeout = new(TestTimeout);
WorkerClientException fault = await Assert.ThrowsAsync<WorkerClientException>(async () =>
{
await foreach (WorkerEvent _ in client.ReadEventsAsync(CancellationToken.None))
await foreach (WorkerEvent _ in client.ReadEventsAsync(drainTimeout.Token))
{
}
});