Resolve Worker-001, Worker-002, Worker-003 code-review findings

Worker-001: WnWrapAlarmConsumer armed a System.Threading.Timer whose OnPoll
callback ran GetXmlCurrentAlarms2 on a thread-pool thread against the
Apartment-threaded wnwrap COM object, which can deadlock on cross-apartment
marshaling. Removed the pollTimer/pollIntervalMs fields, OnPoll, the
poll-interval constructor parameter, and the timer arm/disposal. Polls are
driven externally by the STA via StaRuntime.InvokeAsync(PollOnce).

Worker-002: RunHeartbeatLoopAsync delayed a full HeartbeatInterval before
the first heartbeat. Restructured so the first beat is sent immediately on
entering the loop and the delay applies only between subsequent beats.

Worker-003: ProcessCommandAsync silently returned without a reply when
_state was not a command-serving state after dispatch. Both drop sites now
log a WorkerCommandResultDropped diagnostic with correlation_id via
IWorkerLogger; _state is now volatile.

Three pre-existing tests that asserted strict frame ordering were updated to
tolerate an interleaved first heartbeat (Worker-002 consequence).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-18 20:59:46 -04:00
parent e967e85973
commit 53e3973209
8 changed files with 323 additions and 97 deletions
@@ -77,7 +77,9 @@ public sealed class WorkerPipeClientTests
},
});
WorkerEnvelope shutdownAck = await reader.ReadAsync();
WorkerEnvelope shutdownAck = await ReadUntilAsync(
reader,
WorkerEnvelope.BodyOneofCase.WorkerShutdownAck);
Assert.Equal(WorkerEnvelope.BodyOneofCase.WorkerShutdownAck, shutdownAck.BodyCase);
await clientTask;
}
@@ -120,7 +122,9 @@ public sealed class WorkerPipeClientTests
Assert.Equal(WorkerEnvelope.BodyOneofCase.WorkerReady, (await reader.ReadAsync()).BodyCase);
await writer.WriteAsync(CreateShutdown());
Assert.Equal(WorkerEnvelope.BodyOneofCase.WorkerShutdownAck, (await reader.ReadAsync()).BodyCase);
Assert.Equal(
WorkerEnvelope.BodyOneofCase.WorkerShutdownAck,
(await ReadUntilAsync(reader, WorkerEnvelope.BodyOneofCase.WorkerShutdownAck)).BodyCase);
await clientTask;
}
@@ -143,6 +147,25 @@ public sealed class WorkerPipeClientTests
await Assert.ThrowsAsync<TimeoutException>(async () => await client.RunAsync(workerOptions));
}
/// <summary>
/// Reads frames until one matching the expected body case is found,
/// skipping interleaved heartbeats (the first heartbeat is emitted
/// immediately on entering the heartbeat loop — see Worker-002).
/// </summary>
private static async Task<WorkerEnvelope> ReadUntilAsync(
WorkerFrameReader reader,
WorkerEnvelope.BodyOneofCase expectedBody)
{
while (true)
{
WorkerEnvelope envelope = await reader.ReadAsync();
if (envelope.BodyCase == expectedBody)
{
return envelope;
}
}
}
private static WorkerPipeSession CreateSession(
Stream stream,
WorkerFrameProtocolOptions options)