Skip to content

Enable multithreaded MSBuild for local and PR builds - #10726

Draft
Jan Provazník (JanProvaznik) wants to merge 2 commits into
microsoft:mainfrom
JanProvaznik:perf/msbuild-multithreaded
Draft

Enable multithreaded MSBuild for local and PR builds#10726
Jan Provazník (JanProvaznik) wants to merge 2 commits into
microsoft:mainfrom
JanProvaznik:perf/msbuild-multithreaded

Conversation

@JanProvaznik

@JanProvaznik Jan Provazník (JanProvaznik) commented Aug 25, 2026

Copy link
Copy Markdown

Summary

  • enable MSBuild's experimental multithreaded mode by default for local builds
  • enable multithreaded MSBuild explicitly in ordinary public/PR Azure Pipelines builds on Windows, Linux, and macOS
  • keep MSBuildCache graph/cache follow-up builds process-based
  • keep the official build pipeline unchanged

Local benchmark

Measured five clean-output restore+build runs per mode after warming the SDK, workload, and NuGet caches on an 8-core/16-logical-processor Windows machine.

Mode Runs Average Median Range
Existing process-based MSBuild 5 167.1s 166.6s 154.1–181.9s
Multithreaded MSBuild 5 159.0s 160.7s 144.2–168.3s

Local average improved by 8.1 seconds (4.8%) and median by 5.9 seconds (3.6%). The ranges overlap, so this is directional rather than statistically conclusive.

CI benchmark

Azure Pipelines build 1566758 succeeded in every configuration. The baseline is the average build-step duration from the four most recent successful fork PR builds (1566681, 1564881, 1564777, and 1564593), which use the same cache-disabled fallback path and are therefore more comparable than trusted-branch cache builds.

Build step Multithreaded Fork PR baseline Change
Windows Debug 666.1s 715.0s -6.8%
Windows Release 685.5s 692.4s -1.0%
Linux Debug 367.1s 367.1s 0.0%
Linux Release 352.1s 369.5s -4.7%
macOS Debug 663.0s 702.8s -5.7%
macOS Release 513.3s 850.6s -39.7%

The two Windows configurations improved from 703.7s to 675.8s combined (-4.0%), closely matching the 4.8% local average improvement. Linux is neutral-to-modestly faster. macOS is highly variable—the baseline samples range from 533.7s to 1,015.6s for Debug and 723.9s to 1,021.9s for Release—so the apparent Release gain should not be treated as a reliable estimate.

MSBuildCache compatibility finding

The first experiment, build 1566674, failed because the cache-disabled Windows fallback still imported Microsoft.MSBuildCache.SharedCompilation. Its unannotated ResolveFileAccesses task was routed to a sidecar TaskHost under -mt. MSBuild then corrupted the returned FileAccessData structs during TaskHost packet deserialization through interface boxing and crashed in FileAccessManager.ReportFileAccess when replaying a null path.

The ProjectCachePlugin itself was inactive, so this was not a cache lookup or materialization race. The engine defect is tracked by dotnet/msbuild#14824.

This PR therefore keeps cache graph and cache-specific sign/pack operations process-based. Ordinary Windows fallback builds use -mt without importing cache support packages; Linux and macOS ordinary builds also use -mt.

Conclusion

Multithreaded MSBuild provides a modest end-to-end improvement on Windows: 4.8% locally and 4.0% in comparable PR CI, with no corrected-run failures. Linux results are consistent with a smaller improvement, while current macOS variance is too high for a precise estimate. Cache population and multithreaded execution should remain separate experiments until the TaskHost file-access serialization defect is fixed and their combined topology can be evaluated independently.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings August 25, 2026 11:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Enables multithreaded MSBuild for local Windows builds and public CI builds while leaving official builds unchanged.

Changes:

  • Defaults local PowerShell builds to multithreaded mode.
  • Enables multithreading across public Windows, Linux, and macOS CI paths.
  • Identified missing equivalent default for local Unix builds.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
eng/build.ps1 Configures the local PowerShell default.
azure-pipelines.yml Enables multithreading in public CI build paths.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread eng/build.ps1
Comment on lines +86 to +87
if (-not $PSBoundParameters.ContainsKey("msbuildMultiThreaded")) {
$PSBoundParameters["msbuildMultiThreaded"] = -not $ci
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — Enabling MSBuild's new -msbuildMultiThreaded (-mt) mode in this PR triggers a NullReferenceException inside the ResolveFileAccesses MSBuild task shipped by the Microsoft.MSBuildCache.SharedCompilation package, failing every Windows build leg (Release and Debug); the Linux/macOS legs (which don't hit this code path) compiled cleanly.

Root cause: -mt is incompatible with Microsoft.MSBuildCache.SharedCompilation's ResolveFileAccesses task

This PR adds -msbuildMultiThreaded/-mt to the CI invocations in azure-pipelines.yml and threads the corresponding parameter through eng/build.ps1. On both Windows Release and Windows Debug legs, MSBuild fails 13 projects (e.g. Microsoft.Testing.Platform, TestFramework.SourceGeneration, TestFramework, MSTest.Analyzers, MSTest.SourceGeneration, MSTest.Sdk) with the identical stack:

