Partial Stream Read Fix and Floating Node Fix#14479
Conversation
There was a problem hiding this comment.
Pull request overview
This PR hardens MSBuild’s out-of-proc node IPC and shutdown behavior to address MSB4166/ConnectionFailed scenarios on stream-based transports (notably Unix domain sockets on FreeBSD) and to avoid leaving reusable nodes running after abnormal build termination.
Changes:
- Update parent-side packet reading in
NodeProviderOutOfProcBaseto reliably accumulate fragmented reads for both the 5-byte header and the packet body. - Update child-side header reads in
NodeEndpointOutOfProcBaseto tolerate fragmented delivery of the 5-byte header. - Adjust
BuildManager.HandleNodeShutdownabort shutdown logic to ensure connected nodes are shut down without reuse after abnormal termination.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/Shared/NodeEndpointOutOfProcBase.cs | Accumulates partial reads to reliably read the 5-byte packet header on stream-based IPC. |
| src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs | Accumulates partial reads for packet headers and bodies to prevent false ConnectionFailed failures on fragmented reads. |
| src/Build/BackEnd/BuildManager/BuildManager.cs | Forces non-reuse shutdown of connected nodes on abnormal node shutdown to avoid lingering reusable nodes. |
I'm a bit confused by the description, is it:
Either ways, I think the better fix would be: #if NET
// use https://learn.microsoft.com/dotnet/api/system.io.stream.readexactly
// and/or https://learn.microsoft.com/dotnet/api/system.io.stream.readexactlyasync
#else
// existing code here
#endifwithout forcing localReadPipe over readTask in newer .NET. |
This PR fix two separate issues:
zombie processes were there before the crash, but it was quite "troublesome" to handle them always and the fix was easy (as they were left there for 15min after each failed build for ex.). As for using |
It just expands the matrix for maintainers to review, there is a huge legacy code depending on the existing behavior. Therefore, it make sense to keep the fix as targeted as possible. Also, it's not exactly 'correct'; on .NET Core, the code in main branch prefers |
can someone more familiar with msbuild maybe help on that? for me those places looked the the fail point, becuase they were reading for ex. 2 bytes and expecting 5 - which was causing nodes to crash, as internals on FreeBSD 15 got changed, but the work in correct way, if the stream have more data, calling one read shouldn't not assume all data is returned. |
|
FreeBSD 15 is a community platform released few months ago, so selling a "fix" on a 23y old product for a non-official platform raises questions. I was trying to help you since you've pinged me on the issue. Good luck! |
|
#12944 is not FreeBSD-specific, it is just much easier to bump into this issue on FreeBSD. |
That doesn't say anything about .NET Framework / Windows. My suggestion above isn't |
Fixes #12944
cc @Thefrank @arrowd @am11
Confirmed the fix by doing full VMR builds under FreeBSD 14 and 15 (soon arm64 should also complete).
1. Floating Node Fix (Shutdown Logic)
Context: When a build fails or aborts and MSBuild is tearing down, child nodes were sometimes left floating as zombie processes if the build was initially started with
/nodeReuse:true.Fix: Modified
BuildManager.HandleNodeShutdownto explicitly passenableNodeReuse: falsewhen aborting the build (e.g., when theshutdownPacketindicatesConnectionFailed). This guarantees that nodes are correctly forcefully shut down upon an abnormal termination instead of lingering in the background waiting for a new connection.2. Partial Stream Read Fix (
MSB4166crash on FreeBSD 15)Context: When building the
dotnetVMR on FreeBSD 15, MSBuild frequently crashes withMSB4166: Child node "N" exited prematurely.Root Cause: MSBuild had a long-standing assumption in its IPC code where it assumed that a single
Stream.ReadAsyncorStream.EndReadcall on a NamedPipe/Unix Domain Socket would always return the exact requested number of bytes (in this case, the 5-byte MSBuild packet header). While this generally holds true for Windows Named Pipes operating in message mode, Unix Domain Sockets on Linux/FreeBSD are purely stream-oriented. The FreeBSD 15 kernel occasionally fragments the UDS packet delivery (e.g., delivering 2 bytes, then 3 bytes). When MSBuild received only 2 bytes fromReadAsync, it immediately failed itsbytesRead == Expectedcheck and threw aConnectionFailedexception, killing the child process that was otherwise completely healthy.Fix: Rewrote the IPC stream reading loops on both the parent side (
NodeProviderOutOfProcBase.cs) and the child side (NodeEndpointOutOfProcBase.cs). The logic now correctly utilizeswhileloops that accumulate stream fragments synchronously and asynchronously until the full 5-byte header or packet body is received, fully mitigating fragmentation on Unix Domain Sockets.