libstore: skip optimisation when GC removes a link concurrently - #15904
Merged
xokdvium merged 1 commit intoMay 26, 2026
Conversation
A concurrent garbage collection can remove an entry from the links directory between the moment optimisePath_ checks for it and the moment it creates the hard link. This produced "cannot create hard link: No such file or directory" and failed the whole build. Treat the vanished link as a benign race and skip optimising the path, since a later pass will dedup it. Guard both the lstat (now maybeLstat) and the create_hard_link call, mirroring the existing too_many_links and file_exists handling. Relates to NixOS#7273 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
kolmodin
pushed a commit
to kolmodin/nix
that referenced
this pull request
May 30, 2026
…oved-links libstore: skip optimisation when GC removes a link concurrently
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
nix store optimiseandauto-optimise-storecan abort a build with:https://github.com/cachix/devenv/actions/runs/26294238442/job/77408282471#step:6:2534
when a garbage collection runs at the same time.
The race
In
optimisePath_, when a file with matching contents is already indexed, the flow is:/nix/store/.links/<hash>exists,lstatit,create_hard_link(linkPath, tempLink)and thenrename(tempLink, path).The links-directory entry can have a link count of 1 (the original store path that created it was already collected, but
removeUnusedLinkshas not run yet). A concurrent GC'sremoveUnusedLinksthen deletes that entry in the window between the existence check andcreate_hard_link. The source is gone, socreate_hard_linkfails withENOENT, and the exception propagates and fails the whole operation.This is the
No such file or directoryvariant of #7273. The variant reported there isFile exists, which was addressed by switching tomakeTempPathin #14676; theENOENTwindow described above is still unhandled.Fix
Store optimisation is best effort, so a link disappearing underneath us is benign: skip optimising this path and let a later pass dedup it. This mirrors how the existing
too_many_linksandfile_existscases already shrug and return.Two windows are guarded:
lstat(linkPath)becomesmaybeLstat(linkPath), returning early if the entry vanished before the inode comparison.create_hard_linkcatch now returns onstd::errc::no_such_file_or_directory.Context
This is a race condition. There is no hook to deterministically remove
linkPathmid call, so a non flaky regression test is not feasible, consistent with #7273 which also has none.