* Garbage collector: don't do a complete topological sort of the Nix
store under the reference relation, since that means that the garbage collector will need a long time to start deleting paths. Instead just delete the referrers of a path first.
This commit is contained in:
parent
30c9f909b2
commit
826b271d9a
2 changed files with 87 additions and 74 deletions
|
@ -435,6 +435,83 @@ Paths topoSortPaths(const PathSet & paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LocalStore::tryToDelete(GCAction action, const PathSet & livePaths,
|
||||||
|
const PathSet & tempRootsClosed, PathSet & done, PathSet & deleted,
|
||||||
|
const Path & path, unsigned long long & bytesFreed)
|
||||||
|
{
|
||||||
|
if (done.find(path) != done.end()) return;
|
||||||
|
done.insert(path);
|
||||||
|
|
||||||
|
debug(format("considering deletion of `%1%'") % path);
|
||||||
|
|
||||||
|
if (livePaths.find(path) != livePaths.end()) {
|
||||||
|
if (action == gcDeleteSpecific)
|
||||||
|
throw Error(format("cannot delete path `%1%' since it is still alive") % path);
|
||||||
|
debug(format("live path `%1%'") % path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tempRootsClosed.find(path) != tempRootsClosed.end()) {
|
||||||
|
debug(format("temporary root `%1%'") % path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delete all the referrers first. They must be garbage too,
|
||||||
|
since if they were in the closure of some live path, then this
|
||||||
|
path would also be in the closure. Note that
|
||||||
|
deleteFromStore() below still makes sure that the referrer set
|
||||||
|
has become empty, just in case. */
|
||||||
|
PathSet referrers;
|
||||||
|
if (store->isValidPath(path))
|
||||||
|
queryReferrers(path, referrers);
|
||||||
|
foreach (PathSet::iterator, i, referrers)
|
||||||
|
if (*i != path)
|
||||||
|
tryToDelete(action, livePaths, tempRootsClosed, done, deleted, *i, bytesFreed);
|
||||||
|
|
||||||
|
debug(format("dead path `%1%'") % path);
|
||||||
|
deleted.insert(path);
|
||||||
|
|
||||||
|
/* If just returning the set of dead paths, we also return the
|
||||||
|
space that would be freed if we deleted them. */
|
||||||
|
if (action == gcReturnDead) {
|
||||||
|
bytesFreed += computePathSize(path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef __CYGWIN__
|
||||||
|
AutoCloseFD fdLock;
|
||||||
|
|
||||||
|
/* Only delete a lock file if we can acquire a write lock on it.
|
||||||
|
That means that it's either stale, or the process that created
|
||||||
|
it hasn't locked it yet. In the latter case the other process
|
||||||
|
will detect that we deleted the lock, and retry (see
|
||||||
|
pathlocks.cc). */
|
||||||
|
if (path.size() >= 5 && string(path, path.size() - 5) == ".lock") {
|
||||||
|
fdLock = openLockFile(path, false);
|
||||||
|
if (fdLock != -1 && !lockFile(fdLock, ltWrite, false)) {
|
||||||
|
debug(format("skipping active lock `%1%'") % path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!pathExists(path)) return;
|
||||||
|
|
||||||
|
printMsg(lvlInfo, format("deleting `%1%'") % path);
|
||||||
|
|
||||||
|
/* Okay, it's safe to delete. */
|
||||||
|
unsigned long long freed;
|
||||||
|
deleteFromStore(path, freed);
|
||||||
|
bytesFreed += freed;
|
||||||
|
|
||||||
|
#ifndef __CYGWIN__
|
||||||
|
if (fdLock != -1)
|
||||||
|
/* Write token to stale (deleted) lock file. */
|
||||||
|
writeFull(fdLock, (const unsigned char *) "d", 1);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete,
|
void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete,
|
||||||
bool ignoreLiveness, PathSet & result, unsigned long long & bytesFreed)
|
bool ignoreLiveness, PathSet & result, unsigned long long & bytesFreed)
|
||||||
{
|
{
|
||||||
|
@ -551,96 +628,28 @@ void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete,
|
||||||
/* Read the Nix store directory to find all currently existing
|
/* Read the Nix store directory to find all currently existing
|
||||||
paths. */
|
paths. */
|
||||||
printMsg(lvlError, format("reading the Nix store..."));
|
printMsg(lvlError, format("reading the Nix store..."));
|
||||||
PathSet storePathSet;
|
PathSet storePaths;
|
||||||
if (action != gcDeleteSpecific) {
|
if (action != gcDeleteSpecific) {
|
||||||
Paths entries = readDirectory(nixStore);
|
Paths entries = readDirectory(nixStore);
|
||||||
for (Paths::iterator i = entries.begin(); i != entries.end(); ++i)
|
for (Paths::iterator i = entries.begin(); i != entries.end(); ++i)
|
||||||
storePathSet.insert(canonPath(nixStore + "/" + *i));
|
storePaths.insert(canonPath(nixStore + "/" + *i));
|
||||||
} else {
|
} else {
|
||||||
for (PathSet::iterator i = pathsToDelete.begin();
|
for (PathSet::iterator i = pathsToDelete.begin();
|
||||||
i != pathsToDelete.end(); ++i)
|
i != pathsToDelete.end(); ++i)
|
||||||
{
|
{
|
||||||
assertStorePath(*i);
|
assertStorePath(*i);
|
||||||
storePathSet.insert(*i);
|
storePaths.insert(*i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Topologically sort them under the `referrers' relation. That
|
|
||||||
is, a < b iff a is in referrers(b). This gives us the order in
|
|
||||||
which things can be deleted safely. */
|
|
||||||
/* !!! when we have multiple output paths per derivation, this
|
|
||||||
will not work anymore because we get cycles. */
|
|
||||||
printMsg(lvlError, format("toposorting..."));
|
|
||||||
Paths storePaths = topoSortPaths(storePathSet);
|
|
||||||
|
|
||||||
/* Try to delete store paths in the topologically sorted order. */
|
/* Try to delete store paths in the topologically sorted order. */
|
||||||
printMsg(lvlError, action == gcReturnDead
|
printMsg(lvlError, action == gcReturnDead
|
||||||
? format("looking for garbage...")
|
? format("looking for garbage...")
|
||||||
: format("deleting garbage..."));
|
: format("deleting garbage..."));
|
||||||
|
|
||||||
for (Paths::iterator i = storePaths.begin(); i != storePaths.end(); ++i) {
|
PathSet done;
|
||||||
|
foreach (PathSet::iterator, i, storePaths)
|
||||||
debug(format("considering deletion of `%1%'") % *i);
|
tryToDelete(action, livePaths, tempRootsClosed, done, result, *i, bytesFreed);
|
||||||
|
|
||||||
if (livePaths.find(*i) != livePaths.end()) {
|
|
||||||
if (action == gcDeleteSpecific)
|
|
||||||
throw Error(format("cannot delete path `%1%' since it is still alive") % *i);
|
|
||||||
debug(format("live path `%1%'") % *i);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tempRootsClosed.find(*i) != tempRootsClosed.end()) {
|
|
||||||
debug(format("temporary root `%1%'") % *i);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
debug(format("dead path `%1%'") % *i);
|
|
||||||
result.insert(*i);
|
|
||||||
|
|
||||||
/* If just returning the set of dead paths, we also return the
|
|
||||||
space that would be freed if we deleted them. */
|
|
||||||
if (action == gcReturnDead)
|
|
||||||
bytesFreed += computePathSize(*i);
|
|
||||||
|
|
||||||
if (action == gcDeleteDead || action == gcDeleteSpecific) {
|
|
||||||
|
|
||||||
#ifndef __CYGWIN__
|
|
||||||
AutoCloseFD fdLock;
|
|
||||||
|
|
||||||
/* Only delete a lock file if we can acquire a write lock
|
|
||||||
on it. That means that it's either stale, or the
|
|
||||||
process that created it hasn't locked it yet. In the
|
|
||||||
latter case the other process will detect that we
|
|
||||||
deleted the lock, and retry (see pathlocks.cc). */
|
|
||||||
if (i->size() >= 5 && string(*i, i->size() - 5) == ".lock") {
|
|
||||||
fdLock = openLockFile(*i, false);
|
|
||||||
if (fdLock != -1 && !lockFile(fdLock, ltWrite, false)) {
|
|
||||||
debug(format("skipping active lock `%1%'") % *i);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!pathExists(*i)) continue;
|
|
||||||
|
|
||||||
printMsg(lvlInfo, format("deleting `%1%'") % *i);
|
|
||||||
|
|
||||||
/* Okay, it's safe to delete. */
|
|
||||||
try {
|
|
||||||
unsigned long long freed;
|
|
||||||
deleteFromStore(*i, freed);
|
|
||||||
bytesFreed += freed;
|
|
||||||
} catch (PathInUse & e) {
|
|
||||||
printMsg(lvlError, format("warning: %1%") % e.msg());
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef __CYGWIN__
|
|
||||||
if (fdLock != -1)
|
|
||||||
/* Write token to stale (deleted) lock file. */
|
|
||||||
writeFull(fdLock, (const unsigned char *) "d", 1);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -143,6 +143,10 @@ private:
|
||||||
|
|
||||||
void upgradeStore12();
|
void upgradeStore12();
|
||||||
|
|
||||||
|
void tryToDelete(GCAction action, const PathSet & livePaths,
|
||||||
|
const PathSet & tempRootsClosed, PathSet & done, PathSet & deleted,
|
||||||
|
const Path & path, unsigned long long & bytesFreed);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue