Make the garbage collector more concurrent
Make the garbage collector more concurrent by deleting valid paths outside the region where we're holding the global GC lock. This should greatly reduce the time during which new builds are blocked, since the deletion accounts for the vast majority of the time spent in the GC. To ensure that this is safe, the valid paths are invalidated and renamed to some arbitrary path while we're holding the lock. This ensures that we when we finally delete the path, it's not a (newly) valid or locked path.
This commit is contained in:
parent
8be1979f1a
commit
117670be57
3 changed files with 47 additions and 20 deletions
|
@ -1,6 +1,7 @@
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "misc.hh"
|
#include "misc.hh"
|
||||||
#include "local-store.hh"
|
#include "local-store.hh"
|
||||||
|
#include "immutable.hh"
|
||||||
|
|
||||||
#include <boost/shared_ptr.hpp>
|
#include <boost/shared_ptr.hpp>
|
||||||
|
|
||||||
|
@ -396,11 +397,11 @@ struct LocalStore::GCState
|
||||||
PathSet deleted;
|
PathSet deleted;
|
||||||
PathSet live;
|
PathSet live;
|
||||||
PathSet busy;
|
PathSet busy;
|
||||||
|
PathSet invalidated;
|
||||||
bool gcKeepOutputs;
|
bool gcKeepOutputs;
|
||||||
bool gcKeepDerivations;
|
bool gcKeepDerivations;
|
||||||
GCState(GCResults & results_) : results(results_)
|
unsigned long long bytesInvalidated;
|
||||||
{
|
GCState(GCResults & results_) : results(results_), bytesInvalidated(0) { }
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -419,6 +420,16 @@ bool LocalStore::isActiveTempFile(const GCState & state,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LocalStore::deleteGarbage(GCState & state, const Path & path)
|
||||||
|
{
|
||||||
|
printMsg(lvlInfo, format("deleting `%1%'") % path);
|
||||||
|
unsigned long long bytesFreed, blocksFreed;
|
||||||
|
deletePathWrapped(path, bytesFreed, blocksFreed);
|
||||||
|
state.results.bytesFreed += bytesFreed;
|
||||||
|
state.results.blocksFreed += blocksFreed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool LocalStore::tryToDelete(GCState & state, const Path & path)
|
bool LocalStore::tryToDelete(GCState & state, const Path & path)
|
||||||
{
|
{
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
|
@ -502,15 +513,27 @@ bool LocalStore::tryToDelete(GCState & state, const Path & path)
|
||||||
|
|
||||||
/* The path is garbage, so delete it. */
|
/* The path is garbage, so delete it. */
|
||||||
if (shouldDelete(state.options.action)) {
|
if (shouldDelete(state.options.action)) {
|
||||||
printMsg(lvlInfo, format("deleting `%1%'") % path);
|
|
||||||
|
|
||||||
unsigned long long bytesFreed, blocksFreed;
|
if (isValidPath(path)) {
|
||||||
deleteFromStore(path, bytesFreed, blocksFreed);
|
/* If it's a valid path, invalidate it, rename it, and
|
||||||
state.results.bytesFreed += bytesFreed;
|
schedule it for deletion. The renaming is to ensure
|
||||||
state.results.blocksFreed += blocksFreed;
|
that later (when we're not holding the global GC lock)
|
||||||
|
we can delete the path without being afraid that the
|
||||||
|
path has become alive again. */
|
||||||
|
printMsg(lvlInfo, format("invalidating `%1%'") % path);
|
||||||
|
// Estimate the amount freed using the narSize field.
|
||||||
|
state.bytesInvalidated += queryPathInfo(path).narSize;
|
||||||
|
invalidatePathChecked(path);
|
||||||
|
Path tmp = (format("%1%-gc-%2%") % path % getpid()).str();
|
||||||
|
makeMutable(path.c_str());
|
||||||
|
if (rename(path.c_str(), tmp.c_str()))
|
||||||
|
throw SysError(format("unable to rename `%1%' to `%2%'") % path % tmp);
|
||||||
|
state.invalidated.insert(tmp);
|
||||||
|
} else
|
||||||
|
deleteGarbage(state, path);
|
||||||
|
|
||||||
if (state.options.maxFreed && state.results.bytesFreed > state.options.maxFreed) {
|
if (state.options.maxFreed && state.results.bytesFreed + state.bytesInvalidated > state.options.maxFreed) {
|
||||||
printMsg(lvlInfo, format("deleted more than %1% bytes; stopping") % state.options.maxFreed);
|
printMsg(lvlInfo, format("deleted or invalidated more than %1% bytes; stopping") % state.options.maxFreed);
|
||||||
throw GCLimitReached();
|
throw GCLimitReached();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -635,6 +658,14 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||||
} catch (GCLimitReached & e) {
|
} catch (GCLimitReached & e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Allow other processes to add to the store from here on. */
|
||||||
|
fdGCLock.close();
|
||||||
|
|
||||||
|
/* Delete the invalidated paths now that the lock has been
|
||||||
|
released. */
|
||||||
|
foreach (PathSet::iterator, i, state.invalidated)
|
||||||
|
deleteGarbage(state, *i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1380,11 +1380,8 @@ Paths LocalStore::importPaths(bool requireSignature, Source & source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFreed,
|
void LocalStore::invalidatePathChecked(const Path & path)
|
||||||
unsigned long long & blocksFreed)
|
|
||||||
{
|
{
|
||||||
bytesFreed = 0;
|
|
||||||
|
|
||||||
assertStorePath(path);
|
assertStorePath(path);
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
|
@ -1404,8 +1401,6 @@ void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFr
|
||||||
break;
|
break;
|
||||||
} catch (SQLiteBusy & e) { };
|
} catch (SQLiteBusy & e) { };
|
||||||
}
|
}
|
||||||
|
|
||||||
deletePathWrapped(path, bytesFreed, blocksFreed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -164,10 +164,6 @@ public:
|
||||||
|
|
||||||
void collectGarbage(const GCOptions & options, GCResults & results);
|
void collectGarbage(const GCOptions & options, GCResults & results);
|
||||||
|
|
||||||
/* Delete a path from the Nix store. */
|
|
||||||
void deleteFromStore(const Path & path, unsigned long long & bytesFreed,
|
|
||||||
unsigned long long & blocksFreed);
|
|
||||||
|
|
||||||
/* Optimise the disk space usage of the Nix store by hard-linking
|
/* Optimise the disk space usage of the Nix store by hard-linking
|
||||||
files with the same contents. */
|
files with the same contents. */
|
||||||
void optimiseStore(bool dryRun, OptimiseStats & stats);
|
void optimiseStore(bool dryRun, OptimiseStats & stats);
|
||||||
|
@ -238,6 +234,9 @@ private:
|
||||||
|
|
||||||
void invalidatePath(const Path & path);
|
void invalidatePath(const Path & path);
|
||||||
|
|
||||||
|
/* Delete a path from the Nix store. */
|
||||||
|
void invalidatePathChecked(const Path & path);
|
||||||
|
|
||||||
void verifyPath(const Path & path, const PathSet & store,
|
void verifyPath(const Path & path, const PathSet & store,
|
||||||
PathSet & done, PathSet & validPaths);
|
PathSet & done, PathSet & validPaths);
|
||||||
|
|
||||||
|
@ -249,6 +248,8 @@ private:
|
||||||
|
|
||||||
struct GCState;
|
struct GCState;
|
||||||
|
|
||||||
|
void deleteGarbage(GCState & state, const Path & path);
|
||||||
|
|
||||||
bool tryToDelete(GCState & state, const Path & path);
|
bool tryToDelete(GCState & state, const Path & path);
|
||||||
|
|
||||||
bool isActiveTempFile(const GCState & state,
|
bool isActiveTempFile(const GCState & state,
|
||||||
|
|
Loading…
Reference in a new issue