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.