Skip to content

Fix intermittent ApiCompat.Task build failure due to file lock race condition#55109

Merged
MichaelSimons merged 7 commits into
mainfrom
michaelsimons-investigate-apicompat-ioexception
Jul 2, 2026
Merged

Fix intermittent ApiCompat.Task build failure due to file lock race condition#55109
MichaelSimons merged 7 commits into
mainfrom
michaelsimons-investigate-apicompat-ioexception

Conversation

@MichaelSimons

@MichaelSimons MichaelSimons commented Jul 1, 2026

Copy link
Copy Markdown
Member

Summary

Fixes an intermittent build failure where Microsoft.DotNet.ApiCompat.Task fails with an IOException because its output DLL is locked by a concurrent MSBuild process during parallel builds.

Problem

The GetAssemblyAttributes task intermittently fails in CI when GetAssemblyVersion tries to read a DLL that is temporarily locked by another concurrent MSBuild process. This manifests as:

System.IO.IOException: The process cannot access the file '...Microsoft.DotNet.ApiCompat.Task.dll' because it is being used by another process.
   at Microsoft.NET.Build.Tasks.FileUtilities.GetAssemblyVersion(String sourcePath)
   at Microsoft.NET.Build.Tasks.GetAssemblyAttributes.ExecuteCore()

Impact

This is hitting PR validation and official builds on the order of 20 times per week, requiring build retries each time.

Root cause

Several test projects have ProjectReference entries to single-TFM projects like Microsoft.DotNet.ApiCompat.Task and Microsoft.DotNet.GenAPI.Task that pass TargetFramework as a global property. Since these referenced projects are single-TFM, this creates a second build request with different global properties that writes to the same intermediate output path. When these two builds run concurrently, one node's CoreCompile can overlap with the other's _GetAssemblyInfoFromTemplateFile target, hitting the locked DLL.

This is the same class of issue already documented in ApiCompat.Task.csproj itself (see the _AddBuildOutputToPackageCore target comment and the CopyPackageToTestPackagesDir comment).

Fix

Replaces SetTargetFramework="TargetFramework=$(NetMinimum)" with SkipGetTargetFrameworkProperties="true" UndefineProperties="TargetFramework" on the ProjectReference entries in the ApiCompat and GenAPI integration test projects. This prevents MSBuild from passing TargetFramework as a global property to the referenced single-TFM projects, ensuring they unify with the solution-direct build and eliminating the concurrent-write race.

Fixes #54864

Wraps GetAssemblyVersion in a retry loop (3 attempts with growing delay:
100ms, 200ms, 300ms) to handle transient IOExceptions caused by concurrent
MSBuild processes locking the file during parallel builds.

The original logic is moved to GetAssemblyVersionCore, and the new
GetAssemblyVersion method provides the retry wrapper. This follows the
same pattern used by GenerateBundle.cs and GZipCompress.cs in this repo.

Fixes #54864

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 1, 2026 19:31
@MichaelSimons MichaelSimons requested review from a team and ViktorHofer July 1, 2026 19:34

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

This PR adds retry/backoff around FileUtilities.GetAssemblyVersion to reduce intermittent IOException failures when reading assemblies during parallel MSBuild builds (notably when another process temporarily holds an exclusive lock on the DLL).

Changes:

  • Introduces a retry loop in GetAssemblyVersion to handle transient IOException during file access.
  • Extracts the original implementation into GetAssemblyVersionCore and calls it from the retry wrapper.

Comment thread src/Tasks/Common/FileUtilities.MetadataReader.cs Outdated
Comment thread src/Tasks/Common/FileUtilities.MetadataReader.cs Outdated
@ViktorHofer

Copy link
Copy Markdown
Member

ApiCompat.Task should not get built twice with different global properties. That's a bin and intermediate clash and should get fixed.

The ProjectReference from Microsoft.NET.Build.Tasks to ApiCompat.Task was
passing its own TargetFramework as a global property. Since ApiCompat.Task
is single-TFM (net10.0), this created a duplicate build request with
different global properties that wrote to the same intermediate output path,
causing a file-locking race condition.

