Skip to content

Node reuse node count includes nodes that can't be reused by the counting instance #15061

Description

@ViktorHofer

CountActiveNodesWithMode / GetPossibleRunningNodes filter only on process name MSBuild plus NodeMode.OutOfProcNode in the command line. Nothing checks whether a counted node is actually usable by the counting instance, so the count includes nodes that instance can never reuse. That count then feeds DetermineNodesForReuse, which terminates the instance's own nodes.

Measured on a .NET VMR CI agent building up to 8 repos in parallel, 8 cores, threshold 12. Counts logged at build completion:

repo build system-wide count owned by this instance kept
arcade 18 8 2
symreader 30 4 0
xdt 31 4 0
cecil 33 6 0
emsdk 46 4 0
nuget-client 59 8 0
fsharp 69 14 0

Two independent reasons a counted node is unusable, both observed:

1. The node is busy. It is attached to another concurrently running build's host, so the pipe connect times out. In a set of repo builds that all shared one toolset (.dotnet/sdk/11.0.100-rc.1.26420.103), probing produced 147 pipe-connect timeouts and 0 handshake rejections. The probing path already anticipates this:

// A candidate belongs to another build and may exit, be killed, hit its idle timeout,
// or refuse a handshake at any moment, so probing it is inherently racy.

2. The node belongs to a different toolset. Repos that use VS MSBuild for part of their build run two toolsets side by side - C:\Program Files\Microsoft Visual Studio\18\Enterprise\MSBuild\Current\Bin and the bootstrapped SDK - within a single repo build. Because toolsDirectory is part of the handshake salt, those nodes correctly reject each other. In those legs probing produced 60 handshake rejections ("Handshake failed on part 1. Probably the client is a different MSBuild build") against 34 timeouts.

Note this needs no MSBUILDNODEHANDSHAKESALT; the default salt already encodes the tools directory, so the rejections are the handshake working as designed.

Either way the node is not a reuse candidate, and either way it still counts toward the threshold that terminates nodes which are reuse candidates.

The instance already has this information, then discards it

Every probed candidate is recorded in _processesToIgnore (L370), keyed {handshake}|{pid}. ShutdownConnectedNodes clears that dictionary (L117) and calls DetermineNodesForReuse nine lines later (L126).

Suggestions

  • Move the _processesToIgnore clear after the reuse decision and subtract those PIDs from the count. Nearly free, and it covers both cases above, since both end up recorded there.
  • Count only nodes that are genuinely reuse candidates. A busy node belongs to a live build and will be handled by its own owner at completion.
  • Longer term, fold a handshake hash into the pipe name (for example MSBuild.{handshakeHash}.{pid}) so compatible nodes can be counted by enumerating pipe names, with no process enumeration and no connect attempt. That makes case 2 free to detect.

src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs L763-L841 - affects v11.0.0+

Related: #15060

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions