Skip to content

Skip HandleBuildResultAsync when ProjectInstance is not loaded on in-proc node#13445

Open
YuliiaKovalova wants to merge 1 commit intomainfrom
dev/fix-projectcache-null-project
Open

Skip HandleBuildResultAsync when ProjectInstance is not loaded on in-proc node#13445
YuliiaKovalova wants to merge 1 commit intomainfrom
dev/fix-projectcache-null-project

Conversation

@YuliiaKovalova
Copy link
Member

@YuliiaKovalova YuliiaKovalova commented Mar 24, 2026

Summary

Related to https://github.com/dotnet/msbuild/pull/13332/changes
HandleBuildResultAsync in ProjectCacheService crashes with InternalErrorException("Project unexpectedly null") when a build result arrives for a configuration whose ProjectInstance was never loaded on the in-proc node.

Root Cause

In VS (and global plugin) scenarios, ShouldUseCache returns true without checking IsLoaded. This is by design - it's needed for the pre-build cache request path (BuildManager.ExecuteSubmission), where the project hasn't been evaluated yet and PostCacheRequest loads it via EvaluateProjectIfNecessary.

However, ShouldUseCache is also called from the post-build path (BuildManager.HandleResult) to decide whether to notify cache plugins about the result. When a project was built on an out-of-proc worker node, the in-proc config cache entry never gets a ProjectInstance assigned - ProjectInstance is intentionally not transferred back from worker nodes because it's too large to serialize across the named pipe (only BuildResult with target outcomes is returned).

This means HandleBuildResultAsync is called with a configuration where IsLoaded is false and accessing .Project would crash.

Fix

Replace the VerifyThrowInternalNull(requestConfiguration.Project) assert with an !requestConfiguration.IsLoaded early return.

Why IsLoaded instead of Project == null: The Project getter has an internal assert (!IsCached) that throws if the configuration happens to be in cached-to-disk state. IsLoaded is a safe read-only check (_project?.IsLoaded == true) with no side effects or asserts. It also correctly handles the edge case of a partially-initialized ProjectInstance (deserialized but not yet LateInitialize'd).

Skipping is safe because:

  • The build has already completed successfully at this point
  • The cache plugin's HandleProjectFinishedAsync requires a loaded ProjectInstance to determine applicable plugins and provide project context
  • No build correctness is affected — only the cache plugin notification is skipped for this configuration

Validation

Diagnostic telemetry (ProjectCacheState) deployed in a prior iteration confirmed the root cause across multiple VS repo users:

  • IsLoaded=False, IsCached=False, WasGeneratedByNode=False, IsVsScenario=True
  • Configuration created from BuildRequestData without ProjectInstance (VS submits by path)
  • Project built on out-of-proc worker node, ProjectInstance never transferred back

Copilot AI review requested due to automatic review settings March 24, 2026 13:50
@YuliiaKovalova YuliiaKovalova changed the title Replace diagnostic telemetry with simple null check in HandleBuildResultAsync Skip HandleBuildResultAsync when ProjectInstance is not loaded on in-proc node Mar 24, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates MSBuild’s project cache result handling to gracefully no-op when the BuildRequestConfiguration does not have a locally loaded ProjectInstance (common in VS builds where projects may execute on out-of-proc nodes and the in-proc configuration remains unloaded), removing the prior crash-diagnostics telemetry emission.

Changes:

  • Remove diagnostic crash telemetry emission from HandleBuildResultAsync when the configuration has no locally loaded project instance.
  • Add an IsLoaded-based early return to skip cache result handling when the project isn’t loaded on the in-proc node.
  • Expand inline documentation explaining why HandleBuildResultAsync can be reached with an unloaded configuration in VS/out-of-proc scenarios.

…ultAsync

Telemetry data confirmed the root cause: in the VS scenario, ShouldUseCache
returns true without checking IsLoaded, allowing configurations built on
remote nodes (where Project is never loaded locally) to reach
HandleBuildResultAsync with a null Project.

This is expected behavior — the project cache plugin doesn't need to process
results for projects that were never loaded on the in-proc node. Replace the
diagnostic telemetry emission with a simple null check and early return.
@YuliiaKovalova YuliiaKovalova force-pushed the dev/fix-projectcache-null-project branch from 92fa8f4 to 269a512 Compare March 24, 2026 14:11
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