Adding GlobalPropertiesToRemove=TargetFramework ensures MSBuild unifies
the two build requests into a single cached result, eliminating the
concurrent-write race that triggers the IOException.

Fixes #54864

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@MichaelSimons MichaelSimons force-pushed the michaelsimons-investigate-apicompat-ioexception branch from 83b9680 to 4d37449 Compare July 1, 2026 21:02
@MichaelSimons

Copy link
Copy Markdown
Member Author

ApiCompat.Task should not get built twice with different global properties. That's a bin and intermediate clash and should get fixed.

Addressed in 4d37449. I left the added retry for defense in-depth. Let me know if you think that is unnecessary.

@MichaelSimons MichaelSimons changed the title Add retry logic to GetAssemblyVersion for transient file lock IOException Fix intermittent ApiCompat.Task build failure due to file lock race condition Jul 1, 2026

@ViktorHofer ViktorHofer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I just looked at a binlog as I was suprirsed that Microsoft.NET.Build.Tasks would pass TargetFramework as a global property to ApiCompat.Task.csproj. The P2P protocol shouldn't do this when it sees a non-multitargeting project. Fortunately it doesn't do that, otherwise we would have a product bug in the P2P protocol:

Image

The extra TargetFramework property is coming from somewhere else:

Image

<ProjectReference Include="$(RepoRoot)src\Compatibility\ApiCompat\Microsoft.DotNet.ApiCompat.Task\Microsoft.DotNet.ApiCompat.Task.csproj" ReferenceOutputAssembly="false" SetTargetFramework="TargetFramework=$(NetMinimum)" />
<ProjectReference Include="$(RepoRoot)src\Compatibility\ApiCompat\Microsoft.DotNet.ApiCompat.Tool\Microsoft.DotNet.ApiCompat.Tool.csproj" ReferenceOutputAssembly="false" SetTargetFramework="TargetFramework=$(NetMinimum)" />

The SetTargetFramework metadata is incorrect and should be removed. Passing that to a single targeting project is incorrect. We should update our skill to make that clear. EDIT: dotnet/skills#853

@ViktorHofer ViktorHofer requested a review from a team as a code owner July 2, 2026 05:54
@dsplaisted

Copy link
Copy Markdown
Member

Is there a reason we need to include SkipGetTargetFrameworkProperties="true" UndefineProperties="TargetFramework"? Could we remove those and let the P2P protocol handle the project reference? We'd still set ReferenceOutputAssembly="false".

@ViktorHofer

ViktorHofer commented Jul 2, 2026

Copy link
Copy Markdown
Member

Yes the need for that metadata is all explained in this msbuild skill update: dotnet/skills#853

TL;DR: Without SkipGetTargetFrameworkProperties="true" ResolvePackageAssets errors because a .NETFramework TFM (test project) can't "reference" a .NETCoreApp TFM (task project which is .NETCoreApp only) even if ReferenceOutputAssembly=false is set. UndefineProperties="TargetFramework" is needed because otherwise the TargetFramework global property from the inner build of the test project would flow to the referenced task project.

I would love better support here by the .NET SDK. The current P2P protocol doesn't really consider such cases.

@dsplaisted

Copy link
Copy Markdown
Member

I see, I didn't realize that this was a .NETFramework -> .NETCoreApp dependency.

@MichaelSimons MichaelSimons merged commit 108939f into main Jul 2, 2026
25 checks passed
@MichaelSimons MichaelSimons deleted the michaelsimons-investigate-apicompat-ioexception branch July 2, 2026 13:46
@ViktorHofer

Copy link
Copy Markdown
Member

@dsplaisted filed dotnet/msbuild#14250 and would love your input on this

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.

[Known Build Error] build fails: GetAssemblyAttributes IOException - Microsoft.DotNet.ApiCompat.Task.dll in use by another process

4 participants