perf(scripting): #789 give the Windows worker pipe a 1 MiB buffer quota each way

With a zero quota an overlapped write stays pending until the peer has read the
bytes, so the new blocking writer waited out a peer wake-up per frame (windev
closed loop 54k -> 26k evals/s). With a quota the write completes once buffered.

Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk
This commit is contained in:
Joseph Doherty
2026-11-11 19:30:07 -05:00
parent a7fa327e61
commit 82da1ecbc8
@@ -50,11 +50,20 @@ internal static class WorkerPipeSecurity
return security;
}
/// <summary>
/// The pipe's buffer quota in each direction (lmxopcua#789). With a quota of 0 an overlapped write stays pending
/// until the peer has READ the bytes, so a blocking writer (<see cref="ZB.MOM.WW.OtOpcUa.Core.Scripting.Protocol.WindowsBlockingPipeStream"/>)
/// would wait out a full peer wake-up per frame — measured on windev as half the closed-loop throughput. With a
/// quota a write completes as soon as the bytes are buffered; a peer that stops reading still fills it and blocks
/// the writer, which the heartbeat monitor ends.
/// </summary>
internal const int BufferBytes = 1024 * 1024;
/// <summary>Creates the single-instance server end of a worker pipe with <paramref name="security"/>.</summary>
/// <param name="pipeName">The random pipe name.</param>
/// <param name="security">The ACL (<see cref="ForCurrentHost"/>).</param>
/// <returns>The server stream.</returns>
public static NamedPipeServerStream CreateServer(string pipeName, PipeSecurity security)
=> NamedPipeServerStreamAcl.Create(pipeName, PipeDirection.InOut, maxNumberOfServerInstances: 1,
PipeTransmissionMode.Byte, PipeOptions.Asynchronous, inBufferSize: 0, outBufferSize: 0, security);
PipeTransmissionMode.Byte, PipeOptions.Asynchronous, inBufferSize: BufferBytes, outBufferSize: BufferBytes, security);
}