Resolve Worker-009..015 code-review findings

Worker-009: WorkerFrameWriter serialized twice and WorkerFrameReader
allocated a payload byte[] per frame. The writer now serializes once into a
single prefix+payload buffer; the reader rents the payload buffer from
ArrayPool and honors the logical frame length.

Worker-010: VariantConverter projected a uint+Time value as a full FILETIME,
producing a near-1601 timestamp. The FILETIME projection is now gated on
`value is long`; uint falls through to the integer projection.

Worker-011: replaced the opaque retryAttempts formula in WorkerPipeClient
with MaxRetryAttempts = int.MaxValue, leaving the connect deadline as the
sole bound.

Worker-012: rewrote stale "future PR / polls on a Timer" comments in
AlarmDispatcher, AlarmCommandHandler, MxAccessAlarmEventSink and
MxAccessEventMapper to match the shipped, post-Worker-001 behavior.

Worker-013 (re-triaged): already resolved — StaMessagePumpTests and
MxAccessStaSessionTests cover the pump and poll loop directly.

Worker-014: moved IAlarmCommandHandler into its own file so
AlarmCommandHandler.cs declares one public type.

Worker-015: clarified the MxAccessBaseEventSink.EnqueueEvent overflow-catch
comment explaining the deliberate double RecordFault no-op.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-18 22:42:17 -04:00
parent fe9044115b
commit 1764eff1cf
13 changed files with 229 additions and 127 deletions
@@ -11,13 +11,15 @@ namespace MxGateway.Worker.MxAccess;
/// </summary>
/// <remarks>
/// <para>
/// The dispatcher subscribes the consumer's
/// <see cref="AlarmDispatcher"/> owns the wire-up: it constructs the
/// consumer/sink pair, calls <see cref="Attach"/> to propagate the
/// session id, and subscribes the consumer's
/// <see cref="IMxAccessAlarmConsumer.AlarmTransitionEmitted"/> event
/// to <see cref="EnqueueTransition"/> at session attach time. The
/// <see cref="Attach"/> override here is a stub kept for the data-
/// session shape; the actual wire-up between consumer and sink
/// lives in the A.3 dispatcher (one step up the stack). Captured
/// payload schema and consumer threading discipline are described in
/// so each decoded transition reaches <see cref="EnqueueTransition"/>.
/// The <see cref="Attach"/> method here carries only the session id —
/// the alarm path needs no COM-event subscription of its own because
/// the consumer already polls and raises transition events. The
/// captured payload schema is described in
/// <c>docs/AlarmClientDiscovery.md</c> "Option A — captured".
/// </para>
/// </remarks>
@@ -47,10 +49,10 @@ public sealed class MxAccessAlarmEventSink : IMxAccessEventSink
if (mxAccessComObject is null) throw new ArgumentNullException(nameof(mxAccessComObject));
this.sessionId = sessionId ?? string.Empty;
// PR A.2 — COM-side subscription scaffold. The MXAccess Toolkit alarm
// event source is pinned during dev-rig validation. Until then, the
// worker advertises no alarm subscription; data-change behaviour is
// unaffected.
// The alarm path needs no COM-event subscription here: the wnwrap
// consumer is polled by the worker's STA and raises transition events
// that AlarmDispatcher routes into EnqueueTransition. Attach only
// records the session id stamped onto every emitted MxEvent.
attached = true;
}