1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-10 12:36:01 +01:00

Show which PID is causing a temp root

Example:

  error: Cannot delete path '/nix/store/klyng5rpdkwi5kbxkncy4gjwb490dlhb-foo.drv' because it's in use by Nix process '{nix-process:3605324}'.
This commit is contained in:
Eelco Dolstra 2025-06-27 16:50:48 +02:00
parent cae732f7a1
commit 31b00218fe
6 changed files with 35 additions and 20 deletions

View file

@ -208,7 +208,7 @@ void LocalStore::findTempRoots(Roots & tempRoots, bool censor)
while ((end = contents.find((char) 0, pos)) != std::string::npos) { while ((end = contents.find((char) 0, pos)) != std::string::npos) {
Path root(contents, pos, end - pos); Path root(contents, pos, end - pos);
debug("got temporary root '%s'", root); debug("got temporary root '%s'", root);
tempRoots[parseStorePath(root)].emplace(censor ? censored : fmt("{temp:%d}", pid)); tempRoots[parseStorePath(root)].emplace(censor ? censored : fmt("{nix-process:%d}", pid));
pos = end + 1; pos = end + 1;
} }
} }
@ -465,7 +465,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
{ {
// The temp roots only store the hash part to make it easier to // The temp roots only store the hash part to make it easier to
// ignore suffixes like '.lock', '.chroot' and '.check'. // ignore suffixes like '.lock', '.chroot' and '.check'.
std::unordered_set<std::string> tempRoots; std::unordered_map<std::string, GcRootInfo> tempRoots;
// Hash part of the store path currently being deleted, if // Hash part of the store path currently being deleted, if
// any. // any.
@ -574,7 +574,8 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
debug("got new GC root '%s'", path); debug("got new GC root '%s'", path);
auto hashPart = std::string(storePath->hashPart()); auto hashPart = std::string(storePath->hashPart());
auto shared(_shared.lock()); auto shared(_shared.lock());
shared->tempRoots.insert(hashPart); // FIXME: could get the PID from the socket.
shared->tempRoots.insert_or_assign(hashPart, "{nix-process:unknown}");
/* If this path is currently being /* If this path is currently being
deleted, then we have to wait until deleted, then we have to wait until
deletion is finished to ensure that deletion is finished to ensure that
@ -618,10 +619,14 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
/* Read the temporary roots created before we acquired the global /* Read the temporary roots created before we acquired the global
GC root. Any new roots will be sent to our socket. */ GC root. Any new roots will be sent to our socket. */
Roots tempRoots; {
findTempRoots(tempRoots, options.censor); Roots tempRoots;
for (auto & root : tempRoots) findTempRoots(tempRoots, options.censor);
_shared.lock()->tempRoots.insert(std::string(root.first.hashPart())); for (auto & root : tempRoots)
_shared.lock()->tempRoots.insert_or_assign(
std::string(root.first.hashPart()),
*root.second.begin());
}
/* Synchronisation point for testing, see tests/functional/gc-non-blocking.sh. */ /* Synchronisation point for testing, see tests/functional/gc-non-blocking.sh. */
if (auto p = getEnv("_NIX_TEST_GC_SYNC_2")) if (auto p = getEnv("_NIX_TEST_GC_SYNC_2"))
@ -735,11 +740,12 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
{ {
auto hashPart = std::string(path->hashPart()); auto hashPart = std::string(path->hashPart());
auto shared(_shared.lock()); auto shared(_shared.lock());
if (shared->tempRoots.count(hashPart)) { if (auto i = shared->tempRoots.find(hashPart); i != shared->tempRoots.end()) {
if (options.action == GCOptions::gcDeleteSpecific) if (options.action == GCOptions::gcDeleteSpecific)
throw Error( throw Error(
"Cannot delete path '%s' because it's in use by a Nix process.", "Cannot delete path '%s' because it's in use by '%s'.",
printStorePath(start)); printStorePath(start),
i->second);
return markAlive(); return markAlive();
} }
shared->pending = hashPart; shared->pending = hashPart;

View file

@ -7,8 +7,11 @@
namespace nix { namespace nix {
// FIXME: should turn this into an std::variant to represent the
// several root types.
using GcRootInfo = std::string;
typedef std::unordered_map<StorePath, std::unordered_set<std::string>> Roots; typedef std::unordered_map<StorePath, std::unordered_set<GcRootInfo>> Roots;
struct GCOptions struct GCOptions

View file

@ -9,6 +9,7 @@ mkDerivation {
cat > $out/program <<EOF cat > $out/program <<EOF
#! ${shell} #! ${shell}
echo x > \$TEST_ROOT/fifo
sleep 10000 sleep 10000
EOF EOF

View file

@ -21,11 +21,16 @@ nix-env -p "$profiles/test" -f ./gc-runtime.nix -i gc-runtime
outPath=$(nix-env -p "$profiles/test" -q --no-name --out-path gc-runtime) outPath=$(nix-env -p "$profiles/test" -q --no-name --out-path gc-runtime)
echo "$outPath" echo "$outPath"
fifo="$TEST_ROOT/fifo"
mkfifo "$fifo"
echo "backgrounding program..." echo "backgrounding program..."
"$profiles"/test/program & "$profiles"/test/program "$fifo" &
sleep 2 # hack - wait for the program to get started
child=$! child=$!
echo PID=$child echo PID=$child
cat "$fifo"
expectStderr 1 nix-store --delete "$outPath" | grepQuiet "Cannot delete path.*because it's referenced by the GC root '/proc/"
nix-env -p "$profiles/test" -e gc-runtime nix-env -p "$profiles/test" -e gc-runtime
nix-env -p "$profiles/test" --delete-generations old nix-env -p "$profiles/test" --delete-generations old

View file

@ -23,10 +23,10 @@ if nix-store --gc --print-dead | grep -E "$outPath"$; then false; fi
nix-store --gc --print-dead nix-store --gc --print-dead
inUse=$(readLink "$outPath/reference-to-input-2") inUse=$(readLink "$outPath/reference-to-input-2")
if nix-store --delete "$inUse"; then false; fi expectStderr 1 nix-store --delete "$inUse" | grepQuiet "Cannot delete path.*because it's referenced by the GC root "
test -e "$inUse" test -e "$inUse"
if nix-store --delete "$outPath"; then false; fi expectStderr 1 nix-store --delete "$outPath" | grepQuiet "Cannot delete path.*because it's referenced by the GC root "
test -e "$outPath" test -e "$outPath"
for i in "$NIX_STORE_DIR"/*; do for i in "$NIX_STORE_DIR"/*; do

View file

@ -22,14 +22,14 @@ input2=$(nix-build ../hermetic.nix --no-out-link --arg busybox "$busybox" --arg
input3=$(nix-build ../hermetic.nix --no-out-link --arg busybox "$busybox" --arg withFinalRefs true --arg seed 2 -A passthru.input3 -j0) input3=$(nix-build ../hermetic.nix --no-out-link --arg busybox "$busybox" --arg withFinalRefs true --arg seed 2 -A passthru.input3 -j0)
# Can't delete because referenced # Can't delete because referenced
expectStderr 1 nix-store --delete $input1 | grepQuiet "Cannot delete path" expectStderr 1 nix-store --delete $input1 | grepQuiet "Cannot delete path.*because it's referenced by path"
expectStderr 1 nix-store --delete $input2 | grepQuiet "Cannot delete path" expectStderr 1 nix-store --delete $input2 | grepQuiet "Cannot delete path.*because it's referenced by path"
expectStderr 1 nix-store --delete $input3 | grepQuiet "Cannot delete path" expectStderr 1 nix-store --delete $input3 | grepQuiet "Cannot delete path.*because it's referenced by path"
# These same paths are referenced in the lower layer (by the seed 1 # These same paths are referenced in the lower layer (by the seed 1
# build done in `initLowerStore`). # build done in `initLowerStore`).
expectStderr 1 nix-store --store "$storeA" --delete $input2 | grepQuiet "Cannot delete path" expectStderr 1 nix-store --store "$storeA" --delete $input2 | grepQuiet "Cannot delete path.*because it's referenced by path"
expectStderr 1 nix-store --store "$storeA" --delete $input3 | grepQuiet "Cannot delete path" expectStderr 1 nix-store --store "$storeA" --delete $input3 | grepQuiet "Cannot delete path.*because it's referenced by path"
# Can delete # Can delete
nix-store --delete $hermetic nix-store --delete $hermetic