perf(ipc): give worker pipes real OS buffers instead of zero-quota rendezvous
This commit is contained in:
@@ -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`,
|
||||
|
||||
@@ -12,6 +12,18 @@ namespace ZB.MOM.WW.MxGateway.Server.Sessions;
|
||||
/// <summary>Factory for creating worker clients and launching worker processes.</summary>
|
||||
public sealed class SessionWorkerClientFactory : ISessionWorkerClientFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Kernel buffer quota requested for each direction of a worker pipe. A zero quota — what the
|
||||
/// short <see cref="NamedPipeServerStream"/> 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.
|
||||
/// </summary>
|
||||
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
|
||||
/// <summary>Creates a named pipe for worker communication.</summary>
|
||||
/// <param name="pipeName">The pipe name.</param>
|
||||
/// <returns>Named pipe server stream.</returns>
|
||||
/// <remarks>
|
||||
/// The buffer sizes are explicit so the pipe is not created with a zero quota; see
|
||||
/// <see cref="PipeBufferSizeBytes"/>. On Unix hosts (the macOS test matrix, where named pipes
|
||||
/// are Unix domain sockets) the sizes are advisory — the fix targets Windows production.
|
||||
/// </remarks>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>Waits for a client to connect to the pipe.</summary>
|
||||
|
||||
Reference in New Issue
Block a user