mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-10 08:16:15 +02:00
* 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,
|
||||
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
|
||||
paths. */
|
||||
printMsg(lvlError, format("reading the Nix store..."));
|
||||
PathSet storePathSet;
|
||||
PathSet storePaths;
|
||||
if (action != gcDeleteSpecific) {
|
||||
Paths entries = readDirectory(nixStore);
|
||||
for (Paths::iterator i = entries.begin(); i != entries.end(); ++i)
|
||||
storePathSet.insert(canonPath(nixStore + "/" + *i));
|
||||
storePaths.insert(canonPath(nixStore + "/" + *i));
|
||||
} else {
|
||||
for (PathSet::iterator i = pathsToDelete.begin();
|
||||
i != pathsToDelete.end(); ++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. */
|
||||
printMsg(lvlError, action == gcReturnDead
|
||||
? format("looking for garbage...")
|
||||
: format("deleting garbage..."));
|
||||
|
||||
for (Paths::iterator i = storePaths.begin(); i != storePaths.end(); ++i) {
|
||||
|
||||
debug(format("considering deletion of `%1%'") % *i);
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
PathSet done;
|
||||
foreach (PathSet::iterator, i, storePaths)
|
||||
tryToDelete(action, livePaths, tempRootsClosed, done, result, *i, bytesFreed);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -143,6 +143,10 @@ private:
|
|||
|
||||
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