mirror of
https://github.com/NixOS/nix.git
synced 2025-11-11 04:56:01 +01:00
Merge pull request #130 from DeterminateSystems/improve-nix-store-delete-errors
nix store delete: Show why deletion fails
This commit is contained in:
commit
e809a5626e
7 changed files with 62 additions and 36 deletions
|
|
@ -730,6 +730,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
options.action = (GCOptions::GCAction) readInt(conn.from);
|
options.action = (GCOptions::GCAction) readInt(conn.from);
|
||||||
options.pathsToDelete = WorkerProto::Serialise<StorePathSet>::read(*store, rconn);
|
options.pathsToDelete = WorkerProto::Serialise<StorePathSet>::read(*store, rconn);
|
||||||
conn.from >> options.ignoreLiveness >> options.maxFreed;
|
conn.from >> options.ignoreLiveness >> options.maxFreed;
|
||||||
|
options.censor = !trusted;
|
||||||
// obsolete fields
|
// obsolete fields
|
||||||
readInt(conn.from);
|
readInt(conn.from);
|
||||||
readInt(conn.from);
|
readInt(conn.from);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -458,13 +458,14 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||||
bool gcKeepOutputs = settings.gcKeepOutputs;
|
bool gcKeepOutputs = settings.gcKeepOutputs;
|
||||||
bool gcKeepDerivations = settings.gcKeepDerivations;
|
bool gcKeepDerivations = settings.gcKeepDerivations;
|
||||||
|
|
||||||
std::unordered_set<StorePath> roots, dead, alive;
|
Roots roots;
|
||||||
|
std::unordered_set<StorePath> dead, alive;
|
||||||
|
|
||||||
struct Shared
|
struct Shared
|
||||||
{
|
{
|
||||||
// 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.
|
||||||
|
|
@ -573,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
|
||||||
|
|
@ -612,19 +614,18 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||||
/* Find the roots. Since we've grabbed the GC lock, the set of
|
/* Find the roots. Since we've grabbed the GC lock, the set of
|
||||||
permanent roots cannot increase now. */
|
permanent roots cannot increase now. */
|
||||||
printInfo("finding garbage collector roots...");
|
printInfo("finding garbage collector roots...");
|
||||||
Roots rootMap;
|
|
||||||
if (!options.ignoreLiveness)
|
if (!options.ignoreLiveness)
|
||||||
findRootsNoTemp(rootMap, true);
|
findRootsNoTemp(roots, options.censor);
|
||||||
|
|
||||||
for (auto & i : rootMap) roots.insert(i.first);
|
|
||||||
|
|
||||||
/* 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, true);
|
Roots tempRoots;
|
||||||
for (auto & root : tempRoots) {
|
findTempRoots(tempRoots, options.censor);
|
||||||
_shared.lock()->tempRoots.insert(std::string(root.first.hashPart()));
|
for (auto & root : tempRoots)
|
||||||
roots.insert(root.first);
|
_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. */
|
||||||
|
|
@ -716,21 +717,35 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||||
} catch (InvalidPath &) { }
|
} catch (InvalidPath &) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (options.action == GCOptions::gcDeleteSpecific
|
||||||
|
&& !options.pathsToDelete.count(*path))
|
||||||
|
{
|
||||||
|
throw Error(
|
||||||
|
"Cannot delete path '%s' because it's referenced by path '%s'.",
|
||||||
|
printStorePath(start),
|
||||||
|
printStorePath(*path));
|
||||||
|
}
|
||||||
|
|
||||||
/* If this is a root, bail out. */
|
/* If this is a root, bail out. */
|
||||||
if (roots.count(*path)) {
|
if (auto i = roots.find(*path); i != roots.end()) {
|
||||||
|
if (options.action == GCOptions::gcDeleteSpecific)
|
||||||
|
throw Error(
|
||||||
|
"Cannot delete path '%s' because it's referenced by the GC root '%s'.",
|
||||||
|
printStorePath(start),
|
||||||
|
*i->second.begin());
|
||||||
debug("cannot delete '%s' because it's a root", printStorePath(*path));
|
debug("cannot delete '%s' because it's a root", printStorePath(*path));
|
||||||
return markAlive();
|
return markAlive();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.action == GCOptions::gcDeleteSpecific
|
|
||||||
&& !options.pathsToDelete.count(*path))
|
|
||||||
return;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
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()) {
|
||||||
debug("cannot delete '%s' because it's a temporary root", printStorePath(*path));
|
if (options.action == GCOptions::gcDeleteSpecific)
|
||||||
|
throw Error(
|
||||||
|
"Cannot delete path '%s' because it's in use by '%s'.",
|
||||||
|
printStorePath(start),
|
||||||
|
i->second);
|
||||||
return markAlive();
|
return markAlive();
|
||||||
}
|
}
|
||||||
shared->pending = hashPart;
|
shared->pending = hashPart;
|
||||||
|
|
@ -789,12 +804,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||||
|
|
||||||
for (auto & i : options.pathsToDelete) {
|
for (auto & i : options.pathsToDelete) {
|
||||||
deleteReferrersClosure(i);
|
deleteReferrersClosure(i);
|
||||||
if (!dead.count(i))
|
assert(dead.count(i));
|
||||||
throw Error(
|
|
||||||
"Cannot delete path '%1%' since it is still alive. "
|
|
||||||
"To find out why, use: "
|
|
||||||
"nix-store --query --roots and nix-store --query --referrers",
|
|
||||||
printStorePath(i));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (options.maxFreed > 0) {
|
} else if (options.maxFreed > 0) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -53,6 +56,12 @@ struct GCOptions
|
||||||
* Stop after at least `maxFreed` bytes have been freed.
|
* Stop after at least `maxFreed` bytes have been freed.
|
||||||
*/
|
*/
|
||||||
uint64_t maxFreed{std::numeric_limits<uint64_t>::max()};
|
uint64_t maxFreed{std::numeric_limits<uint64_t>::max()};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to hide potentially sensitive information about GC
|
||||||
|
* roots (such as PIDs).
|
||||||
|
*/
|
||||||
|
bool censor = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 path '"
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue