* Topologically sort paths under the references relation to ensure

that they are deleted in an order that maintains the closure
  invariant.
* Presence of a path in a temporary roots file does not imply that all
  paths in its closure are also present, so add the closure.
This commit is contained in:
Eelco Dolstra 2005-01-31 14:00:43 +00:00
parent 33c5d23b81
commit 252c9c91ab
3 changed files with 83 additions and 38 deletions

View file

@ -110,7 +110,6 @@ static void readTempRoots(PathSet & tempRoots, FDs & fds)
throw SysError(format("statting `%1%'") % path);
unsigned char buf[st.st_size]; /* !!! stack space */
readFull(*fd, buf, st.st_size);
debug(format("FILE SIZE %1%") % st.st_size);
/* Extract the roots. */
string contents((char *) buf, st.st_size);
@ -129,6 +128,37 @@ static void readTempRoots(PathSet & tempRoots, FDs & fds)
}
static void dfsVisit(const PathSet & paths, const Path & path,
PathSet & visited, Paths & sorted)
{
if (visited.find(path) != visited.end()) return;
visited.insert(path);
PathSet references;
if (isValidPath(path))
queryReferences(path, references);
for (PathSet::iterator i = references.begin();
i != references.end(); ++i)
/* Don't traverse into paths that don't exist. That can
happen due to substitutes for non-existent paths. */
if (*i != path && paths.find(*i) != paths.end())
dfsVisit(paths, *i, visited, sorted);
sorted.push_front(path);
}
static Paths topoSort(const PathSet & paths)
{
Paths sorted;
PathSet visited;
for (PathSet::const_iterator i = paths.begin(); i != paths.end(); ++i)
dfsVisit(paths, *i, visited, sorted);
return sorted;
}
void collectGarbage(const PathSet & roots, GCAction action,
PathSet & result)
{
@ -160,74 +190,86 @@ void collectGarbage(const PathSet & roots, GCAction action,
FDs fds;
readTempRoots(tempRoots, fds);
for (FDs::iterator i = fds.begin(); i != fds.end(); ++i)
debug(format("FD %1%") % (int) **i);
/* Close the temporary roots. Note that we *cannot* do this in
readTempRoots(), because there we may not have all locks yet,
meaning that an invalid path can become valid (and thus add to
the references graph) after we have added it to the closure
(and computeFSClosure() assumes that the presence of a path
means that it has already been closed). */
PathSet tempRootsClosed;
for (PathSet::iterator i = tempRoots.begin(); i != tempRoots.end(); ++i)
if (isValidPath(*i))
computeFSClosure(*i, tempRootsClosed);
else
tempRootsClosed.insert(*i);
/* After this point the set of roots or temporary roots cannot
increase, since we hold locks on everything. So everything
that is not currently in in `livePaths' or `tempRootsClosed'
can be deleted. */
/* !!! TODO: Try to acquire (without blocking) exclusive locks on
the files in the `pending' directory. Delete all files for
which we managed to acquire such a lock (since if we could get
such a lock, that means that the process that owned the file
has died). */
/* !!! TODO: Acquire shared locks on all files in the pending
directories. This prevents the set of pending paths from
increasing while we are garbage-collecting. Read the set of
pending paths from those files. */
/* Read the Nix store directory to find all currently existing
paths. */
Strings storeNames = readDirectory(nixStore);
Paths storePaths = readDirectory(nixStore);
PathSet storePaths2;
for (Paths::iterator i = storePaths.begin(); i != storePaths.end(); ++i)
storePaths2.insert(canonPath(nixStore + "/" + *i));
for (Strings::iterator i = storeNames.begin(); i != storeNames.end(); ++i) {
Path path = canonPath(nixStore + "/" + *i);
/* Topologically sort them under the `referers' relation. That
is, a < b iff a is in referers(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. */
storePaths = topoSort(storePaths2);
for (Paths::iterator i = storePaths.begin(); i != storePaths.end(); ++i) {
if (livePaths.find(path) != livePaths.end()) {
debug(format("live path `%1%'") % path);
debug(format("considering deletion of `%1%'") % *i);
if (livePaths.find(*i) != livePaths.end()) {
debug(format("live path `%1%'") % *i);
continue;
}
if (tempRoots.find(path) != tempRoots.end()) {
debug(format("temporary root `%1%'") % path);
if (tempRootsClosed.find(*i) != tempRootsClosed.end()) {
debug(format("temporary root `%1%'") % *i);
continue;
}
debug(format("dead path `%1%'") % path);
result.insert(path);
debug(format("dead path `%1%'") % *i);
result.insert(*i);
AutoCloseFD fdLock;
if (action == gcDeleteDead) {
printMsg(lvlInfo, format("deleting `%1%'") % path);
/* 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") {
if (i->size() >= 5 && string(*i, i->size() - 5) == ".lock") {
fdLock = open(path.c_str(), O_RDWR);
fdLock = open(i->c_str(), O_RDWR);
if (fdLock == -1) {
if (errno == ENOENT) continue;
throw SysError(format("opening lock file `%1%'") % path);
throw SysError(format("opening lock file `%1%'") % *i);
}
if (!lockFile(fdLock, ltWrite, false)) {
debug(format("skipping active lock `%1%'") % path);
debug(format("skipping active lock `%1%'") % *i);
continue;
}
}
printMsg(lvlInfo, format("deleting `%1%'") % *i);
deleteFromStore(path);
/* Okay, it's safe to delete. */
deleteFromStore(*i);
if (fdLock != -1)
/* Write token to stale (deleted) lock file. */
writeFull(fdLock, (const unsigned char *) "d", 1);
}
/* Only delete lock files if the path is belongs to doesn't
exist and isn't a temporary root and we can acquire an
exclusive lock on it. */
/* !!! */
}
}

View file

@ -311,10 +311,9 @@ void queryReferences(const Path & storePath, PathSet & references)
void queryReferers(const Path & storePath, PathSet & referers)
{
Paths referers2;
if (!isRealisablePath(noTxn, storePath))
throw Error(format("path `%1%' is not valid") % storePath);
nixDB.queryStrings(noTxn, dbReferers, storePath, referers2);
PathSet referers2 = getReferers(noTxn, storePath);
referers.insert(referers2.begin(), referers2.end());
}
@ -427,6 +426,8 @@ void registerValidPath(const Transaction & txn,
}
/* Invalidate a path. The caller is responsible for checking that
there are no referers. */
static void invalidatePath(const Path & path, Transaction & txn)
{
debug(format("unregistering path `%1%'") % path);
@ -551,8 +552,11 @@ void deleteFromStore(const Path & _path)
assertStorePath(path);
Transaction txn(nixDB);
if (isValidPathTxn(txn, path))
if (isValidPathTxn(txn, path)) {
if (getReferers(txn, path).size() > 0)
throw Error(format("cannot delete path `%1%' because it is in use") % path);
invalidatePath(path, txn);
}
txn.commit();
deletePath(path);

View file

@ -407,7 +407,6 @@ AutoCloseFD::operator int() const
void AutoCloseFD::close()
{
if (fd != -1) {
debug(format("closing fd %1%") % fd);
if (::close(fd) == -1)
/* This should never happen. */
throw SysError("closing file descriptor");