Skip to content

Add App Host Support for MSBuild#13175

Merged
YuliiaKovalova merged 60 commits intomainfrom
dev/ykovalova/app_host_support
Mar 3, 2026
Merged

Add App Host Support for MSBuild#13175
YuliiaKovalova merged 60 commits intomainfrom
dev/ykovalova/app_host_support

Conversation

@YuliiaKovalova
Copy link
Copy Markdown
Member

@YuliiaKovalova YuliiaKovalova commented Feb 2, 2026

Part 1 of: #12995

Context

This PR implements App Host support for MSBuild, enabling MSBuild to run as a native executable (MSBuild.exe or MSBuild on Unix) instead of dotnet + MSBuild.dll

Changes Made

Added DotnetHostEnvironmentHelper class to manage DOTNET_ROOT environment variables

When MSBuild runs as an app host, child processes (worker nodes and task hosts) need DOTNET_ROOT set to locate the .NET runtime
These variables are cleared after startup so they don't leak to tools executed by child processes
Only clears DOTNET_ROOT variants if they weren't present in the original build process environment

Node Launcher Updates

Updated NodeLauncher to pass environment overrides when launching worker nodes
NodeLaunchData record struct with EnvironmentOverrides support
Implemented BuildEnvironmentBlock for Windows process creation with custom environment
NodeProviderOutOfProc and NodeProviderOutOfProcTaskHost now provide DOTNET_ROOT overrides

Notes

Backward compatible: Falls back to dotnet MSBuild.dll if app host doesn't exist (older SDKs)
Uses ProcessStartInfo.Environment for thread-safe, scoped environment overrides
The SDK can continue using dotnet MSBuild.dll or opt to use the new app host—this is not a forced change

@YuliiaKovalova YuliiaKovalova self-assigned this Feb 2, 2026
@YuliiaKovalova
Copy link
Copy Markdown
Member Author

@copilot could you please move all $(LatestDotNetCoreForMSBuild) related changes to a separate PR?

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 3, 2026

@YuliiaKovalova I've opened a new pull request, #13189, to work on those changes. Once the pull request is ready, I'll request review from you.

@YuliiaKovalova YuliiaKovalova force-pushed the dev/ykovalova/app_host_support branch from 90e06c7 to 95f4da2 Compare February 3, 2026 16:46
rainersigwald added a commit that referenced this pull request Mar 11, 2026
There was a collision between #13220 (at end of build, look for other
running MSBuild processes and decide whether node reuse is likely to be
worthwhile) and #13175 (use an apphost on core). This broke the
overprovisioning detection, which was looking for `dotnet` instead
of the new `MSBuild`.

