Fix GC when there are cycles in the referrers graph

(where "referrers" includes the reverse of derivation outputs and
derivers). Now we do a full traversal to look if we can reach any
root. If not, all paths reached can be deleted.
This commit is contained in:
Eelco Dolstra 2021-10-08 16:58:19 +02:00
parent e31a48366f
commit 35c98a59c5
2 changed files with 45 additions and 34 deletions

View file

@ -499,34 +499,29 @@ void LocalStore::deleteFromStore(GCState & state, std::string_view baseName)
} }
bool LocalStore::tryToDelete( bool LocalStore::canReachRoot(
GCState & state, GCState & state,
StorePathSet & visited, StorePathSet & visited,
const StorePath & path, const StorePath & path)
bool recursive)
{ {
checkInterrupt(); checkInterrupt();
//Activity act(*logger, lvlDebug, format("considering whether to delete '%1%'") % path); //Activity act(*logger, lvlDebug, format("considering whether to delete '%1%'") % path);
/* Wake up any client waiting for deletion of this path to if (state.options.action == GCOptions::gcDeleteSpecific
finish. */ && !state.options.pathsToDelete.count(path))
Finally releasePending([&]() { return true;
auto shared(state.shared.lock());
shared->pending.reset();
state.wakeup.notify_all();
});
if (!visited.insert(path).second) return true; if (!visited.insert(path).second) return false;
if (state.alive.count(path)) return false; if (state.alive.count(path)) return true;
if (state.dead.count(path)) return true; if (state.dead.count(path)) return false;
if (state.roots.count(path)) { if (state.roots.count(path)) {
debug("cannot delete '%s' because it's a root", printStorePath(path)); debug("cannot delete '%s' because it's a root", printStorePath(path));
state.alive.insert(path); state.alive.insert(path);
return false; return true;
} }
if (isValidPath(path)) { if (isValidPath(path)) {
@ -555,12 +550,9 @@ bool LocalStore::tryToDelete(
} }
for (auto & i : incoming) for (auto & i : incoming)
if (i != path if (i != path && canReachRoot(state, visited, i)) {
&& (recursive || state.options.pathsToDelete.count(i))
&& !tryToDelete(state, visited, i, recursive))
{
state.alive.insert(path); state.alive.insert(path);
return false; return true;
} }
} }
@ -568,18 +560,11 @@ bool LocalStore::tryToDelete(
auto hashPart = std::string(path.hashPart()); auto hashPart = std::string(path.hashPart());
auto shared(state.shared.lock()); auto shared(state.shared.lock());
if (shared->tempRoots.count(hashPart)) if (shared->tempRoots.count(hashPart))
return false; return true;
shared->pending = hashPart; shared->pending = hashPart;
} }
state.dead.insert(path); return false;
if (state.shouldDelete) {
invalidatePathChecked(path);
deleteFromStore(state, path.to_string());
}
return true;
} }
@ -628,7 +613,6 @@ void LocalStore::removeUnusedLinks(const GCState & state)
} }
void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
{ {
GCState state(options, results); GCState state(options, results);
@ -769,12 +753,21 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
for (auto & i : options.pathsToDelete) { for (auto & i : options.pathsToDelete) {
StorePathSet visited; StorePathSet visited;
if (!tryToDelete(state, visited, i, false))
if (canReachRoot(state, visited, i))
throw Error( throw Error(
"cannot delete path '%1%' since it is still alive. " "cannot delete path '%1%' since it is still alive. "
"To find out why, use: " "To find out why, use: "
"nix-store --query --roots", "nix-store --query --roots",
printStorePath(i)); printStorePath(i));
auto sorted = topoSortPaths(visited);
for (auto & path : sorted) {
if (state.dead.count(path)) continue;
invalidatePathChecked(path);
deleteFromStore(state, path.to_string());
state.dead.insert(path);
}
} }
} else if (options.maxFreed > 0) { } else if (options.maxFreed > 0) {
@ -801,7 +794,26 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
if (auto storePath = maybeParseStorePath(storeDir + "/" + name)) { if (auto storePath = maybeParseStorePath(storeDir + "/" + name)) {
StorePathSet visited; StorePathSet visited;
tryToDelete(state, visited, *storePath, true);
/* Wake up any GC client waiting for deletion of
the paths in 'visited' to finish. */
Finally releasePending([&]() {
auto shared(state.shared.lock());
shared->pending.reset();
state.wakeup.notify_all();
});
if (!canReachRoot(state, visited, *storePath)) {
auto sorted = topoSortPaths(visited);
for (auto & path : sorted) {
if (state.dead.count(path)) continue;
if (state.shouldDelete) {
invalidatePathChecked(path);
deleteFromStore(state, path.to_string());
}
state.dead.insert(path);
}
}
} else } else
deleteFromStore(state, name); deleteFromStore(state, name);

View file

@ -240,11 +240,10 @@ private:
struct GCState; struct GCState;
bool tryToDelete( bool canReachRoot(
GCState & state, GCState & state,
StorePathSet & visited, StorePathSet & visited,
const StorePath & path, const StorePath & path);
bool recursive);
void deleteFromStore(GCState & state, std::string_view baseName); void deleteFromStore(GCState & state, std::string_view baseName);