Make the canReachRoots() traversal non-recursive

This commit is contained in:
Eelco Dolstra 2021-10-14 12:31:21 +02:00
parent 09b14ea97a
commit eab934cb2a
3 changed files with 115 additions and 111 deletions

View file

@ -499,75 +499,6 @@ void LocalStore::deleteFromStore(GCState & state, std::string_view baseName)
} }
bool LocalStore::canReachRoot(
GCState & state,
StorePathSet & visited,
const StorePath & path)
{
checkInterrupt();
//Activity act(*logger, lvlDebug, format("considering whether to delete '%1%'") % path);
if (state.options.action == GCOptions::gcDeleteSpecific
&& !state.options.pathsToDelete.count(path))
return true;
if (!visited.insert(path).second) return false;
if (state.alive.count(path)) return true;
if (state.dead.count(path)) return false;
if (state.roots.count(path)) {
debug("cannot delete '%s' because it's a root", printStorePath(path));
state.alive.insert(path);
return true;
}
if (isValidPath(path)) {
StorePathSet incoming;
/* Don't delete this path if any of its referrers are alive. */
queryReferrers(path, incoming);
/* If keep-derivations is set and this is a derivation, then
don't delete the derivation if any of the outputs are
alive. */
if (state.gcKeepDerivations && path.isDerivation()) {
for (auto & [name, maybeOutPath] : queryPartialDerivationOutputMap(path))
if (maybeOutPath &&
isValidPath(*maybeOutPath) &&
queryPathInfo(*maybeOutPath)->deriver == path)
incoming.insert(*maybeOutPath);
}
/* If keep-outputs is set, then don't delete this path if
there are derivers of this path that are not garbage. */
if (state.gcKeepOutputs) {
auto derivers = queryValidDerivers(path);
for (auto & i : derivers)
incoming.insert(i);
}
for (auto & i : incoming)
if (i != path && canReachRoot(state, visited, i)) {
state.alive.insert(path);
return true;
}
}
{
auto hashPart = std::string(path.hashPart());
auto shared(state.shared.lock());
if (shared->tempRoots.count(hashPart))
return true;
shared->pending = hashPart;
}
return false;
}
/* Unlink all files in /nix/store/.links that have a link count of 1, /* Unlink all files in /nix/store/.links that have a link count of 1,
which indicates that there are no other links and so they can be which indicates that there are no other links and so they can be
safely deleted. FIXME: race condition with optimisePath(): we safely deleted. FIXME: race condition with optimisePath(): we
@ -746,28 +677,115 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
state.roots.insert(root.first); state.roots.insert(root.first);
} }
/* Now either delete all garbage paths, or just the specified /* Helper function that visits all paths reachable from `start`
paths (for gcDeleteSpecific). */ via the referrers edges and optionally derivers and derivation
output edges. If any of those paths is a root, then we cannot
delete this path. */
auto deleteReferrersClosure = [&](const StorePath & start) {
StorePathSet visited;
std::queue<StorePath> todo;
/* 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();
});
auto enqueue = [&](const StorePath & path) {
if (visited.insert(path).second)
todo.push(path);
};
enqueue(start);
while (auto path = pop(todo)) {
checkInterrupt();
/* Bail out if we've previously discovered that this path
is alive. */
if (state.alive.count(*path)) {
state.alive.insert(start);
return;
}
/* If we've previously deleted this path, we don't have to
handle it again. */
if (state.dead.count(*path)) continue;
/* If this is a root, bail out. */
if (state.roots.count(*path)) {
debug("cannot delete '%s' because it's a root", printStorePath(*path));
state.alive.insert(*path);
state.alive.insert(start);
return;
}
if (state.options.action == GCOptions::gcDeleteSpecific
&& !state.options.pathsToDelete.count(*path))
return;
{
auto hashPart = std::string(path->hashPart());
auto shared(state.shared.lock());
if (shared->tempRoots.count(hashPart)) {
debug("cannot delete '%s' because it's a temporary root", printStorePath(*path));
state.alive.insert(*path);
state.alive.insert(start);
return;
}
shared->pending = hashPart;
}
if (isValidPath(*path)) {
/* Visit the referrers of this path. */
StorePathSet referrers;
queryReferrers(*path, referrers);
for (auto & p : referrers)
enqueue(p);
/* If keep-derivations is set and this is a
derivation, then visit the derivation outputs. */
if (state.gcKeepDerivations && path->isDerivation()) {
for (auto & [name, maybeOutPath] : queryPartialDerivationOutputMap(*path))
if (maybeOutPath &&
isValidPath(*maybeOutPath) &&
queryPathInfo(*maybeOutPath)->deriver == *path)
enqueue(*maybeOutPath);
}
/* If keep-outputs is set, then visit the derivers. */
if (state.gcKeepOutputs) {
auto derivers = queryValidDerivers(*path);
for (auto & i : derivers)
enqueue(i);
}
}
}
for (auto & path : topoSortPaths(visited)) {
if (!state.dead.insert(path).second) continue;
if (state.shouldDelete) {
invalidatePathChecked(path);
deleteFromStore(state, path.to_string());
}
}
};
/* Either delete all garbage paths, or just the specified
paths (for gcDeleteSpecific). */
if (options.action == GCOptions::gcDeleteSpecific) { if (options.action == GCOptions::gcDeleteSpecific) {
for (auto & i : options.pathsToDelete) { for (auto & i : options.pathsToDelete) {
StorePathSet visited; deleteReferrersClosure(i);
if (!state.dead.count(i))
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) {
@ -792,29 +810,9 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
string name = dirent->d_name; string name = dirent->d_name;
if (name == "." || name == ".." || name == linksName) continue; if (name == "." || name == ".." || name == linksName) continue;
if (auto storePath = maybeParseStorePath(storeDir + "/" + name)) { if (auto storePath = maybeParseStorePath(storeDir + "/" + name))
StorePathSet visited; deleteReferrersClosure(*storePath);
else
/* 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
deleteFromStore(state, name); deleteFromStore(state, name);
} }

View file

@ -240,11 +240,6 @@ private:
struct GCState; struct GCState;
bool canReachRoot(
GCState & state,
StorePathSet & visited,
const StorePath & path);
void deleteFromStore(GCState & state, std::string_view baseName); void deleteFromStore(GCState & state, std::string_view baseName);
void findRoots(const Path & path, unsigned char type, Roots & roots); void findRoots(const Path & path, unsigned char type, Roots & roots);

View file

@ -523,6 +523,17 @@ std::optional<typename T::value_type> remove_begin(T & c)
} }
/* Remove and return the first item from a container. */
template <class T>
std::optional<typename T::value_type> pop(T & c)
{
if (c.empty()) return {};
auto v = std::move(c.front());
c.pop();
return v;
}
template<typename T> template<typename T>
class Callback; class Callback;