Always search for `MSBuild` instead, and remove the now-unnecesary
"try to filter `dotnet.exe` processes to those running `MSBuild.dll`
code too.
jonathanpeppers added a commit to dotnet/android that referenced this pull request Mar 16, 2026
MSBuild 18+ (dotnet/msbuild#13175) added app host support which
changes how TaskHostFactory with Runtime="NET" resolves the MSBuild
executable. When running from a local/xcopy SDK (bin/Release/dotnet),
the task host resolution falls back to the global SDK path because
DOTNET_HOST_PATH is not set, causing MSB4216.

Set DOTNET_HOST_PATH to  so the .NET 11 MSBuild
task host can find the correct dotnet host.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jonathanpeppers added a commit to dotnet/android that referenced this pull request Mar 16, 2026
MSBuild 18+ (dotnet/msbuild#13175) added app host support that changed
how TaskHostFactory with Runtime="NET" spawns task host processes. The
MetadataLoadContext used to inspect task assemblies is disposed before
output validation completes, causing MSB4027/MSB4216 on macOS and
Linux when running from a local/xcopy SDK.

Remove TaskHostFactory and Runtime="NET" from the git prep task
UsingTask declarations in XAVersionInfo.targets. These tasks only
shell out to git and don't need out-of-proc isolation.

Also revert the DOTNET_HOST_PATH workaround attempts in DotNet.targets
and msbuild.mk which were insufficient (the issue is MetadataLoadContext
lifecycle, not host path resolution).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jonathanpeppers added a commit to dotnet/android that referenced this pull request Mar 16, 2026
The new MSBuild app host (dotnet/msbuild#13175) requires DOTNET_HOST_PATH
to bootstrap the .NET runtime when spawning out-of-proc task hosts. Without
this, tasks using TaskHostFactory (like ILLink's ComputeManagedAssemblies)
fail with MSB4221 when using a locally-installed SDK.

Set DOTNET_HOST_PATH in:
- msbuild.mk: for all Makefile-driven builds
- DotNetCLI.cs: for test-driven dotnet invocations

[build] Remove unnecessary DOTNET_HOST_PATH from msbuild.mk

The git prep tasks no longer use TaskHostFactory, so the Makefile
doesn't need DOTNET_HOST_PATH. Only the test infrastructure needs it
for ILLink's TaskHostFactory.

[tests] Add comment explaining DOTNET_HOST_PATH workaround

Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
jonathanpeppers added a commit to dotnet/android that referenced this pull request Mar 17, 2026
Changes: dotnet/dotnet@ee56b9a...5ff448a

- **Dependency Updates**:
  - From [11.0.0-preview.3.26157.103 to 11.0.0-preview.3.26165.107][1]
     - Microsoft.NET.Workload.Mono.ToolChain.Current.Manifest-11.0.100-preview.3
     - Microsoft.NET.ILLink
     - Microsoft.NETCore.App.Ref
  - From [11.0.0-beta.26157.103 to 11.0.0-beta.26165.107][1]
     - Microsoft.DotNet.Build.Tasks.Feed
  - From [0.11.5-preview.26157.103 to 0.11.5-preview.26165.107][1]
     - Microsoft.DotNet.Cecil
  - From [11.0.100-preview.3.26157.103 to 11.0.100-preview.3.26165.107][1]
     - Microsoft.NET.Sdk
     - Microsoft.NET.Workload.Emscripten.Current.Manifest-11.0.100-preview.3
     - Microsoft.TemplateEngine.Authoring.Tasks

[1]: dotnet/dotnet@ee56b9a...5ff448a

* [build] Remove TaskHostFactory from XAVersionInfo.targets

MSBuild 18+ (dotnet/msbuild#13175) added app host support that changed
how TaskHostFactory with Runtime="NET" spawns task host processes. The
MetadataLoadContext used to inspect task assemblies is disposed before
output validation completes, causing MSB4027/MSB4216 on macOS and
Linux when running from a local/xcopy SDK.

Remove TaskHostFactory and Runtime="NET" from the git prep task
UsingTask declarations in XAVersionInfo.targets. These tasks only
shell out to git and don't need out-of-proc isolation.

* [build] Set DOTNET_HOST_PATH for MSBuild TaskHostFactory app host

The new MSBuild app host (dotnet/msbuild#13175) requires DOTNET_HOST_PATH
to bootstrap the .NET runtime when spawning out-of-proc task hosts. Without
this, tasks using TaskHostFactory (like ILLink's ComputeManagedAssemblies)
fail with MSB4221 when using a locally-installed SDK.

Set DOTNET_HOST_PATH in:
- DotNetCLI.cs: for test-driven dotnet invocations

Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
jonathanpeppers added a commit to dotnet/android that referenced this pull request Mar 17, 2026
The new MSBuild app host (dotnet/msbuild#13175) requires DOTNET_HOST_PATH
to bootstrap the .NET runtime when spawning out-of-proc task hosts. Without
this, tasks using TaskHostFactory (like ILLink's ComputeManagedAssemblies)
fail with MSB4221 when using a locally-installed SDK.

Backport of the fix from main (287f27d).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
YuliiaKovalova added a commit to dotnet/sdk that referenced this pull request Mar 18, 2026
MSBuild's apphost support (dotnet/msbuild#13175) requires DOTNET_HOST_PATH
to resolve DOTNET_ROOT when launching out-of-proc task hosts for
TaskHostFactory tasks (e.g. ComputeWasmBuildAssets, ComputeManagedAssemblies).

The MSBuild apphost is now created in the SDK layout (dotnet/dotnet#5183),
so ResolveAppHostOrFallback takes the apphost code path. It calls
CreateDotnetRootEnvironmentOverrides which derives DOTNET_ROOT from
DOTNET_HOST_PATH. While the dotnet CLI sets this internally, explicitly
providing it in the test environment ensures it is available for the
task host subprocess launch.

Also enable MSBUILDDEBUGCOMM tracing to capture handshake details for
diagnosing any remaining task host connection issues.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
YuliiaKovalova added a commit to dotnet/sdk that referenced this pull request Mar 18, 2026
MSBuild's apphost support (dotnet/msbuild#13175) requires DOTNET_HOST_PATH
to resolve DOTNET_ROOT when launching out-of-proc task hosts for
TaskHostFactory tasks (e.g. ComputeWasmBuildAssets, ComputeManagedAssemblies).

The MSBuild apphost is now created in the SDK layout (dotnet/dotnet#5183),
so ResolveAppHostOrFallback takes the apphost code path. It calls
CreateDotnetRootEnvironmentOverrides which derives DOTNET_ROOT from
DOTNET_HOST_PATH. While the dotnet CLI sets this internally, explicitly
providing it in the test environment ensures it is available for the
task host subprocess launch.

Diagnostics added:
- DOTNET_HOST_PATH set in test environment variables
- MSBUILDDEBUGCOMM=1 for node communication tracing
- MSBuildDebugEngine=1 with MSBUILDDEBUGPATH for trace file output
- SDK layout dump in AoT_Publish test to verify MSBuild apphost presence

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
YuliiaKovalova added a commit to dotnet/sdk that referenced this pull request Mar 18, 2026
MSBuild's apphost support (dotnet/msbuild#13175) requires DOTNET_HOST_PATH
to resolve DOTNET_ROOT when launching out-of-proc task hosts for
TaskHostFactory tasks (e.g. ComputeWasmBuildAssets, ComputeManagedAssemblies).

Changes:
- Set DOTNET_HOST_PATH in test environment variables
- Enable MSBUILDDEBUGCOMM=1 and MSBuildDebugEngine=1 for trace file output
- Add SDK layout diagnostics to AoT_Publish test:
  * List MSBuild* files in SDK folder
  * Check apphost exists and has execute permissions (ls -la)
  * Attempt to run the apphost with --help to verify it can launch
  * Dump MSBuild.runtimeconfig.json content
  * Log DOTNET_HOST_PATH, DOTNET_ROOT, OS, architecture info
  * Verify dotnet host exists

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
YuliiaKovalova added a commit to dotnet/sdk that referenced this pull request Mar 18, 2026
MSBuild's apphost support (dotnet/msbuild#13175) requires DOTNET_HOST_PATH
to resolve DOTNET_ROOT when launching out-of-proc task hosts for
TaskHostFactory tasks (e.g. ComputeWasmBuildAssets, ComputeManagedAssemblies).

Changes:
- Set DOTNET_HOST_PATH in test environment variables
- Enable MSBUILDDEBUGCOMM=1 and MSBuildDebugEngine=1 for trace file output
- Add SDK layout diagnostics to AoT_Publish test:
  * List MSBuild* files in SDK folder
  * Check apphost exists and has execute permissions (ls -la)
  * Attempt to run the apphost with --help to verify it can launch
  * Dump MSBuild.runtimeconfig.json content
  * Log DOTNET_HOST_PATH, DOTNET_ROOT, OS, architecture info
  * Verify dotnet host exists

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
YuliiaKovalova added a commit that referenced this pull request Mar 18, 2026
On macOS, /tmp is a symlink to /private/tmp. The parent process constructs
the handshake toolsDirectory from the MSBuild property \
which preserves the unresolved /tmp form. The child task host process
computes its toolsDirectory from AppContext.BaseDirectory which the .NET
runtime resolves to /private/tmp. The different strings produce different
hashes in the handshake salt, resulting in mismatched pipe names and the
parent being unable to connect to the child task host (MSB4216).

This affects all TaskHostFactory tasks (ComputeWasmBuildAssets,
ComputeManagedAssemblies, ILLink, etc.) on macOS when the SDK is
installed under a symlinked path.

The fix calls Path.GetFullPath() on the msbuildAssemblyPath before using
it in the handshake and for path construction, ensuring the parent
resolves symlinks the same way the child does.

Regression introduced in #13175 (Add App Host Support for MSBuild) which
changed the .NET task host handshake from using no explicit toolsDirectory
(both sides defaulted to MSBuildToolsDirectoryRoot) to passing an explicit
toolsDirectory from the MSBuild property value.

Fixes dotnet/sdk#53350 (comment)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
YuliiaKovalova added a commit that referenced this pull request Mar 18, 2026
…B4216

On macOS, /tmp is a symlink to /private/tmp. PR #13175 changed
ResolveAppHostOrFallback to pass an explicit toolsDirectory to the
parent's Handshake constructor, sourced from the MSBuild property
(NetCoreSdkRoot = MSBuildThisFileDirectory). The child task host
process computes its toolsDirectory from AppContext.BaseDirectory
via MSBuildToolsDirectoryRoot.

On macOS, MSBuild properties preserve the unresolved /tmp form while
AppContext.BaseDirectory resolves to /private/tmp. The different
strings produce different hashes in the handshake salt, resulting in
mismatched pipe names — the parent listens on one pipe, the child
connects to another — causing MSB4216.

Before #13175, both parent and child used MSBuildToolsDirectoryRoot
as the default (no explicit toolsDirectory), so both resolved
symlinks identically and the handshake matched.

The fix uses BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot
for the handshake toolsDirectory on the parent side — the same source
the child uses — ensuring path strings match regardless of symlinks.
File I/O operations continue to use the original msbuildAssemblyPath
(both path forms work for filesystem access on macOS).

Fixes dotnet/sdk#53350 (comment)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jonathanpeppers added a commit to dotnet/android that referenced this pull request Mar 18, 2026
Changes: dotnet/dotnet@1e7efac...695853e

* Update dependencies from https://github.com/dotnet/dotnet build 20260315.6
On relative base path root
Microsoft.DotNet.Build.Tasks.Feed From Version 10.0.0-beta.26156.117 -> To Version 10.0.0-beta.26165.106
Microsoft.NET.Sdk From Version 10.0.300-preview.26156.117 -> To Version 10.0.300-preview.0.26165.106
Microsoft.TemplateEngine.Authoring.Tasks From Version 10.0.300-preview.26156.117 -> To Version 10.0.300-preview.26165.106

* Remove dead DARC NuGet feeds

These feeds were added manually for older servicing releases (runtime
9.0.5 and 8.0.16) and have since been cleaned up from Azure DevOps.
They cause NU1900 restore failures (warnings-as-errors).

* Switch MAUI pipeline ref from net10.0 to main

MAUI's net10.0 branch has a dead DARC feed (darc-pub-dotnet-android-1719a35b)
that causes NU1301 restore failures. MAUI main targets .NET 10 and does not
have the stale feed.

* [build] Ignore `RS0016` public API errors in MAUI integration lane (#10884)

Set `PublicApiType=Generate` on the `maui_tests_integration` job to skip the
`Microsoft.CodeAnalysis.PublicApiAnalyzers` package, which produces `RS0016`
errors for new/changed MAUI APIs that dotnet/android does not need to validate.

* Set DOTNET_HOST_PATH for MSBuild TaskHostFactory app host

The new MSBuild app host (dotnet/msbuild#13175) requires DOTNET_HOST_PATH
to bootstrap the .NET runtime when spawning out-of-proc task hosts. Without
this, tasks using TaskHostFactory (like ILLink's ComputeManagedAssemblies)
fail with MSB4221 when using a locally-installed SDK.

Backport of the fix from main (287f27d).

Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
YuliiaKovalova added a commit to dotnet/sdk that referenced this pull request Mar 18, 2026
The Helix test environment sets DOTNET_ROOT but not DOTNET_HOST_PATH.
MSBuild's apphost support (dotnet/msbuild#13175) needs DOTNET_HOST_PATH
to derive DOTNET_ROOT when launching TaskHostFactory tasks as out-of-proc
MSBuild apphost child processes.

Without DOTNET_HOST_PATH, AddNetHostParamsIfNeeded in AssemblyTaskFactory
cannot populate DotnetHostPath/MSBuildAssemblyPath, and the task host
launch fails with MSB4216.

Confirmed via diagnostic run on Helix macOS: DOTNET_HOST_PATH was empty
in the test runner process environment while DOTNET_ROOT was set.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
YuliiaKovalova added a commit that referenced this pull request Mar 19, 2026
Remove explicit toolsDirectory from Handshake in ResolveAppHostOrFallback.

Root cause: On macOS, /tmp is a symlink to /private/tmp. The parent
process passed toolsDirectory from $(NetCoreSdkRoot) MSBuild property
(which preserves the unresolved symlink path /tmp/...), while the
child task host computed its toolsDirectory from
BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot (derived from
AppContext.BaseDirectory, which resolves symlinks to /private/tmp/...).
This produced different handshake hashes, causing the parent to fail to
connect to the child task host process with MSB4216.

Fix: Don't pass explicit toolsDirectory to the Handshake constructor.
Both parent and child now default to BuildEnvironmentHelper, which
consistently resolves symlinks via AppContext.BaseDirectory.

Before PR #13175, neither side passed explicit toolsDirectory, so both
defaulted to BuildEnvironmentHelper and always matched. PR #13175
introduced the asymmetry by passing  on the parent side.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
YuliiaKovalova added a commit that referenced this pull request Mar 19, 2026
)

### Context

PR #13175 (App Host Support) introduced a regression on macOS when the
SDK is accessed through a symlinked path. On macOS, `/tmp` is a symlink
to `/private/tmp`. When Helix tests run from `/tmp/helix/...`, the
`$(NetCoreSdkRoot)` MSBuild property preserves the unresolved path
`/tmp/helix/.../sdk/11.0.100-ci`, but the child task host process
resolves it to `/private/tmp/helix/.../sdk/11.0.100-ci` via
`AppContext.BaseDirectory`.

### Root Cause

In `ResolveAppHostOrFallback`, the parent passed `toolsDirectory:
msbuildAssemblyPath` (from `$(NetCoreSdkRoot)`) to the `Handshake`
constructor, while the child (`NodeEndpointOutOfProcTaskHost`) passed no
explicit `toolsDirectory`, defaulting to
`BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot` (which
resolves symlinks).

This produced different handshake hashes:
- **Parent**: `hash("/tmp/.../sdk/11.0.100-ci")`
- **Child**: `hash("/private/tmp/.../sdk/11.0.100-ci")`
- **Result**: Handshake mismatch -> MSB4216

Before PR #13175, neither side passed explicit `toolsDirectory`, so both
defaulted to `BuildEnvironmentHelper` and always matched.

### Changes Made

- On .NET Core (`#if RUNTIME_TYPE_NETCORE`): omit explicit
`toolsDirectory` so both parent and child default to
`BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot`, which
resolves symlinks consistently via `AppContext.BaseDirectory`.
- On .NET Framework: keep `toolsDirectory: msbuildAssemblyPath` because
the parent (VS) and child (.NET task host) are in **different
directories**, and Windows has no symlink issues.
- Updated regression test to validate actual fix behavior (not
tautological).

### Testing

- `Handshake_ExternalPathCanMismatch_DefaultAlwaysMatches` - proves that
an external path (like `$(NetCoreSdkRoot)`) produces a different
handshake than the default, and that omitting `toolsDirectory` on both
sides always matches.
- `Handshake_WithSymlinkedToolsDirectory_ProducesDifferentKey` - proves
the bug mechanism with real symlinks on Unix.
- Existing E2E tests for TaskHostFactory tasks.
- SDK test validation on Helix macOS (the original failing environment).

### Notes

This fix addresses the MSB4216 errors seen in SDK Helix tests for
`ComputeWasmBuildAssets`, `ComputeManagedAssemblies`,
`MarshalingPInvokeScanner`, and other `TaskHostFactory` tasks on macOS.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
AR-May pushed a commit to AR-May/msbuild that referenced this pull request Mar 19, 2026
…net#13406)

### Context

PR dotnet#13175 (App Host Support) introduced a regression on macOS when the
SDK is accessed through a symlinked path. On macOS, `/tmp` is a symlink
to `/private/tmp`. When Helix tests run from `/tmp/helix/...`, the
`$(NetCoreSdkRoot)` MSBuild property preserves the unresolved path
`/tmp/helix/.../sdk/11.0.100-ci`, but the child task host process
resolves it to `/private/tmp/helix/.../sdk/11.0.100-ci` via
`AppContext.BaseDirectory`.

### Root Cause

In `ResolveAppHostOrFallback`, the parent passed `toolsDirectory:
msbuildAssemblyPath` (from `$(NetCoreSdkRoot)`) to the `Handshake`
constructor, while the child (`NodeEndpointOutOfProcTaskHost`) passed no
explicit `toolsDirectory`, defaulting to
`BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot` (which
resolves symlinks).

This produced different handshake hashes:
- **Parent**: `hash("/tmp/.../sdk/11.0.100-ci")`
- **Child**: `hash("/private/tmp/.../sdk/11.0.100-ci")`
- **Result**: Handshake mismatch -> MSB4216

Before PR dotnet#13175, neither side passed explicit `toolsDirectory`, so both
defaulted to `BuildEnvironmentHelper` and always matched.

### Changes Made

- On .NET Core (`#if RUNTIME_TYPE_NETCORE`): omit explicit
`toolsDirectory` so both parent and child default to
`BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot`, which
resolves symlinks consistently via `AppContext.BaseDirectory`.
- On .NET Framework: keep `toolsDirectory: msbuildAssemblyPath` because
the parent (VS) and child (.NET task host) are in **different
directories**, and Windows has no symlink issues.
- Updated regression test to validate actual fix behavior (not
tautological).

### Testing

- `Handshake_ExternalPathCanMismatch_DefaultAlwaysMatches` - proves that
an external path (like `$(NetCoreSdkRoot)`) produces a different
handshake than the default, and that omitting `toolsDirectory` on both
sides always matches.
- `Handshake_WithSymlinkedToolsDirectory_ProducesDifferentKey` - proves
the bug mechanism with real symlinks on Unix.
- Existing E2E tests for TaskHostFactory tasks.
- SDK test validation on Helix macOS (the original failing environment).

### Notes

This fix addresses the MSB4216 errors seen in SDK Helix tests for
`ComputeWasmBuildAssets`, `ComputeManagedAssemblies`,
`MarshalingPInvokeScanner`, and other `TaskHostFactory` tasks on macOS.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
YuliiaKovalova added a commit that referenced this pull request Mar 27, 2026
When MSBuild is launched via dotnet.exe (e.g. dotnet build, dotnet msbuild),
BuildEnvironmentHelper resolves CurrentMSBuildExePath to MSBuild.exe (the
AppHost) because it prefers .exe over .dll. This causes all out-of-proc
worker nodes to launch as MSBuild.exe AppHost processes, incurring native
bootstrap overhead per node.

This change detects when the current process is dotnet.exe and the resolved
MSBuild location is the AppHost, then substitutes MSBuild.dll so worker
nodes are launched via dotnet.exe instead. This only applies to regular
worker nodes (nodemode:1), not task host nodes (nodemode:2) which may
need the AppHost for COM host object support.

Fixes a ~16% regression in NuGet restore of OrchardCore (61s -> 72s)
introduced by the AppHost PR #13175.
YuliiaKovalova added a commit that referenced this pull request Mar 27, 2026
When MSBuild is launched via dotnet.exe (e.g. dotnet build, dotnet msbuild),
BuildEnvironmentHelper resolves CurrentMSBuildExePath to MSBuild.exe (the
AppHost) because it prefers .exe over .dll. This causes all out-of-proc
worker nodes to launch as MSBuild.exe AppHost processes, incurring native
bootstrap overhead per node.

This change detects when the current process is dotnet.exe and the resolved
MSBuild location is the AppHost, then substitutes MSBuild.dll so worker
nodes are launched via dotnet.exe instead. This only applies to regular
worker nodes (nodemode:1), not task host nodes (nodemode:2) which may
need the AppHost for COM host object support.

Fixes a ~16% regression in NuGet restore of OrchardCore (61s -> 72s)
introduced by the AppHost PR #13175.
YuliiaKovalova added a commit that referenced this pull request Mar 27, 2026
When MSBuild is launched via dotnet.exe (e.g. dotnet build, dotnet msbuild),
BuildEnvironmentHelper resolves CurrentMSBuildExePath to MSBuild.exe (the
AppHost) because it prefers .exe over .dll. This causes all out-of-proc
worker nodes to launch as MSBuild.exe AppHost processes, incurring native
bootstrap overhead per node.

This change detects when the current process is dotnet.exe and the resolved
MSBuild location is the AppHost, then substitutes MSBuild.dll so worker
nodes are launched via dotnet.exe instead. This only applies to regular
worker nodes (nodemode:1), not task host nodes (nodemode:2) which may
need the AppHost for COM host object support.

Fixes a ~16% regression in NuGet restore of OrchardCore (61s -> 72s)
introduced by the AppHost PR #13175.
rainersigwald added a commit that referenced this pull request Mar 30, 2026
Adds ETW instrumentation around the node create-or-connect path in
`NodeProviderOutOfProcBase.GetNodes`, to help chase down a possible
regression from #13175.

## New ETW events

| Event | ID | Parameters | Description |
|---|---|---|---|
| `NodeConnectStart` | 99 | `nodeId` | Begin acquiring a node (reuse or
launch) |
| `NodeConnectStop` | 100 | `nodeId`, `processId`, `isReused` | Node
acquired successfully |
| `NodeReuseScanStart` | 101 | | Begin scanning for reusable node
processes |
| `NodeReuseScanStop` | 102 | `candidateCount` | Scan complete |
| `NodeLaunchStart` | 103 | `nodeId` | Begin OS process creation |
| `NodeLaunchStop` | 104 | `nodeId`, `processId` | Process created |
| `NodePipeConnectStart` | 105 | `nodeId`, `processId` | Begin named
pipe connect + handshake |
| `NodePipeConnectStop` | 106 | `nodeId`, `processId`, `success` | Pipe
connect complete |

## Timeline (new node)
```
NodeReuseScanStart / NodeReuseScanStop(candidates)
NodeConnectStart(nodeId)
  NodeLaunchStart(nodeId)
  NodeLaunchStop(nodeId, pid)
  NodePipeConnectStart(nodeId, pid)
  NodePipeConnectStop(nodeId, pid, success)
NodeConnectStop(nodeId, pid, isReused=false)
```

## Timeline (reused node)
```
NodeReuseScanStart / NodeReuseScanStop(candidates)
NodeConnectStart(nodeId)
  NodePipeConnectStart(nodeId, candidatePid)
  NodePipeConnectStop(nodeId, candidatePid, success)
NodeConnectStop(nodeId, pid, isReused=true)
```

Both `NodeProviderOutOfProc` and `NodeProviderOutOfProcTaskHost` flow
through `GetNodes`.

No behavioral change. Pure observability addition.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rainersigwald pushed a commit that referenced this pull request Mar 30, 2026
…os (#13452)

PR #13175 introduced the MSBuild AppHost (`MSBuild.exe`). After this
change, `dotnet build` spawns worker nodes as `MSBuild.exe` processes.
ETL trace analysis of OrchardCore NuGet restore shows a **~16%
regression** (61s -> 72s):

The root cause of the performance difference is not fully understood.
Reverting worker nodes to `dotnet MSBuild.dll` in CLI scenarios
restores baseline performance.
dfederm pushed a commit to dfederm/msbuild that referenced this pull request Apr 9, 2026
Adds ETW instrumentation around the node create-or-connect path in
`NodeProviderOutOfProcBase.GetNodes`, to help chase down a possible
regression from dotnet#13175.

## New ETW events

| Event | ID | Parameters | Description |
|---|---|---|---|
| `NodeConnectStart` | 99 | `nodeId` | Begin acquiring a node (reuse or
launch) |
| `NodeConnectStop` | 100 | `nodeId`, `processId`, `isReused` | Node
acquired successfully |
| `NodeReuseScanStart` | 101 | | Begin scanning for reusable node
processes |
| `NodeReuseScanStop` | 102 | `candidateCount` | Scan complete |
| `NodeLaunchStart` | 103 | `nodeId` | Begin OS process creation |
| `NodeLaunchStop` | 104 | `nodeId`, `processId` | Process created |
| `NodePipeConnectStart` | 105 | `nodeId`, `processId` | Begin named
pipe connect + handshake |
| `NodePipeConnectStop` | 106 | `nodeId`, `processId`, `success` | Pipe
connect complete |

## Timeline (new node)
```
NodeReuseScanStart / NodeReuseScanStop(candidates)
NodeConnectStart(nodeId)
  NodeLaunchStart(nodeId)
  NodeLaunchStop(nodeId, pid)
  NodePipeConnectStart(nodeId, pid)
  NodePipeConnectStop(nodeId, pid, success)
NodeConnectStop(nodeId, pid, isReused=false)
```

## Timeline (reused node)
```
NodeReuseScanStart / NodeReuseScanStop(candidates)
NodeConnectStart(nodeId)
  NodePipeConnectStart(nodeId, candidatePid)
  NodePipeConnectStop(nodeId, candidatePid, success)
NodeConnectStop(nodeId, pid, isReused=true)
```

Both `NodeProviderOutOfProc` and `NodeProviderOutOfProcTaskHost` flow
through `GetNodes`.

No behavioral change. Pure observability addition.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dfederm pushed a commit to dfederm/msbuild that referenced this pull request Apr 9, 2026
…os (dotnet#13452)

PR dotnet#13175 introduced the MSBuild AppHost (`MSBuild.exe`). After this
change, `dotnet build` spawns worker nodes as `MSBuild.exe` processes.
ETL trace analysis of OrchardCore NuGet restore shows a **~16%
regression** (61s -> 72s):

The root cause of the performance difference is not fully understood.
Reverting worker nodes to `dotnet MSBuild.dll` in CLI scenarios
restores baseline performance.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants