question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Usage of hard or symbolic linking leads to NuGet cache corruption

See original GitHub issue

Issue Description

MSBuild can use hard or symbolic links to avoid excessive file copies on disk. It is a good method to speed-up builds but at the same time, it is very easy to silently corrupt the NuGet cache. I have seen that this issue has been mentioned several times already, but it was never explained how and when exactly the NuGet cache gets corrupted:

Related issues:

Steps to Reproduce

  1. Build an executable project with hard or symbolic links enabled
  2. Update the version of referenced NuGet package
  3. Build the application again, but this time, without using hard/symbolic links. MSBuild will try to update dependencies in the application’s output folder. Sadly, instead of replacing existing links, it replaces actual files in NuGet cache (thus corrupting it).
  • hard-links
dotnet nuget locals --clear all
dotnet new console
dotnet add package newtonsoft.json -v 13.0.1
dotnet build /p:CreateHardLinksForCopyLocalIfPossible=true
dotnet add package newtonsoft.json -v 13.0.2
dotnet build
  • symbolic-links
dotnet nuget locals --clear all
dotnet new console
dotnet add package newtonsoft.json -v 13.0.1
dotnet build /p:CreateSymbolicLinksForCopyLocalIfPossible=true
dotnet add package newtonsoft.json -v 13.0.2
dotnet build

In both cases file newtonsoft.json\13.0.1\lib\netstandard2.0\Newtonsoft.Json.dll is silently replaced with newtonsoft.json\13.0.2\lib\net6.0\Newtonsoft.Json.dll:

Expected Behavior

Files in the NuGet cache remain untouched.

Actual Behavior

Files in a NuGet package are silently replaced with files from another version.

Analysis

Both Windows and Linux systems are affected, also it doesn’t matter whether hard or symbolic links are used. The problem is that File.Copy operation, instead of replacing the link, replaces the file that the link is linking to. To safely replace a link with a different file or link, File.Delete needs to be called first. Unfortunately, MSBuild calls File.Delete only when the usage of hard or symbolic links is requested. When the build doesn’t use hard or symbolic links, then the File.Delete is not called.

Versions & Configurations

Issue Analytics

  • State:closed
  • Created 9 months ago
  • Comments:15 (14 by maintainers)

github_iconTop GitHub Comments

1reaction
JeremyKuhnecommented, May 5, 2023

GetFullPath is still significantly expensive, right? Although perhaps better than before, it will still hit the disk in some cases, right?

@danmoseley On Windows it typically is just a string parsing/manipulation routine. It will get the current directory and possibly environment variables (for drive relative paths), but those are not disk based. There is some sort of check regarding legacy device names PRN, CON, etc. but I don’t recall precisely what it does off the top of my head (check for device availability for serial ports maybe?).

On .NET Framework (and early .NET Core versions) Path.GetFullPath was significantly more expensive as we would try to parse the path and check for validity up front (before passing it to the OS). In 4.6.2 I ported back some of the changes which improves things dramatically.

0reactions
Forgindcommented, May 5, 2023

@Forgind The alternative I had was to replace the string.Equals on the specified paths in DoCopyIfNecessary with a PathsAreIdentical. Is Path.GetFullPath that expensive?

I see that is what you did in #8685.

Right; I couldn’t think of another way to validate that the paths really are the same, but I moved the check a little earlier and tried to unify some of the computation to minimize the impact in most cases. The only case where I think perf regresses is if you try to create a hard/symlink, and that’s minor, per JeremyKuhne’s comment, unless someone is using an older runtime. I undrafted the PR; from internal discussion, it sounds like we’re leaning towards taking it, so that should hopefully resolve the writing-through-symlink problem without reintroducing the copy-onto-self-means-delete problem.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Nuget corrupt packages - msbuild
When the build runs I get failures when nuget downloads packages, for example: error : Unable to read package from path 'bootstrap.3.3.2.nupkg' ...
Read more >
Reflinks vs Symlinks vs Hard Links, and How They Can ...
The hard link and symbolic link approaches are big wins because of their speed, but doing so runs the risk of polluting the...
Read more >
nuget - What do Yellow Warning Triangles mean on ...
To prevent NuGet from restoring packages during build, open the Visual Studio Options dialog, click on the Package Manager node and uncheck ' ......
Read more >
Large Dataset Optimization | Data Version Control
Hard /soft links optimize speed and space in the file system, but may break ... as hard/symlinks, but don't carry any risk of...
Read more >
Adding a reference to an assembly expands symlinks in ...
When this is set, Paket creates a symbolic link into the user's NuGet cache. If I then add a reference to an assembly...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found