feat(worker): adopt negotiated frame max, bound drain, priority write scheduler (IPC-02/04 + WRK-04/07 worker half)

Worker half of the Wave 3 size/backpressure + write-ordering pass:

- IPC-02: the worker adopts GatewayHello.max_frame_bytes during the handshake
  (WorkerFrameProtocolOptions.AdoptNegotiatedMaxMessageBytes) instead of a
  hard-coded default; 0 keeps the default, a value above a 256 MiB ceiling is
  rejected. Reader and writer share the options instance, applied before the
  message loop.
- IPC-04: DrainEvents caps each reply at MaxDrainEventsPerReply (10_000) and
  treats max_events = 0 as that cap rather than 'drain the entire queue', so one
  diagnostic drain cannot pack a session-killing reply frame.
- WRK-04: WorkerFrameWriter stamps the envelope Sequence at the actual point of
  writing (under the write lock) instead of at envelope creation, so the on-wire
  order and the stamped sequence always agree under concurrent producers.
- WRK-07: the writer is now a cooperative priority scheduler — callers enqueue at
  Control or Event priority and the draining lock-holder writes all control
  frames before any event frame, so replies/faults/heartbeats jump ahead of an
  event backlog. Per-frame validation/size rejections fail only that frame; a
  stream write failure fails all queued frames.

Tests: monotonic gap-free sequence under concurrency, control-before-event
priority (gated stream), negotiated-max adoption, DrainEvents zero-bound.
Worker builds x86 only — verified on windev.
This commit is contained in:
Joseph Doherty
2026-07-09 09:09:14 -04:00
parent c8b3a2281a
commit ebe6aeac98
7 changed files with 450 additions and 30 deletions
@@ -125,6 +125,14 @@ internal sealed class FakeRuntimeSession : IWorkerRuntimeSession
/// </summary>
public uint? SuppressDrainForBatchSize { get; set; }
/// <summary>
/// Records the <c>maxEvents</c> argument of the most recent non-suppressed
/// <see cref="DrainEvents"/> call — i.e. the effective cap the session passed for an explicit
/// DrainEvents control command. Lets a test assert the worker bounds the drain (IPC-04) rather
/// than forwarding the client's raw <c>max_events = 0</c>.
/// </summary>
public uint? LastDrainMaxEvents { get; private set; }
/// <inheritdoc />
public IReadOnlyList<WorkerEvent> DrainEvents(uint maxEvents)
{
@@ -133,6 +141,8 @@ internal sealed class FakeRuntimeSession : IWorkerRuntimeSession
return Array.Empty<WorkerEvent>();
}
LastDrainMaxEvents = maxEvents;
lock (gate)
{
int drainCount = maxEvents == 0