MSB4018: The "ResolveFileAccesses" task failed unexpectedly.
System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.Build.FileAccesses.FileAccessManager.ReportFileAccess(FileAccessData fileAccessData, Int32 nodeId)
   at Microsoft.Build.BackEnd.TaskHostTask.HandleTaskHostTaskComplete(TaskHostTaskComplete taskHostTaskComplete)
   at Microsoft.Build.BackEnd.TaskHostTask.HandlePacket(INodePacket packet, Boolean& taskFinished)
   at Microsoft.Build.BackEnd.TaskHostTask.Execute()
   at Microsoft.Build.BackEnd.TaskExecutionHost.Execute()
   at Microsoft.Build.BackEnd.TaskBuilder.<ExecuteInstantiatedTask>d__26.MoveNext()

thrown from Microsoft.MSBuildCache.SharedCompilation.targets:9 in the ResolveCoreCompileFileAccesses target of the restored .packages\microsoft.msbuildcache.sharedcompilation\0.1.328-preview package. This repo already uses that MSBuildCache package for shared/out-of-proc compilation, which relies on MSBuild's TaskHostTask/FileAccessManager file-access-reporting plumbing (used for build-output caching). Turning on -mt changes MSBuild's node/threading model for concurrent task execution and file-access reporting; FileAccessManager.ReportFileAccess null-refs when it receives a completion callback for a node id it apparently doesn't have registered under multi-threaded execution — an incompatibility between this MSBuildCache version's file-access hook and -mt mode, not a defect in TestFx's own source.

Evidence that isolates -mt as the trigger:

  • Only the Windows legs that pass -msbuildMultiThreaded:$true/-mt (Windows Release, Windows Debug) fail; the Windows application-model acceptance leg, both Linux legs, and both macOS legs built cleanly.
  • Every failing project fails at the exact same target/task/line (ResolveCoreCompileFileAccessesResolveFileAccesses, SharedCompilation.targets:9) — one root cause fanning out across 13 projects, not 13 independent bugs.
  • No C#/analyzer compiler errors were reported anywhere in either failing leg — the actual compile never runs; the crash happens in the file-access-tracking wrapper around the compile task itself.

Affected projects (13 total, identical stack; representative subset)

  • src/Platform/Microsoft.Testing.Platform/Microsoft.Testing.Platform.csproj
  • src/Platform/Microsoft.Testing.Platform.ServerMode.Client.Sources/...csproj
  • src/TestFramework/TestFramework.SourceGeneration/TestFramework.SourceGeneration.csproj
  • src/TestFramework/TestFramework/TestFramework.csproj (Debug leg only)
  • src/Analyzers/MSTest.Analyzers/MSTest.Analyzers.csproj
  • src/Analyzers/MSTest.SourceGeneration/MSTest.SourceGeneration.csproj
  • src/Analyzers/MSTest.GlobalConfigsGenerator/MSTest.GlobalConfigsGenerator.csproj
  • src/Package/MSTest.Sdk/MSTest.Sdk.csproj
  • test/IntegrationTests/TestAssets/SampleProjectForAssemblyResolution/...csproj
  • samples/CtrfPlayground/XunitMtp/XunitMtp.csproj

Suggested fix

This is a build-infrastructure/tooling incompatibility, not a TestFx source-code bug, so it falls outside this workflow's automated fix-commit scope (limited to src//test/; eng/build.ps1 and azure-pipelines.yml are both excluded, and the fix is not a mechanical rename provable from a compiler error anyway). Two viable directions for a maintainer:

  1. Don't enable -mt on legs where Microsoft.MSBuildCache.SharedCompilation is active until that package (currently pinned at 0.1.328-preview) ships a fix for -mt compatibility — i.e. drop the newly-added -msbuildMultiThreaded lines for the Windows Release/Debug stages in azure-pipelines.yml, or gate them behind whether MSBuildCache is enabled for that leg.
  2. If -mt is required for this PR's goal, file/check for an upstream issue against Microsoft.MSBuildCache for -mt (multi-threaded node) compatibility with its ResolveFileAccesses/FileAccessManager file-access-tracking hook, and bump the package once fixed.

Since the crash originates inside a third-party MSBuild extension package rather than TestFx source, no inline code suggestion is offered — reverting or gating the newly-added -mt flags on the affected legs is the pragmatic short-term mitigation.


Build overview (Windows Release leg)
Build: FAILED
Duration: 156.2s
MSBuild: 18.10.0-1.26379.9+c88db8eb0
Projects: 74  Errors: 13  Warnings: 1
All MSBuild errors (13, identical across Windows Release + Windows Debug legs)
Code Task Target Root message
MSB4018 ResolveFileAccesses ResolveCoreCompileFileAccesses NullReferenceException in FileAccessManager.ReportFileAccess, thrown from Microsoft.MSBuildCache.SharedCompilation.targets:9

(13 occurrences across each failing leg, one per failed project, all sharing this identical stack trace.)


🤖 Generated by the Build Failure Analysis workflow using (a href="(dev.azure.com/redacted) · commit ca552a2

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · auto · 92.4 AIC · ⌖ 1.74 AIC · ⊞ 13.3K · [◷]( · )

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 25, 2026 12:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

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.

2 participants