perf(scripting): #789 Windows worker pipe I/O blocks on dedicated threads, off the completion port

A .NET pipe opened Asynchronous completes even its synchronous Read/Write
through the thread pool's completion port, so every frame on Windows cost a
completion packet plus a pool work item (and the pool's spin-wait). New
WindowsBlockingPipeStream issues its own overlapped ReadFile/WriteFile with the
OVERLAPPED.hEvent low bit set (no completion packet) and waits on that event on
the calling thread; reads and writes stay concurrent, unlike a synchronous
handle. ScriptWorkerFrameCodec.BlockingStream picks it on Windows and the pipe
itself on Unix, so both ends now use the dedicated blocking reader + synchronous
combining writer on every OS (UseBlockingIo and the async read loops are gone).

Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk
This commit is contained in:
Joseph Doherty
2026-11-11 19:11:10 -05:00
parent f109e5f29e
commit 3c0e5ea4d7
7 changed files with 328 additions and 83 deletions
@@ -22,9 +22,8 @@ internal static class Program
PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
try
{
// Synchronous on Unix so the socket under the pipe stays blocking (ScriptWorkerFrameCodec.UseBlockingIo).
if (ScriptWorkerFrameCodec.UseBlockingIo) pipe.Connect(TimeSpan.FromSeconds(10));
else await pipe.ConnectAsync(TimeSpan.FromSeconds(10), CancellationToken.None);
// Synchronous, so the socket under a Unix pipe stays blocking (ScriptWorkerFrameCodec.BlockingStream).
pipe.Connect(TimeSpan.FromSeconds(10));
}
catch (Exception ex)
{
@@ -32,7 +31,7 @@ internal static class Program
return ScriptWorkerEnvironment.ExitProtocolFault;
}
var host = new WorkerHost(pipe, nonce, parentPid);
var host = new WorkerHost(ScriptWorkerFrameCodec.BlockingStream(pipe), nonce, parentPid);
return await host.RunAsync();
}
}