Skip to content

RestoreTask is not "thread safe" - NuGet restore fails with "Cannot create a file when that file already exists" when restore invoked in parallel #7648

@natemcmaster

Description

@natemcmaster

Invoking the "Restore" target on projects in parallel leads to a race condition in committing the restore result to disk. This appears most often as the result of developers writing their own MSBuild targets and using the <MSBuild> task to invoke Restore.

The only workaround I could find was to avoid this calling pattern completely. Instead, you can call the the 'restore' target in NuGet.targets and pass in the list of projects.

cref dotnet/arcade#1567
cref https://github.com/aspnet/BuildTools/blob/0872134136bf3d181e3ca3fa700b7ff392c055b3/files/KoreBuild/modules/projectbuild/module.targets#L21-L70

Ideas

I don't see an obvious solution. Here are some ideas

  • Use IBuildEngine4.GetRegisteredTaskObject within RestoreTask to avoid double-restores during the same build.
  • Use IBuildEngine4.GetRegisteredTaskObject within RestoreTask to detect a double-restore was invoked, and provide a warning message.
  • Include which file is the problem in the error message.
  • Instead of failing immediately when you can't write the restore result, retry.

Details about Problem

NuGet product used: dotnet.exe or MSBuild.exe
dotnet.exe --version: 2.2.100
VS version: 15.9
OS version: Any
Worked before?: no

Detailed repro steps so we can see the same problem

  1. Create a set of 3 simple .NET standard class library projects (see attached repro). Two projects should both have ProjectReference to the third project.
Lib A --> LibC
Lib B --> LibC
  1. Create an MSBuild targets file like this. We'll call it "ci.proj".
<!-- ci.proj -->
<Project>
    <ItemGroup>
        <Projects Include="LibA/LibA.csproj" />
        <Projects Include="LibB/LibB.csproj" />
        <Projects Include="LibC/LibC.csproj" />
    </ItemGroup>

    <Target Name="Restore">
        <MSbuild Projects="@(Projects)"
                 Targets="Restore"
                 BuildInParallel="true" />
    </Target>
</Project>
  1. Run restore by invoking msbuild -t:ci.proj -t:restore

Result

NuGet fails in LibC with an error that says "Cannot create a file when that file already exists". The error message does not indicate which file was the problem.

 Restoring packages for C:\tmp\restoreRepro\LibC\LibC.csproj...
  Restoring packages for C:\tmp\restoreRepro\LibC\LibC.csproj...
  Restoring packages for C:\tmp\restoreRepro\LibB\LibB.csproj...
  Generating MSBuild file C:\tmp\restoreRepro\LibC\obj\LibC.csproj.nuget.g.props.
  Generating MSBuild file C:\tmp\restoreRepro\LibC\obj\LibC.csproj.nuget.g.targets.
  Generating MSBuild file C:\tmp\restoreRepro\LibC\obj\LibC.csproj.nuget.g.targets.
  Generating MSBuild file C:\tmp\restoreRepro\LibB\obj\LibB.csproj.nuget.g.props.
  Generating MSBuild file C:\tmp\restoreRepro\LibB\obj\LibB.csproj.nuget.g.targets.
  Restore completed in 12.8 ms for C:\tmp\restoreRepro\LibC\LibC.csproj.
  Restore completed in 14.61 ms for C:\tmp\restoreRepro\LibB\LibB.csproj.
  Restoring packages for C:\tmp\restoreRepro\LibA\LibA.csproj...
  Restoring packages for C:\tmp\restoreRepro\LibC\LibC.csproj...
C:\Users\namc\.dotnet\x64\sdk\2.2.100\NuGet.targets(114,5): error : Cannot create a file when that file already exists [C:\tmp\restoreRepro\LibB\LibB.csproj]
  Generating MSBuild file C:\tmp\restoreRepro\LibA\obj\LibA.csproj.nuget.g.props.
  Generating MSBuild file C:\tmp\restoreRepro\LibA\obj\LibA.csproj.nuget.g.targets.
  Restore completed in 128.42 ms for C:\tmp\restoreRepro\LibC\LibC.csproj.
  Restore completed in 147.55 ms for C:\tmp\restoreRepro\LibA\LibA.csproj.

Other suggested things

This is a race condition, so it may not appear right away. But it is consistent. I get consistent repros like this:

for($i = 0; $i -lt 100; $i+=1) {
  gci -re */obj/**/* | % {rm $_ } # clean the last restore result
  dotnet msbuild build.proj -bl -t:Restore
  if ($LASTEXITCODE -ne 0){ break }
}

Verbose Logs

msbuild.binlog.zip

Sample Project

repro-parallel-restore.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    Functionality:RestorePriority:3Issues under consideration. With enough upvotes, will be reconsidered to be added to the backlog.Type:Bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions