* Make the garbage collector do the right thing when `gc-keep-outputs'

is enabled by not depending on the deriver.
This commit is contained in:
Eelco Dolstra 2010-01-25 16:04:32 +00:00
parent f0c0277970
commit 5388944e8d
4 changed files with 90 additions and 18 deletions

View File

@ -454,7 +454,12 @@ struct LocalStore::GCState
PathSet busy; PathSet busy;
bool gcKeepOutputs; bool gcKeepOutputs;
bool gcKeepDerivations; bool gcKeepDerivations;
GCState(GCResults & results_) : results(results_)
bool drvsIndexed;
typedef std::multimap<string, Path> 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(); && 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<string, Path>(
getNameOfStorePath(*i), nixStore + "/" + *i));
state.drvsIndexed = true;
}
string name = getNameOfStorePath(path);
// Urgh, I should have used Haskell...
std::pair<GCState::DrvsByName::iterator, GCState::DrvsByName::iterator> 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) 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 (!pathExists(path)) return true;
/* If gc-keep-outputs is set, then don't delete this path if /* If gc-keep-outputs is set, then don't delete this path if
its deriver is not garbage. !!! This is somewhat buggy, its deriver is not garbage. !!! Nix does not reliably
since there might be multiple derivers, but the database store derivers, so we have to look at all derivations to
only stores one. */ 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) { if (state.gcKeepOutputs) {
Path deriver = queryDeriver(path); PathSet derivers = findDerivers(state, path);
/* Break an infinite recursion if gc-keep-derivations and foreach (PathSet::iterator, deriver, derivers) {
gc-keep-outputs are both set by tentatively assuming /* Break an infinite recursion if gc-keep-derivations
that this path is garbage. This is a safe assumption and gc-keep-outputs are both set by tentatively
because at this point, the only thing that can prevent assuming that this path is garbage. This is a safe
it from being garbage is the deriver. Since assumption because at this point, the only thing
tryToDelete() works "upwards" through the dependency that can prevent it from being garbage is the
graph, it won't encouter this path except in the call deriver. Since tryToDelete() works "upwards"
to tryToDelete() in the gc-keep-derivation branch. */ through the dependency graph, it won't encouter
state.deleted.insert(path); this path except in the call to tryToDelete() in
if (deriver != "" && !tryToDelete(state, deriver)) { the gc-keep-derivation branch. */
state.deleted.erase(path); state.deleted.insert(path);
printMsg(lvlDebug, format("cannot delete `%1%' because its deriver is alive") % path); if (!tryToDelete(state, *deriver)) {
goto isLive; state.deleted.erase(path);
printMsg(lvlDebug, format("cannot delete `%1%' because its deriver `%2%' is alive") % path % *deriver);
goto isLive;
}
} }
} }
} }

View File

@ -178,6 +178,8 @@ private:
bool tryToDelete(GCState & state, const Path & path); bool tryToDelete(GCState & state, const Path & path);
PathSet findDerivers(GCState & state, const Path & path);
bool isActiveTempFile(const GCState & state, bool isActiveTempFile(const GCState & state,
const Path & path, const string & suffix); const Path & path, const string & suffix);

View File

@ -1,6 +1,7 @@
#include "store-api.hh" #include "store-api.hh"
#include "globals.hh" #include "globals.hh"
#include "util.hh" #include "util.hh"
#include "derivations.hh"
#include <limits.h> #include <limits.h>
@ -52,6 +53,18 @@ Path toStorePath(const Path & path)
} }
string getNameOfStorePath(const Path & path)
{
Path::size_type slash = path.rfind('/');
string p = slash == Path::npos ? path : string(path, slash + 1);
Path::size_type dash = p.find('-');
assert(dash != Path::npos);
string p2 = string(p, dash + 1);
if (isDerivation(p2)) p2 = string(p2, 0, p2.size() - 4);
return p2;
}
Path followLinksToStore(const Path & _path) Path followLinksToStore(const Path & _path)
{ {
Path path = absPath(_path); Path path = absPath(_path);

View File

@ -243,6 +243,12 @@ void checkStoreName(const string & name);
Path toStorePath(const Path & path); Path toStorePath(const Path & path);
/* Get the "name" part of a store path, that is, the part after the
hash and the dash, and with any ".drv" suffix removed
(e.g. /nix/store/<hash>-foo-1.2.3.drv => foo-1.2.3). */
string getNameOfStorePath(const Path & path);
/* Follow symlinks until we end up with a path in the Nix store. */ /* Follow symlinks until we end up with a path in the Nix store. */
Path followLinksToStore(const Path & path); Path followLinksToStore(const Path & path);