1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-25 19:51:00 +01:00

* Combine referrer update to prevent quadratic complexity in the

garbage collector.
This commit is contained in:
Eelco Dolstra 2008-03-08 19:21:20 +00:00
parent af7fd0b21f
commit 10c6e2a629
3 changed files with 54 additions and 19 deletions

View file

@ -82,6 +82,11 @@ LocalStore::LocalStore()
LocalStore::~LocalStore() LocalStore::~LocalStore()
{ {
try {
flushDelayedUpdates();
} catch (...) {
ignoreException();
}
} }
@ -211,21 +216,28 @@ static void appendReferrer(const Path & from, const Path & to)
} }
/* Atomically update the referrers file. */ /* Atomically update the referrers file. If `purge' is true, the set
static void rewriteReferrers(const Path & path, const PathSet & referrers) of referrers is set to `referrers'. Otherwise, the current set of
referrers is purged of invalid paths. */
void LocalStore::rewriteReferrers(const Path & path, bool purge, PathSet referrers)
{ {
Path referrersFile = referrersFileFor(path); Path referrersFile = referrersFileFor(path);
PathLocks referrersLock(singleton<PathSet, Path>(referrersFile)); PathLocks referrersLock(singleton<PathSet, Path>(referrersFile));
referrersLock.setDeletion(true); referrersLock.setDeletion(true);
if (purge)
/* queryReferrers() purges invalid paths, so that's all we
need. */
queryReferrers(path, referrers);
Path tmpFile = tmpFileForAtomicUpdate(referrersFile); Path tmpFile = tmpFileForAtomicUpdate(referrersFile);
AutoCloseFD fd = open(tmpFile.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0666); AutoCloseFD fd = open(tmpFile.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0666);
if (fd == -1) throw SysError(format("opening file `%1%'") % referrersFile); if (fd == -1) throw SysError(format("opening file `%1%'") % referrersFile);
string s; string s;
for (PathSet::const_iterator i = referrers.begin(); i != referrers.end(); ++i) { foreach (PathSet::const_iterator, i, referrers) {
s += " "; s += *i; s += " "; s += *i;
} }
@ -238,6 +250,15 @@ static void rewriteReferrers(const Path & path, const PathSet & referrers)
} }
void LocalStore::flushDelayedUpdates()
{
foreach (PathSet::iterator, i, delayedUpdates) {
rewriteReferrers(*i, true, PathSet());
}
delayedUpdates.clear();
}
void LocalStore::registerValidPath(const Path & path, void LocalStore::registerValidPath(const Path & path,
const Hash & hash, const PathSet & references, const Hash & hash, const PathSet & references,
const Path & deriver) const Path & deriver)
@ -501,10 +522,6 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
} }
#define foreach(it_type, it, collection) \
for (it_type it = collection.begin(); it != collection.end(); ++it)
/* Invalidate a path. The caller is responsible for checking that /* Invalidate a path. The caller is responsible for checking that
there are no referrers. */ there are no referrers. */
void LocalStore::invalidatePath(const Path & path) void LocalStore::invalidatePath(const Path & path)
@ -529,18 +546,25 @@ void LocalStore::invalidatePath(const Path & path)
/* Clear `path' from the info cache. */ /* Clear `path' from the info cache. */
pathInfoCache.erase(path); pathInfoCache.erase(path);
delayedUpdates.erase(path);
/* Remove the corresponding referrer entries for each path /* Cause the referrer files for each path referenced by this one
referenced by this one. This has to happen after removing the to be updated. This has to happen after removing the info file
info file to preserve the invariant (see to preserve the invariant (see registerValidPath()).
registerValidPath()). */
foreach (PathSet::iterator, i, info.references) { The referrer files are updated lazily in flushDelayedUpdates()
/* !!! O(n) */ to prevent quadratic performance in the garbage collector
if (*i == path) continue; /* self-reference */ (i.e., when N referrers to some path X are deleted, we have to
PathSet referrers; queryReferrers(*i, referrers); rewrite the referrers file for X N times, causing O(N^2) I/O).
referrers.erase(path);
rewriteReferrers(*i, referrers); What happens if we die before the referrer file can be updated?
} That's not a problem, because stale (invalid) entries in the
referrer file are ignored by queryReferrers(). Thus a referrer
file is allowed to have stale entries; removing them is just an
optimisation. verifyStore() gets rid of them eventually.
*/
foreach (PathSet::iterator, i, info.references)
if (*i != path) delayedUpdates.insert(*i);
} }
@ -943,7 +967,7 @@ void LocalStore::verifyStore(bool checkContents)
} else referrersNew.insert(*j); } else referrersNew.insert(*j);
} }
if (update) rewriteReferrers(from, referrersNew); if (update) rewriteReferrers(from, false, referrersNew);
} }
} }

View file

@ -118,10 +118,17 @@ private:
every once in a while. */ every once in a while. */
std::map<Path, ValidPathInfo> pathInfoCache; std::map<Path, ValidPathInfo> pathInfoCache;
/* Store paths for which the referrers file must be purged. */
PathSet delayedUpdates;
void registerValidPath(const ValidPathInfo & info); void registerValidPath(const ValidPathInfo & info);
ValidPathInfo queryPathInfo(const Path & path); ValidPathInfo queryPathInfo(const Path & path);
void rewriteReferrers(const Path & path, bool purge, PathSet referrers);
void flushDelayedUpdates();
bool queryReferrersInternal(const Path & path, PathSet & referrers); bool queryReferrersInternal(const Path & path, PathSet & referrers);
void invalidatePath(const Path & path); void invalidatePath(const Path & path);

View file

@ -12,6 +12,10 @@
namespace nix { namespace nix {
#define foreach(it_type, it, collection) \
for (it_type it = collection.begin(); it != collection.end(); ++it)
/* Return an environment variable. */ /* Return an environment variable. */
string getEnv(const string & key, const string & def = ""); string getEnv(const string & key, const string & def = "");