fix(WRK-22,WRK-24,WRK-25,WRK-27,IPC-26): worker write-seam hardening
ci / java (push) Successful in 2m7s
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Failing after 1m13s
ci / portable (push) Failing after 4m6s

WRK-22/IPC-26: tombstone a WriteAsync/WriteBatchAsync cancelled while
waiting for the write lock (PendingFrame.Claimed under _gate; DequeueNext
skips cancelled, claims the frame it returns) so a cancelled write never
reaches the wire unless already claimed mid-write (documented residual).

WRK-25: add WriteBatchAsync; RunEventDrainLoopAsync submits the drained
event batch through it, so a burst of N events costs one flush not N.
IPC-30 oversized-event structured fault preserved via FindOversizedEvent.

WRK-24: reject a below-1024 negotiated frame maximum at the handshake
(MinNegotiableFrameBytes, matching GatewayOptionsValidator floor).

WRK-27: alarm poll advertises StaCallInProgress on the heartbeat snapshot
so the watchdog suppresses to the ceiling, not the grace.

Docs (WorkerFrameProtocol.md, MxAccessWorkerInstanceDesign.md) and the
2026-07-12 remediation registers/change-log updated in the same commit.
This commit is contained in:
Joseph Doherty
2026-08-07 07:50:38 -04:00
parent 10534ec906
commit 8df35cd63a
14 changed files with 912 additions and 58 deletions
@@ -24,6 +24,14 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
private CancellationTokenSource? alarmPollCts;
private Task? alarmPollTask;
private int? alarmConsumerThreadId;
// True on the STA thread exactly around the alarm PollOnce COM call. The alarm poll runs outside
// the StaCommandDispatcher (so it does not inflate PendingCommandCount or perturb command dispatch
// ordering), which means CaptureHeartbeat would otherwise see no in-flight activity during a long
// poll and the watchdog would fault the session at the 15 s grace instead of the 75 s ceiling
// granted to dispatched commands. Surfacing the poll on the heartbeat closes that asymmetry
// (WRK-27). Volatile: written on the STA thread, read on the heartbeat thread.
private volatile bool staAlarmPollInProgress;
private bool disposed;
/// <summary>
@@ -247,8 +255,20 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
await staRuntime.InvokeAsync(
() =>
{
EnsureOnAlarmConsumerThread();
handler.PollOnce();
// Advertise the poll to the watchdog for exactly the span of the COM call
// (WRK-27): set on the STA thread immediately before the affinity check and
// PollOnce, clear in the finally so a heartbeat captured mid-poll reports
// StaCallInProgress and one captured after does not.
staAlarmPollInProgress = true;
try
{
EnsureOnAlarmConsumerThread();
handler.PollOnce();
}
finally
{
staAlarmPollInProgress = false;
}
},
cancellationToken).ConfigureAwait(false);
}
@@ -377,7 +397,8 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
pendingCommandCount,
(uint)eventQueue.Count,
eventQueue.LastEventSequence,
currentCommandCorrelationId);
currentCommandCorrelationId,
staAlarmPollInProgress);
}
/// <inheritdoc />