-
Notifications
You must be signed in to change notification settings - Fork 10.6k
Add -CentralizedRestore to avoid NuGet parallel restore race conditions #65069
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
31e9ddc
18e8558
cac0fcd
c0860a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,73 @@ | ||||||
| <Project> | ||||||
| <!-- | ||||||
| Centralized restore project that restores ALL projects in a single MSBuild invocation. | ||||||
| This avoids the NuGet race condition (https://github.com/NuGet/Home/issues/7648) where | ||||||
| parallel restore of the same project from multiple callers causes "Cannot create a file | ||||||
| when that file already exists" errors. | ||||||
|
|
||||||
| Usage in CI: | ||||||
| ./eng/build.cmd -ci -CentralizedRestore ... | ||||||
|
|
||||||
| Or manually: | ||||||
| dotnet restore AspNetCore.slnx | ||||||
| dotnet msbuild eng/CentralizedRestore.proj /t:RestoreDelayedOnly /p:Configuration=Release | ||||||
| ./eng/build.cmd -ci -NoRestore /p:RestoreForTestAssets=false /p:RestoreForDelayedProjects=false ... | ||||||
| --> | ||||||
|
|
||||||
| <Import Project="RequiresDelayedBuildProjects.props" /> | ||||||
|
|
||||||
| <PropertyGroup> | ||||||
| <RepoRoot Condition="'$(RepoRoot)' == ''">$([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)', '..'))</RepoRoot> | ||||||
| </PropertyGroup> | ||||||
|
|
||||||
| <!-- | ||||||
| Restore only delayed build projects and test assets (projects not in the main solution). | ||||||
| Called after `dotnet restore AspNetCore.slnx` to complete the restore. | ||||||
| --> | ||||||
| <Target Name="RestoreDelayedOnly"> | ||||||
| <Message Importance="High" Text="======================================" /> | ||||||
| <Message Importance="High" Text="Restoring delayed build projects and test assets" /> | ||||||
| <Message Importance="High" Text="======================================" /> | ||||||
|
|
||||||
| <!-- Filter to only restorable projects (not .proj or .nodeproj) --> | ||||||
| <ItemGroup> | ||||||
| <_DelayedProjectsToRestore Include="@(RequiresDelayedBuild)" | ||||||
| Condition="'%(Extension)' != '.nodeproj' AND '%(Extension)' != '.proj'" /> | ||||||
| </ItemGroup> | ||||||
|
|
||||||
| <MSBuild Projects="@(_DelayedProjectsToRestore)" | ||||||
| Targets="Restore" | ||||||
| BuildInParallel="false" | ||||||
| Properties="MSBuildRestoreSessionId=$([System.Guid]::NewGuid())" /> | ||||||
|
|
||||||
| <Message Importance="High" Text="======================================" /> | ||||||
| <Message Importance="High" Text="Delayed projects restore complete" /> | ||||||
| <Message Importance="High" Text="======================================" /> | ||||||
| </Target> | ||||||
|
|
||||||
| <!-- | ||||||
| Full restore of everything - alternative to solution-based restore. | ||||||
| Slower but more comprehensive. | ||||||
| --> | ||||||
| <Target Name="RestoreAll"> | ||||||
| <Message Importance="High" Text="======================================" /> | ||||||
| <Message Importance="High" Text="Centralized Restore - Restoring all projects" /> | ||||||
| <Message Importance="High" Text="======================================" /> | ||||||
|
|
||||||
| <Exec Command="dotnet restore "$(RepoRoot)AspNetCore.slnx"" /> | ||||||
|
||||||
| <Exec Command="dotnet restore "$(RepoRoot)AspNetCore.slnx"" /> | |
| <Exec Command="dotnet restore "$(RepoRoot)AspNetCore.slnx" --disable-parallel" /> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -206,6 +206,10 @@ param( | |
| [Alias('pb')] | ||
| [switch]$ProductBuild, | ||
|
|
||
| # Use centralized restore to avoid NuGet parallel restore race conditions (NuGet/Home#7648) | ||
| # When set, performs a single upfront restore then builds without restore | ||
| [switch]$CentralizedRestore, | ||
|
|
||
| # Intentionally lowercase as tools.ps1 depends on it | ||
| [switch]$fromVMR, | ||
|
|
||
|
|
@@ -271,7 +275,9 @@ $RunBuild = if ($NoBuild) { $false } else { $true } | |
|
|
||
| # Run restore by default unless -NoRestore is set. | ||
| # -NoBuild implies -NoRestore, unless -Restore is explicitly set (as in restore.cmd) | ||
| # -CentralizedRestore performs a single upfront restore then disables per-project restore | ||
| $RunRestore = if ($NoRestore) { $false } | ||
| elseif ($CentralizedRestore) { $false } # Will do centralized restore separately | ||
|
||
| elseif ($Restore) { $true } | ||
| elseif ($NoBuild) { $false } | ||
| else { $true } | ||
|
|
@@ -484,6 +490,44 @@ try { | |
| $global:VerbosePreference = 'Continue' | ||
| } | ||
|
|
||
| # Perform centralized restore if requested - this restores everything upfront in a single | ||
| # coordinated operation to avoid NuGet race conditions (https://github.com/NuGet/Home/issues/7648) | ||
| if ($CentralizedRestore) { | ||
| Write-Host | ||
| Write-Host "===== Centralized Restore =====" -ForegroundColor Cyan | ||
| Write-Host "Restoring all projects upfront to avoid NuGet parallel restore race conditions" -ForegroundColor Cyan | ||
|
|
||
| # Restore via the solution file with --disable-parallel to avoid NuGet internal race conditions | ||
| # NuGet's RestoreTask is not thread-safe when same project is restored in parallel | ||
| Write-Host "Restoring AspNetCore.slnx (sequential)..." -ForegroundColor Yellow | ||
| & dotnet restore "$RepoRoot\AspNetCore.slnx" --disable-parallel --verbosity $Verbosity | ||
| if ($LASTEXITCODE -ne 0) { | ||
| Write-Error "Solution restore failed" | ||
| exit $LASTEXITCODE | ||
| } | ||
|
|
||
| # Generate files that other projects depend on (normally done AfterTargets="Restore" in Tools.props) | ||
| # Must be done BEFORE any other project evaluation since they import these generated files | ||
| Write-Host "Generating build files..." -ForegroundColor Yellow | ||
| & dotnet restore "$RepoRoot\eng\tools\GenerateFiles\GenerateFiles.csproj" --verbosity $Verbosity | ||
| if ($LASTEXITCODE -ne 0) { | ||
| Write-Error "GenerateFiles restore failed" | ||
| exit $LASTEXITCODE | ||
| } | ||
| & dotnet msbuild "$RepoRoot\eng\tools\GenerateFiles\GenerateFiles.csproj" /t:GenerateDirectoryBuildFiles /p:Configuration=$Configuration /verbosity:$Verbosity | ||
| if ($LASTEXITCODE -ne 0) { | ||
| Write-Error "GenerateFiles build failed" | ||
| exit $LASTEXITCODE | ||
| } | ||
|
|
||
| # Note: Delayed build projects (RequiresDelayedBuild) are NOT restored here because they | ||
| # require the targeting pack to be built first. | ||
| # They will be restored later by BuildAfterTargetingPack.csproj with sequential restore. | ||
|
|
||
| Write-Host "===== Centralized Restore Complete =====" -ForegroundColor Cyan | ||
| Write-Host | ||
| } | ||
|
|
||
| if (-not $NoBuildRepoTasks) { | ||
| Write-Host | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,13 @@ | |
| <IsUnitTestProject Condition=" '$(SkipTestBuild)' == 'true' ">false</IsUnitTestProject> | ||
| <IsUnitTestProject Condition=" '$(SkipTestBuild)' != 'true' ">true</IsUnitTestProject> | ||
| <IsTestProject>$(IsUnitTestProject)</IsTestProject> | ||
| <!-- | ||
| Use sequential restore when CentralizedRestore is enabled to avoid NuGet race condition where | ||
| parallel restore of the same project causes "Cannot create a file when that file already exists" errors. | ||
| See https://github.com/NuGet/Home/issues/7648 | ||
| --> | ||
| <_RestoreInParallel Condition="'$(CentralizedRestore)' != 'true'">true</_RestoreInParallel> | ||
| <_RestoreInParallel Condition="'$(CentralizedRestore)' == 'true'">false</_RestoreInParallel> | ||
| </PropertyGroup> | ||
|
|
||
| <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" /> | ||
|
|
@@ -45,7 +52,7 @@ | |
| Condition=" '$(DotNetBuildSourceOnly)' != 'true' AND ('$(TargetOsName)' == 'win' AND '$(TargetArchitecture)' == 'x64') " | ||
| Returns="@(TargetPathWithTargetPlatformMoniker)"> | ||
| <MSBuild Projects="@(RequiresDelayedBuild)" | ||
| BuildInParallel="$(BuildInParallel)" | ||
| BuildInParallel="$(_RestoreInParallel)" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This project is the source of most of the issues - I think the rest of the custom stuff here may be overkill, but I'd bet that just setting this to
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, maybe we just pass
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for ideas. I was not aware of the other PR, let's maybe first wait for a while and see how good it works, then I can come back here if needed and try to work on improving this attempt. |
||
| Properties="MSBuildRestoreSessionId=$([System.Guid]::NewGuid())" | ||
| Targets="Restore" /> | ||
| <MSBuild Projects="@(RequiresDelayedBuild)" BuildInParallel="$(BuildInParallel)" Targets="Build"> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation references properties RestoreForTestAssets and RestoreForDelayedProjects that do not appear to exist in the codebase. This example command would not work as documented. Consider updating the documentation to reflect the actual properties used, which is CentralizedRestore based on the implementation in build.ps1 and the condition checks in BuildAfterTargetingPack.csproj and FunctionalTestWithAssets.targets.