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
|
arrives within a heartbeat interval, so worst-case residency is a few envelope
|
||||||
references for seconds — not a leak.
|
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
|
## Verification
|
||||||
|
|
||||||
The frame protocol lives in `ZB.MOM.WW.MxGateway.Worker.Ipc` (`WorkerFrameReader`,
|
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>
|
/// <summary>Factory for creating worker clients and launching worker processes.</summary>
|
||||||
public sealed class SessionWorkerClientFactory : ISessionWorkerClientFactory
|
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 IWorkerProcessLauncher _workerProcessLauncher;
|
||||||
private readonly GatewayMetrics _metrics;
|
private readonly GatewayMetrics _metrics;
|
||||||
private readonly TimeProvider _timeProvider;
|
private readonly TimeProvider _timeProvider;
|
||||||
@@ -155,6 +167,11 @@ public sealed class SessionWorkerClientFactory : ISessionWorkerClientFactory
|
|||||||
/// <summary>Creates a named pipe for worker communication.</summary>
|
/// <summary>Creates a named pipe for worker communication.</summary>
|
||||||
/// <param name="pipeName">The pipe name.</param>
|
/// <param name="pipeName">The pipe name.</param>
|
||||||
/// <returns>Named pipe server stream.</returns>
|
/// <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)
|
private static NamedPipeServerStream CreatePipe(string pipeName)
|
||||||
{
|
{
|
||||||
return new NamedPipeServerStream(
|
return new NamedPipeServerStream(
|
||||||
@@ -162,7 +179,9 @@ public sealed class SessionWorkerClientFactory : ISessionWorkerClientFactory
|
|||||||
PipeDirection.InOut,
|
PipeDirection.InOut,
|
||||||
maxNumberOfServerInstances: 1,
|
maxNumberOfServerInstances: 1,
|
||||||
PipeTransmissionMode.Byte,
|
PipeTransmissionMode.Byte,
|
||||||
PipeOptions.Asynchronous);
|
PipeOptions.Asynchronous,
|
||||||
|
inBufferSize: PipeBufferSizeBytes,
|
||||||
|
outBufferSize: PipeBufferSizeBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Waits for a client to connect to the pipe.</summary>
|
/// <summary>Waits for a client to connect to the pipe.</summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user