[IBuildEngine callbacks] Stage 2: RequestCores/ReleaseCores#13306
[IBuildEngine callbacks] Stage 2: RequestCores/ReleaseCores#13306JanProvaznik wants to merge 5 commits intodotnet:mainfrom
Conversation
New IPC packet pair for forwarding IBuildEngine9.RequestCores() and ReleaseCores() from TaskHost to the owning worker node. TaskHostCoresRequest (0x22) uses a single packet type with an IsRelease bool to distinguish RequestCores from ReleaseCores — both operations share the same int parameter semantics (core count) and response shape (granted cores or acknowledgment). TaskHostCoresResponse (0x23) returns GrantedCores for RequestCores calls and 0 as acknowledgment for ReleaseCores calls. Both implement ITaskHostCallbackPacket for request/response correlation via RequestId, following the Stage 1 pattern (TaskHostIsRunningMultipleNodes*). The NodePacketType enum values 0x22/0x23 were already reserved; this commit updates their doc comments to use 'owning worker node' terminology. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace 'throw NotImplementedException()' stubs with callback-based forwarding using the Stage 1 SendCallbackRequestAndWaitForResponse<T> infrastructure. Both methods: - Validate input (requestedCores/coresToRelease > 0) before IPC roundtrip, matching the in-process TaskHost's fail-fast behavior - Check CallbacksSupported gate — when disabled, RequestCores returns 0 and ReleaseCores is no-op, both logging MSB5022 - Send TaskHostCoresRequest and block on TaskHostCoresResponse via TCS Register TaskHostCoresResponse handler in the constructor and add the response routing case to HandlePacket's switch statement. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Register TaskHostCoresRequest handler and add HandleCoresRequest() which forwards calls to the in-process TaskHost's IBuildEngine9 implementation. The OOP TaskHostNode is a thin proxy — the real implicit-core accounting and scheduler communication happens in the in-process TaskHost class on the worker node side. HandleCoresRequest dispatches based on IsRelease: - RequestCores: forwards to engine9.RequestCores(), returns GrantedCores - ReleaseCores: forwards to engine9.ReleaseCores(), returns 0 (ack) When _buildEngine doesn't implement IBuildEngine9 (shouldn't happen in practice), RequestCores returns 0 and ReleaseCores is no-op — matching the HandleIsRunningMultipleNodesRequest pattern for IBuildEngine2. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
RequestCoresTask is a test task that calls IBuildEngine9.RequestCores(N) and optionally ReleaseCores. It exposes GrantedCores as an output property for assertion. Linked into ExampleTask.csproj for E2E cross-runtime use. TestNetTaskResourceCallback/ is the E2E test project that runs RequestCoresTask via TaskHostFactory with Runtime='NET', mirroring the TestNetTaskCallback/ project from Stage 1. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Packet serialization (4 tests): - TaskHostCoresRequest round-trip for RequestCores and ReleaseCores - TaskHostCoresResponse round-trip for granted cores and acknowledgment Integration (5 tests): - RequestCores via explicit TaskHostFactory — verifies granted >= 1 - RequestCores + ReleaseCores full cycle — verifies no errors - RequestCores auto-ejected in MT mode — verifies TaskHost forwarding - RequestCores fallback when callbacks disabled — verifies MSB5022 - IsRunningMultipleNodes fallback (Stage 1, unchanged) E2E cross-runtime (1 test): - NetTaskHost_CallbackRequestCoresTest via bootstrapped MSBuild Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
4601b9a to
086f16b
Compare
- Mark RequestCores/ReleaseCores as implemented in status table - Rename TaskHostResourceRequest/Response to TaskHostCoresRequest/Response - Update phased rollout table with PR dotnet#13306 - Replace Stage 2 design considerations with implementation summary Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
086f16b to
89594d2
Compare
|
@AR-May @rainersigwald this one should be simple, it is conceptually the same as the previous PR, the juicy one will be the next one. |
There was a problem hiding this comment.
Pull request overview
Adds Stage 2 of TaskHost IBuildEngine callback forwarding by implementing IBuildEngine9.RequestCores / ReleaseCores over the existing callback packet infrastructure, enabling resource-aware tasks to run out-of-proc without NotImplementedException.
Changes:
- Introduces
TaskHostCoresRequest/TaskHostCoresResponsepackets and newNodePacketTypeentries. - Implements
RequestCores/ReleaseCoresforwarding inOutOfProcTaskHostNode(TaskHost side) and handles requests inTaskHostTask(worker node side). - Adds/updates unit + integration + E2E tests and a new ExampleNetTask asset for the callback.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Shared/TaskHostCoresRequest.cs | New callback request packet for RequestCores/ReleaseCores. |
| src/Shared/TaskHostCoresResponse.cs | New callback response packet returning granted cores / ack. |
| src/Shared/INodePacket.cs | Renames/defines packet types for cores request/response (0x22/0x23). |
| src/MSBuild/OutOfProcTaskHostNode.cs | Implements RequestCores/ReleaseCores via callback request/response + handler registration. |
| src/MSBuild/MSBuild.csproj | Includes new shared packet source files. |
| src/Build/Microsoft.Build.csproj | Includes new shared packet source files. |
| src/Build/Instance/TaskFactories/TaskHostTask.cs | Registers/handles cores request packet and forwards to in-proc IBuildEngine9. |
| src/Build.UnitTests/BackEnd/TaskHostCallbackPacket_Tests.cs | Adds round-trip serialization tests for new packets. |
| src/Build.UnitTests/BackEnd/TaskHostCallback_Tests.cs | Adds integration tests for RequestCores/ReleaseCores through TaskHost. |
| src/Build.UnitTests/BackEnd/RequestCoresTask.cs | Adds a test task that exercises RequestCores/ReleaseCores. |
| src/Build.UnitTests/NetTaskHost_E2E_Tests.cs | Adds E2E test to validate RequestCores callback across runtimes. |
| src/Build.UnitTests/TestAssets/ExampleNetTask/ExampleTask/ExampleTask.csproj | Links the new RequestCores test task into ExampleTask. |
| src/Build.UnitTests/TestAssets/ExampleNetTask/TestNetTaskResourceCallback/TestNetTaskResourceCallback.csproj | New test asset project invoking the RequestCores task via TaskHostFactory. |
| src/Build.UnitTests/TestAssets/ExampleNetTask/TestNetTaskResourceCallback/global.json | New global.json for the new test asset. |
| new BuildRequestData(projectInstance, targetsToBuild: ["Test"])); | ||
|
|
||
| // MSB5022 error should be logged — the callback was not forwarded | ||
| logger.ErrorCount.ShouldBeGreaterThan(0); | ||
| logger.FullLog.ShouldContain("MSB5022"); |
There was a problem hiding this comment.
This test checks that MSB5022 is logged when callbacks are disabled, but it doesn’t assert the expected functional fallback (RequestCores returning 0) or the overall build result (a logged error should typically fail the build). Adding assertions for the returned GrantedCores/Result value and the BuildResult outcome would cover the new behavior more completely.
| @@ -0,0 +1,2 @@ | |||
| { | |||
There was a problem hiding this comment.
global.json is currently an empty object. Other ExampleNetTask bootstrap-based test assets include an sdk section (allowPrerelease, rollForward) plus a comment explaining why it exists, to prevent walking up the repo tree and picking up the repo-root SDK settings. Consider matching that established pattern here to avoid non-deterministic SDK selection when running the E2E test from the bootstrap layout.
| { | |
| { | |
| // This global.json exists to prevent walking up the repo tree and | |
| // inheriting the repo-root SDK settings when running the E2E test | |
| // from the bootstrap layout. Use the latest installed SDK instead. | |
| "sdk": { | |
| "allowPrerelease": true, | |
| "rollForward": "latestMajor" | |
| } |
| <Compile Include="..\Shared\TaskHostCoresRequest.cs" /> | ||
| <Compile Include="..\Shared\TaskHostCoresResponse.cs" /> |
There was a problem hiding this comment.
It's making me really sad to see us pile up new shared code. Not blocking but I'd appreciate putting some thought into that.
| // Point dotnet resolution to the bootstrap layout so the .NET Core TaskHost | ||
| // uses the locally-built MSBuild.dll (with callback support) instead of the system SDK. | ||
| string bootstrapCorePath = Path.Combine(RunnerUtilities.BootstrapRootPath, "core"); | ||
| string bootstrapDotnet = Path.Combine(bootstrapCorePath, "dotnet.exe"); | ||
| env.SetEnvironmentVariable("DOTNET_HOST_PATH", bootstrapDotnet); | ||
| env.SetEnvironmentVariable("DOTNET_ROOT", bootstrapCorePath); | ||
| env.SetEnvironmentVariable("DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR", bootstrapCorePath); |
There was a problem hiding this comment.
@YuliiaKovalova I feel like you had a less manual mechanism to do this, am I misremembering?
There was a problem hiding this comment.
After apphost introduction it will be simplified
https://github.com/dotnet/msbuild/blob/0dae95d7a68c558089c4364c820bbabf64005ad1/src/Build.UnitTests/NetTaskHost_E2E_Tests.cs
Context
Stage 2 of implementing IBuildEngine callback support in TaskHost. Stage 1 (#13149) established the packet infrastructure and implemented
IsRunningMultipleNodes. This PR addsRequestCores/ReleaseCores(IBuildEngine9) using the same pattern.Previously, tasks running in TaskHost that called
RequestCoresorReleaseCoreswould get aNotImplementedException. This blocked resource-aware tasks from properly coordinating CPU usage when running out-of-process.Partial fix for #12863
Changes Made
New Packets:
TaskHostCoresRequest(0x22) — single packet type for both operations, withIsReleasebool to distinguishRequestCoresvsReleaseCoresTaskHostCoresResponse(0x23) — returnsGrantedCores(int) for RequestCores, 0 for ReleaseCores acknowledgmentOutOfProcTaskHostNode (TaskHost side):
throw NotImplementedException()withSendCallbackRequestAndWaitForResponse<TaskHostCoresResponse>()requestedCores > 0,coresToRelease > 0) matching in-process behaviorCallbacksSupported— when disabled,RequestCoresreturns 0,ReleaseCoresis no-op (both log MSB5022)TaskHostCoresResponsein packet handler and route inHandlePacketswitchTaskHostTask (Worker node side):
TaskHostCoresRequestpacket handlerHandleCoresRequest(): forwardsRequestCores/ReleaseCoresto the in-processIBuildEngine9TaskHostclass — the OOP node is a thin proxyCallback Flow
sequenceDiagram participant Task as Task Thread<br/>(TaskHost) participant Main as Main Thread<br/>(TaskHost) participant Worker as TaskHostTask<br/>(Worker Node) Task->>Task: BuildEngine.RequestCores(N) Task->>Task: Create TaskHostCoresRequest(N, isRelease=false) Task->>Task: Store TCS in _pendingCallbackRequests[requestId] Task->>Worker: SendData(request) Task-->>Task: Block on TCS.Task Worker->>Worker: HandleCoresRequest(request) Worker->>Worker: granted = _buildEngine.RequestCores(N) Worker->>Main: SendData(TaskHostCoresResponse(granted)) Main->>Main: HandleCallbackResponse(response) Main->>Main: _pendingCallbackRequests[requestId].SetResult(response) Task-->>Task: TCS unblocks Task->>Task: return response.GrantedCoresTesting
RequestCores_WorksWithExplicitTaskHostFactory— verifies cores granted >= 1RequestAndReleaseCores_WorksWithExplicitTaskHostFactory— full request+release cycleRequestCores_WorksWhenAutoEjectedInMultiThreadedMode— MT auto-ejection to TaskHostRequestCores_LogsErrorWhenCallbacksNotSupported— MSB5022 fallbackIsRunningMultipleNodes_LogsErrorWhenCallbacksNotSupported— Stage 1 fallback (unchanged)NetTaskHost_CallbackRequestCoresTestvia bootstrapped MSBuildDocumentation
Updated
taskhost-threading.mdwith:Next Stages