From 5388944e8d1ca61e23d42a6a0769b925f099f4e1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 25 Jan 2010 16:04:32 +0000 Subject: * Make the garbage collector do the right thing when `gc-keep-outputs' is enabled by not depending on the deriver. --- src/libstore/gc.cc | 87 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 18 deletions(-) (limited to 'src/libstore/gc.cc') diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index fc9791023a59..baa6826fe2a5 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -454,7 +454,12 @@ struct LocalStore::GCState PathSet busy; bool gcKeepOutputs; bool gcKeepDerivations; - GCState(GCResults & results_) : results(results_) + + bool drvsIndexed; + typedef std::multimap DrvsByName; + DrvsByName drvsByName; // derivation paths hashed by name attribute + + GCState(GCResults & results_) : results(results_), drvsIndexed(false) { } }; @@ -474,6 +479,42 @@ bool LocalStore::isActiveTempFile(const GCState & state, && state.tempRoots.find(string(path, 0, path.size() - suffix.size())) != state.tempRoots.end(); } + +/* Return all the derivations in the Nix store that have `path' as an + output. This function assumes that derivations have the same name + as their outputs. */ +PathSet LocalStore::findDerivers(GCState & state, const Path & path) +{ + PathSet derivers; + + Path deriver = queryDeriver(path); + if (deriver != "") derivers.insert(deriver); + + if (!state.drvsIndexed) { + Paths entries = readDirectory(nixStore); + foreach (Paths::iterator, i, entries) + if (isDerivation(*i)) + state.drvsByName.insert(std::pair( + getNameOfStorePath(*i), nixStore + "/" + *i)); + state.drvsIndexed = true; + } + + string name = getNameOfStorePath(path); + + // Urgh, I should have used Haskell... + std::pair range = + state.drvsByName.equal_range(name); + + for (GCState::DrvsByName::iterator i = range.first; i != range.second; ++i) + if (isValidPath(i->second)) { + Derivation drv = derivationFromPath(i->second); + foreach (DerivationOutputs::iterator, j, drv.outputs) + if (j->second.path == path) derivers.insert(i->second); + } + + return derivers; +} + bool LocalStore::tryToDelete(GCState & state, const Path & path) { @@ -519,24 +560,34 @@ bool LocalStore::tryToDelete(GCState & state, const Path & path) if (!pathExists(path)) return true; /* If gc-keep-outputs is set, then don't delete this path if - its deriver is not garbage. !!! This is somewhat buggy, - since there might be multiple derivers, but the database - only stores one. */ + its deriver is not garbage. !!! Nix does not reliably + store derivers, so we have to look at all derivations to + determine which of them derive `path'. Since this makes + the garbage collector very slow to start on large Nix + stores, here we just look for all derivations that have the + same name as `path' (where the name is the part of the + filename after the hash, i.e. the `name' attribute of the + derivation). This is somewhat hacky: currently, the + deriver of a path always has the same name as the output, + but this might change in the future. */ if (state.gcKeepOutputs) { - Path deriver = queryDeriver(path); - /* Break an infinite recursion if gc-keep-derivations and - gc-keep-outputs are both set by tentatively assuming - that this path is garbage. This is a safe assumption - because at this point, the only thing that can prevent - it from being garbage is the deriver. Since - tryToDelete() works "upwards" through the dependency - graph, it won't encouter this path except in the call - to tryToDelete() in the gc-keep-derivation branch. */ - state.deleted.insert(path); - if (deriver != "" && !tryToDelete(state, deriver)) { - state.deleted.erase(path); - printMsg(lvlDebug, format("cannot delete `%1%' because its deriver is alive") % path); - goto isLive; + PathSet derivers = findDerivers(state, path); + foreach (PathSet::iterator, deriver, derivers) { + /* Break an infinite recursion if gc-keep-derivations + and gc-keep-outputs are both set by tentatively + assuming that this path is garbage. This is a safe + assumption because at this point, the only thing + that can prevent it from being garbage is the + deriver. Since tryToDelete() works "upwards" + through the dependency graph, it won't encouter + this path except in the call to tryToDelete() in + the gc-keep-derivation branch. */ + state.deleted.insert(path); + if (!tryToDelete(state, *deriver)) { + state.deleted.erase(path); + printMsg(lvlDebug, format("cannot delete `%1%' because its deriver `%2%' is alive") % path % *deriver); + goto isLive; + } } } } -- cgit 1.4.1