Stage 3: Forward BuildProjectFile* callbacks from OOP TaskHost to worker node#13350
Draft
JanProvaznik wants to merge 13 commits intodotnet:mainfrom
Draft
Stage 3: Forward BuildProjectFile* callbacks from OOP TaskHost to worker node#13350JanProvaznik wants to merge 13 commits intodotnet:mainfrom
JanProvaznik wants to merge 13 commits intodotnet:mainfrom
Conversation
…llbacks Introduce two new IPC packets for forwarding BuildProjectFile* calls from the OOP TaskHost to the worker node: - TaskHostBuildRequest (0x20): Carries all 6-param canonical form args (projectFileNames, targetNames, globalProperties, removeGlobalProperties, toolsVersions, returnTargetOutputs) - TaskHostBuildResponse (0x21): Carries bool success + target outputs using proven TaskParameter/TaskParameterTaskItem serialization Includes TranslateNullableStringArray helper for string arrays with null elements (e.g. toolsVersions), and IDictionary<->Dictionary<string,string> conversion helpers matching TaskHostConfiguration precedent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace all 4 BuildProjectFile* stubs (IBuildEngine 4-param, IBuildEngine2 5-param, IBuildEngine2 7-param, IBuildEngine3 6-param) with real callback implementations that forward to the worker node. All overloads normalize to the canonical 6-param form, send a TaskHostBuildRequest via SendCallbackRequestAndWaitForResponse, and unpack the TaskHostBuildResponse. Gated behind CallbacksSupported with MSB5022 fallback for older worker nodes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add HandleBuildRequest to TaskHostTask that receives TaskHostBuildRequest from the OOP TaskHost, forwards to IBuildEngine3.BuildProjectFilesInParallel, and sends back a TaskHostBuildResponse. Wraps entire handler in try/catch with ExceptionHandling.IsCriticalException filter to always send a failure response on exception, preventing the OOP task thread from blocking forever on TCS. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- BuildProjectFileTask: configurable test task calling BuildEngine.BuildProjectFile with ProjectPath, Targets, GlobalProperties, and BuildSucceeded output - TestNetTaskBuildCallback: E2E .NET task project with ChildProject.proj - Link BuildProjectFileTask into ExampleTask.csproj for E2E use Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Serialization tests (4): - TaskHostBuildRequest round-trip with all fields - TaskHostBuildRequest with null arrays (toolsVersions, globalProperties) - TaskHostBuildResponse success with target outputs + ITaskItem metadata - TaskHostBuildResponse failure with no outputs Integration tests (6): - BuildProjectFile with explicit TaskHostFactory - BuildProjectFile with global properties forwarding - BuildProjectFile with target output extraction - BuildProjectFile child project failure returns false - BuildProjectFile auto-ejected in multithreaded mode - BuildProjectFile MSB5022 fallback when callbacks not supported E2E test (1): - Cross-runtime BuildProjectFile via .NET TaskHost Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Guard against null TargetOutputsPerProject in the result-copy loop. BuildEngineResult defensively converts null to empty list, but the explicit check makes intent clearer and prevents potential NRE if that invariant ever changes. Found via multi-model code review (Gemini). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use properly typed nullable arrays and null-forgiving operators to satisfy warnings-as-errors in full build. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add packet pair (0x26/0x27) to forward IBuildEngine3.Yield() and Reacquire() calls from OOP TaskHost to the owning worker node. TaskHostYieldRequest carries a YieldOperation enum (Yield or Reacquire). TaskHostYieldResponse is a simple acknowledgment for Reacquire (Yield is fire-and-forget). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
OOP TaskHost side (OutOfProcTaskHostNode): - Add _yieldedTaskCount for tracking yielded state during callbacks - YieldForCallback/ReacquireAfterCallback bracket BuildProjectFile calls - Real Yield() sends TaskHostYieldRequest(Yield) — fire-and-forget - Real Reacquire() sends TaskHostYieldRequest(Reacquire) — blocking - HandleTaskHostConfiguration allows new config when task is yielded (_isTaskExecuting && _yieldedTaskCount > 0) Worker side (TaskHostTask): - HandleYieldRequest forwards Yield as fire-and-forget to engine3.Yield() - HandleYieldRequest forwards Reacquire with blocking engine3.Reacquire() then sends TaskHostYieldResponse acknowledgment Note: Same-process TaskHost reuse (child task running on the same OOP process as the yielded parent) requires additional changes to NodeProviderOutOfProcTaskHost connection management. Currently, nested TaskHostFactory tasks spawn separate OOP processes because the parent TaskHostTask holds its connection via a shared TaskHostNodeKey. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add Theory tests for TaskHostYieldRequest round-trip serialization (both Yield and Reacquire operations) and a Fact test for TaskHostYieldResponse round-trip serialization. Use byte parameter with cast to avoid CS0051 (internal YieldOperation enum used as public method parameter). Also remove blank line before closing brace (SA1508) in TaskHostCallback_Tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implement handler stack pattern and concurrent task execution support in the OOP TaskHost, enabling a yielded task to allow the scheduler to reuse the same TaskHost process for nested builds. Key changes: - NodeProviderOutOfProcTaskHost: Replace _nodeIdToPacketHandler with _nodeIdToPacketHandlerStack (Stack<INodePacketHandler>) for push/pop of handlers when tasks yield and new tasks arrive on same process. - OutOfProcTaskHostNode: Replace _isTaskExecuting with _activeTaskCount and _yieldedTaskCount for concurrent task tracking. - TaskExecutionContext: Per-task state (config, environment, pending callbacks) stored in ConcurrentDictionary + AsyncLocal for thread isolation. - EffectiveConfiguration property: Uses per-task context first, falling back to global _currentConfiguration. Fixes NRE when a nested task's CompleteTask clears _currentConfiguration while the yielded parent task still needs it for logging. - YieldForCallback/ReacquireAfterCallback: Save/restore operating environment (current directory, env vars) around callbacks. - E2E test verifying parent and child tasks report same PID when using TaskHostFactory with same Runtime. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes identified by parallel review with Sonnet, Opus, Gemini, GPT: 1. _taskWrapper overwrite: Capture in local variable before ExecuteTask so nested Task B cannot overwrite it. Prevents leaked wrappers and double-cleanup. 2. _taskRunnerThread overwrite: HandleShutdown now joins all active task threads from _taskContexts (not just the last one), and fails their pending callbacks so they can unblock before WaitHandle disposal. 3. _taskCompletePacket single-slot race: Replace with ConcurrentQueue and drain in CompleteTask. Prevents lost completion packets when multiple tasks finish concurrently. 4. Non-atomic active/yielded transition: Reverse Interlocked order in YieldForCallback (increment yielded BEFORE decrementing active) and ReacquireAfterCallback (increment active BEFORE decrementing yielded). Ensures the sum is never zero during transition, preventing CompleteTask from prematurely nulling _currentConfiguration. 5. Shared warning fields overwrite: Save/restore WarningsAsErrors, WarningsNotAsErrors, WarningsAsMessages in TaskExecutionContext alongside environment variables. 6. Yield/Reacquire counter imbalance: RunTask finally block checks TaskExecutionState.Yielded to determine whether _activeTaskCount was already decremented by YieldForCallback, preventing counters from going negative if a task exits after Yield without Reacquire. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- scripts/test-external-repos.ps1: Reusable script to build Roslyn and WPF projects with bootstrap MSBuild. Enables callbacks (MSBUILDENABLETASKHOSTCALLBACKS=1) and -mt flag. Supports -SkipClone, -CleanBuild, -Repos parameters. - Fix CS8600 nullable warning in BuildProjectFileAndReportPidTask.cs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stage 3 of IBuildEngine callback support for out-of-process TaskHost. Implements forwarding of all 4 BuildProjectFile* overloads (IBuildEngine, IBuildEngine2, IBuildEngine3) from OOP TaskHost to the owning worker node, plus Yield/Reacquire forwarding and TaskHost process reuse for nested builds.
Design
Single packet pair for BuildProjectFile: All 4 overloads normalize into IBuildEngine3 6-param canonical form, then serialize into TaskHostBuildRequest (0x20) / TaskHostBuildResponse (0x21) pair.
Yield/Reacquire forwarding: TaskHostYieldRequest (0x26) / TaskHostYieldResponse (0x27) packets forward IBuildEngine3.Yield() (fire-and-forget) and Reacquire() (blocking) from OOP TaskHost to worker node.
Yield-for-callback pattern: BuildProjectFilesInParallel calls YieldForCallback() before sending the request, decrementing _activeTaskCount\ so the scheduler can reuse the same TaskHost process for nested builds (e.g., when a child project needs to run a task with the same runtime/architecture).
Handler stack (NodeProviderOutOfProcTaskHost): Replaced _nodeIdToPacketHandler\ with _nodeIdToPacketHandlerStack\ (Stack). When a task yields and a new task is scheduled on the same process, the new handler is pushed; when it completes, the handler is popped. Packet routing always delegates to _localPacketFactory\ to avoid recursive StackOverflow.
Concurrent task execution (OutOfProcTaskHostNode):
Nested Build Flow (TaskHost Process Reuse)
Tests
Context