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:
parent
af7fd0b21f
commit
10c6e2a629
3 changed files with 54 additions and 19 deletions
|
|
@ -82,6 +82,11 @@ 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. */
|
||||
static void rewriteReferrers(const Path & path, const PathSet & referrers)
|
||||
/* Atomically update the referrers file. If `purge' is true, the set
|
||||
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);
|
||||
|
||||
PathLocks referrersLock(singleton<PathSet, Path>(referrersFile));
|
||||
referrersLock.setDeletion(true);
|
||||
|
||||
if (purge)
|
||||
/* queryReferrers() purges invalid paths, so that's all we
|
||||
need. */
|
||||
queryReferrers(path, referrers);
|
||||
|
||||
Path tmpFile = tmpFileForAtomicUpdate(referrersFile);
|
||||
|
||||
AutoCloseFD fd = open(tmpFile.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0666);
|
||||
if (fd == -1) throw SysError(format("opening file `%1%'") % referrersFile);
|
||||
|
||||
string s;
|
||||
for (PathSet::const_iterator i = referrers.begin(); i != referrers.end(); ++i) {
|
||||
foreach (PathSet::const_iterator, i, referrers) {
|
||||
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,
|
||||
const Hash & hash, const PathSet & references,
|
||||
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
|
||||
there are no referrers. */
|
||||
void LocalStore::invalidatePath(const Path & path)
|
||||
|
|
@ -529,18 +546,25 @@ void LocalStore::invalidatePath(const Path & path)
|
|||
|
||||
/* Clear `path' from the info cache. */
|
||||
pathInfoCache.erase(path);
|
||||
delayedUpdates.erase(path);
|
||||
|
||||
/* Remove the corresponding referrer entries for each path
|
||||
referenced by this one. This has to happen after removing the
|
||||
info file to preserve the invariant (see
|
||||
registerValidPath()). */
|
||||
foreach (PathSet::iterator, i, info.references) {
|
||||
/* !!! O(n) */
|
||||
if (*i == path) continue; /* self-reference */
|
||||
PathSet referrers; queryReferrers(*i, referrers);
|
||||
referrers.erase(path);
|
||||
rewriteReferrers(*i, referrers);
|
||||
}
|
||||
/* Cause the referrer files for each path referenced by this one
|
||||
to be updated. This has to happen after removing the info file
|
||||
to preserve the invariant (see registerValidPath()).
|
||||
|
||||
The referrer files are updated lazily in flushDelayedUpdates()
|
||||
to prevent quadratic performance in the garbage collector
|
||||
(i.e., when N referrers to some path X are deleted, we have to
|
||||
rewrite the referrers file for X N times, causing O(N^2) I/O).
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (update) rewriteReferrers(from, referrersNew);
|
||||
if (update) rewriteReferrers(from, false, referrersNew);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -118,10 +118,17 @@ private:
|
|||
every once in a while. */
|
||||
std::map<Path, ValidPathInfo> pathInfoCache;
|
||||
|
||||
/* Store paths for which the referrers file must be purged. */
|
||||
PathSet delayedUpdates;
|
||||
|
||||
void registerValidPath(const ValidPathInfo & info);
|
||||
|
||||
ValidPathInfo queryPathInfo(const Path & path);
|
||||
|
||||
void rewriteReferrers(const Path & path, bool purge, PathSet referrers);
|
||||
|
||||
void flushDelayedUpdates();
|
||||
|
||||
bool queryReferrersInternal(const Path & path, PathSet & referrers);
|
||||
|
||||
void invalidatePath(const Path & path);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@
|
|||
namespace nix {
|
||||
|
||||
|
||||
#define foreach(it_type, it, collection) \
|
||||
for (it_type it = collection.begin(); it != collection.end(); ++it)
|
||||
|
||||
|
||||
/* Return an environment variable. */
|
||||
string getEnv(const string & key, const string & def = "");
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue