diff --git a/docs/WorkerFrameProtocol.md b/docs/WorkerFrameProtocol.md index b623707..36fe955 100644 --- a/docs/WorkerFrameProtocol.md +++ b/docs/WorkerFrameProtocol.md @@ -164,6 +164,17 @@ class drains both queues to empty, and the heartbeat loop guarantees one arrives within a heartbeat interval, so worst-case residency is a few envelope references for seconds — not a leak. +## Pipe Buffers + +The gateway creates each worker pipe with an explicit 128 KiB kernel buffer per +direction (`SessionWorkerClientFactory.PipeBufferSizeBytes`) rather than the zero +quota the short `NamedPipeServerStream` overloads request. A zero-quota byte-mode +pipe makes every write rendezvous with a pending read, so a writer with no reader +parked blocks until one arrives — the failure class behind the historical windev +full-suite wedge. A real quota decouples writer latency from reader scheduling and +lets the flush coalescing above actually pay off. On Unix hosts, where named pipes +are Unix domain sockets, the sizes are advisory. + ## Verification The frame protocol lives in `ZB.MOM.WW.MxGateway.Worker.Ipc` (`WorkerFrameReader`, diff --git a/src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionWorkerClientFactory.cs b/src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionWorkerClientFactory.cs index 4a14f6b..ab416d9 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionWorkerClientFactory.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionWorkerClientFactory.cs @@ -12,6 +12,18 @@ namespace ZB.MOM.WW.MxGateway.Server.Sessions; /// Factory for creating worker clients and launching worker processes. public sealed class SessionWorkerClientFactory : ISessionWorkerClientFactory { + /// + /// Kernel buffer quota requested for each direction of a worker pipe. A zero quota — what the + /// short overloads request — makes every byte-mode write + /// rendezvous with a pending read, so writer latency is coupled to reader scheduling and a + /// writer with no reader parked blocks indefinitely. That is the failure class behind the + /// historical windev full-suite wedge (all tests reported, testhost never exiting). A real + /// quota lets a whole frame land in the kernel and the writer return. 128 KiB comfortably + /// holds the control traffic and typical event batches without reserving nonpaged pool per + /// session for the rare maximum-sized frame, which still streams through in chunks. + /// + private const int PipeBufferSizeBytes = 128 * 1024; + private readonly IWorkerProcessLauncher _workerProcessLauncher; private readonly GatewayMetrics _metrics; private readonly TimeProvider _timeProvider; @@ -155,6 +167,11 @@ public sealed class SessionWorkerClientFactory : ISessionWorkerClientFactory /// Creates a named pipe for worker communication. /// The pipe name. /// Named pipe server stream. + /// + /// The buffer sizes are explicit so the pipe is not created with a zero quota; see + /// . On Unix hosts (the macOS test matrix, where named pipes + /// are Unix domain sockets) the sizes are advisory — the fix targets Windows production. + /// private static NamedPipeServerStream CreatePipe(string pipeName) { return new NamedPipeServerStream( @@ -162,7 +179,9 @@ public sealed class SessionWorkerClientFactory : ISessionWorkerClientFactory PipeDirection.InOut, maxNumberOfServerInstances: 1, PipeTransmissionMode.Byte, - PipeOptions.Asynchronous); + PipeOptions.Asynchronous, + inBufferSize: PipeBufferSizeBytes, + outBufferSize: PipeBufferSizeBytes); } /// Waits for a client to connect to the pipe.