documentation/MSBuild-Coordinator.md
Important Note: This document describes the architecture and design of the MSBuild Build Coordinator at a high level. For current implementation details, class structures, method signatures, or specific code patterns, always consult the source code directly. This ensures you're working with accurate, up-to-date information.
The MSBuild Build Coordinator is a resource management system that orchestrates and enforces fair-share allocation of build nodes across multiple simultaneous MSBuild processes. It prevents system resource exhaustion by maintaining a global node budget and dynamically distributing available nodes among competing builds.
The coordinator runs as a separate process (MSBuild.Coordinator), not inside MSBuild.exe. MSBuild clients connect to it over named pipes, request grants, and continue building with the granted node count.
When multiple MSBuild processes run concurrently (common in user multi-tasking), each process could independently attempt to spawn the maximum number of nodes, leading to:
The coordinator solves this by:
Note: The current default budget is intentionally conservative for V1. As we gather real-world usage data, we should experiment with alternative defaults (including moderate oversubscription above 1x processor count) and tune this value for better throughput without destabilizing interactive machine workloads.
┌──────────────────────────────────────────────────────────────────┐
│ System with Multiple Builds │
└──────────────────────────────────────────────────────────────────┘
Build 1 Build 2 Build 3
│ │ │
│ RequestNodes(4) │ RequestNodes(4) │ RequestNodes(4)
│ │ │
│ ◄── NodeGrant(4) │ ◄── NodeGrant(4) │ ◄── Wait(queued)
│ │ │
└───────────────────────┼───────────────────────┘
(via Named Pipes - IPC)
↓
┌────────────────────────────────────┐
│ MSBuild Build Coordinator │
│ │
│ ┌──────────────────────────────┐ │
│ │ Node Budget Manager │ │
│ │ • Total Budget: 8 nodes │ │
│ │ • Allocated: 8 │ │
│ │ • Available: 0 │ │
│ └──────────────────────────────┘ │
│ │
│ ┌──────────────────────────────┐ │
│ │ Active Builds │ │
│ │ • Build 1: 4 nodes │ │
│ │ • Build 2: 4 nodes │ │
│ └──────────────────────────────┘ │
│ │
│ ┌──────────────────────────────┐ │
│ │ Waiting Builds Queue │ │
│ │ • Build 3: waiting │ │
│ └──────────────────────────────┘ │
└────────────────────────────────────┘
Later, when one 4-node build releases:
Build 3 ◄── NodeGrant(4)
Coordinator Server (src/MSBuild.Coordinator/)
CoordinatorServer.cs - Main coordinator server that listens for client connections via named pipeNodeBudgetManager.cs - Implements node allocation and fair-share logicClientConnection.cs - Manages individual client connectionsBuildGrant.cs - Represents a node allocation to a buildProgram.cs - Server launcher and singleton instance managementClient-Side (src/Build/BackEnd/BuildManager/)
CoordinatorClient.cs - Client connection handler integrated into BuildManagerBuildManager.cs - Requests nodes from coordinator and sets build parallelismProtocol (src/Framework/Coordinator/)
ClientHandshakeMessage, ServerHandshakeMessageRequestNodesMessage, HeartbeatMessage, ReleaseNodesMessageNodeGrantMessage, WaitMessage, ErrorMessageCapabilities.cs - Capability constants for feature negotiationCoordinatorSettings.cs - Configuration managementEvery connection begins with a capabilities handshake:
ClientHandshakeMessage (ConnectionId, ProcessId, capabilities)ServerHandshakeMessage (capabilities)Both sides advertise the features they support; unknown capabilities are ignored, allowing older clients to work with newer servers.
The coordinator does not use a protocol version number. Instead, it uses a capabilities-based versioning model:
This design avoids the "version bump" problem where a single version number forces all-or-nothing upgrades. New features can be added incrementally — a newer coordinator can offer capabilities that older clients simply don't use, and vice versa. Both sides degrade gracefully when a capability is absent.
After the handshake, the coordinator uses a binary protocol with six message types:
Client → Server:
RequestNodesMessage - Requests a node grant (contains requested node count)HeartbeatMessage - Periodic keep-alive message (default: every 5 seconds)ReleaseNodesMessage - Sent when build completes, releases allocated nodesServer → Client:
NodeGrantMessage - Grants nodes to a buildWaitMessage - Indicates build is queued, no nodes immediately availableErrorMessage - Indicates an error conditionSource: src/Framework/Coordinator/
Successful Grant:
Build → ClientHandshakeMessage(ConnectionId, PID, capabilities)
Build ← ServerHandshakeMessage(capabilities)
Build → RequestNodesMessage(4)
Build ← NodeGrantMessage(4)
Build → Heartbeat (every 5s)
Build → ReleaseNodesMessage (on completion)
Build Queued:
Build → ClientHandshakeMessage(ConnectionId, PID, capabilities)
Build ← ServerHandshakeMessage(capabilities)
Build → RequestNodesMessage(4)
Build ← WaitMessage
Build → Heartbeat (every 5s while waiting)
Eventually: Build ← NodeGrantMessage(N) [N is fair-share computed from available nodes and contenders, capped by requested nodes (N <= 4 here)]
When multiple builds compete for limited nodes, the coordinator computes a fair share per grant. The contender count depends on the phase:
Initial request path (TryGrant):
fair_share = max(1, available_nodes / (waiting_builds + 1))
granted_nodes = min(fair_share, requested_nodes)
Wait-queue drain path (DrainWaitQueue):
fair_share = max(1, available_nodes / waiting_builds)
granted_nodes = min(fair_share, requested_nodes)
This ensures:
First Build Requests Full Budget (8 total nodes)
Three Full-Budget Requests Launched Together (8 total nodes)
MaxNodeCount is not specified: each build requests Environment.ProcessorCount (the full default budget)WaitMessage and enter the wait queueQueued Mixed-Demand Scenario (8 total nodes)
During build initialization:
MSBUILDUSECOORDINATOR environment variable is setCoordinatorClient attempts to connect to the coordinatorRequestNodesMessage with desired node count (the value of /maxcpucount passed to MSBuild — defaults to 1 if omitted, or the logical processor count if /m is passed without a value)NodeGrantMessage (nodes granted) or WaitMessage (queued)
WaitMessage is received, CoordinatorClient starts sending periodic heartbeats while waiting for the deferred NodeGrantMessage, so the coordinator doesn't consider it stale during the queue wait.V1 Behavior: The number of nodes granted to a build is fixed at initialization and does not change during the build's lifetime. The grant persists as long as the build is running (indicated by heartbeats) and is released only when the build completes.
During build execution:
CoordinatorClient continues sending periodic heartbeats to indicate the build is still activeOn build completion:
ReleaseNodesMessage to free nodes for other waiting buildsKey Principle: The coordinator is entirely optional. If it's unavailable or disabled, the build uses its requested node count without coordination.
Sources:
| Variable | Default | Purpose |
|---|---|---|
MSBUILDUSECOORDINATOR | (empty) | Enable coordinator (set to any value to enable) |
MSBUILDCOORDINATORPIPENAME | msbuild-coordinator-{UserName} | Override default pipe name |
MSBUILDCOORDINATORNODEBUDGET | Processor count | Override total node budget |
MSBUILDCOORDINATORHEARTBEAT | 5000 | Override heartbeat interval (ms) |
MSBUILDCOORDINATORSHUTDOWNTIMEOUT | 60000 | Override shutdown timeout (ms) |
Note: MSBUILDCOORDINATORNODEBUDGET is the primary knob for throughput experiments, including testing moderate oversubscription factors above 1x processor count.
Source: src/MSBuild.Coordinator/Program.cs
The coordinator detects stalled or crashed clients through periodic heartbeats:
Source: src/MSBuild.Coordinator/CoordinatorServer.cs
When a build completes normally:
ReleaseNodesMessage with its grant IDSource: src/MSBuild.Coordinator/CoordinatorServer.cs
The coordinator system is designed to be fully optional:
This means coordinator failures never block or degrade build execution—they only disable coordination.
Sources:
Comprehensive test coverage in src/MSBuild.Coordinator.UnitTests/:
For detailed implementation information, refer to: