* Some refactoring: put the GC options / results in separate structs.
* The garbage collector now also prints the number of blocks freed.
This commit is contained in:
parent
934c58aa38
commit
a72709afd8
15 changed files with 252 additions and 166 deletions
|
|
@ -578,17 +578,17 @@ void getOwnership(const Path & path)
|
|||
|
||||
|
||||
void deletePathWrapped(const Path & path,
|
||||
unsigned long long & bytesFreed)
|
||||
unsigned long long & bytesFreed, unsigned long long & blocksFreed)
|
||||
{
|
||||
try {
|
||||
/* First try to delete it ourselves. */
|
||||
deletePath(path, bytesFreed);
|
||||
deletePath(path, bytesFreed, blocksFreed);
|
||||
} catch (SysError & e) {
|
||||
/* If this failed due to a permission error, then try it with
|
||||
the setuid helper. */
|
||||
if (haveBuildUsers() && !amPrivileged()) {
|
||||
getOwnership(path);
|
||||
deletePath(path, bytesFreed);
|
||||
deletePath(path, bytesFreed, blocksFreed);
|
||||
} else
|
||||
throw;
|
||||
}
|
||||
|
|
@ -597,8 +597,8 @@ void deletePathWrapped(const Path & path,
|
|||
|
||||
void deletePathWrapped(const Path & path)
|
||||
{
|
||||
unsigned long long dummy;
|
||||
deletePathWrapped(path, dummy);
|
||||
unsigned long long dummy1, dummy2;
|
||||
deletePathWrapped(path, dummy1, dummy2);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -439,9 +439,9 @@ 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)
|
||||
void LocalStore::tryToDelete(const GCOptions & options, GCResults & results,
|
||||
const PathSet & livePaths, const PathSet & tempRootsClosed, PathSet & done,
|
||||
const Path & path)
|
||||
{
|
||||
if (done.find(path) != done.end()) return;
|
||||
done.insert(path);
|
||||
|
|
@ -449,7 +449,7 @@ void LocalStore::tryToDelete(GCAction action, const PathSet & livePaths,
|
|||
debug(format("considering deletion of `%1%'") % path);
|
||||
|
||||
if (livePaths.find(path) != livePaths.end()) {
|
||||
if (action == gcDeleteSpecific)
|
||||
if (options.action == GCOptions::gcDeleteSpecific)
|
||||
throw Error(format("cannot delete path `%1%' since it is still alive") % path);
|
||||
debug(format("live path `%1%'") % path);
|
||||
return;
|
||||
|
|
@ -470,15 +470,18 @@ void LocalStore::tryToDelete(GCAction action, const PathSet & livePaths,
|
|||
queryReferrers(path, referrers);
|
||||
foreach (PathSet::iterator, i, referrers)
|
||||
if (*i != path)
|
||||
tryToDelete(action, livePaths, tempRootsClosed, done, deleted, *i, bytesFreed);
|
||||
tryToDelete(options, results, livePaths, tempRootsClosed, done, *i);
|
||||
|
||||
debug(format("dead path `%1%'") % path);
|
||||
deleted.insert(path);
|
||||
results.paths.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);
|
||||
if (options.action == GCOptions::gcReturnDead) {
|
||||
unsigned long long bytesFreed, blocksFreed;
|
||||
computePathSize(path, bytesFreed, blocksFreed);
|
||||
results.bytesFreed += bytesFreed;
|
||||
results.blocksFreed += blocksFreed;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -504,9 +507,10 @@ void LocalStore::tryToDelete(GCAction action, const PathSet & livePaths,
|
|||
printMsg(lvlInfo, format("deleting `%1%'") % path);
|
||||
|
||||
/* Okay, it's safe to delete. */
|
||||
unsigned long long freed;
|
||||
deleteFromStore(path, freed);
|
||||
bytesFreed += freed;
|
||||
unsigned long long bytesFreed, blocksFreed;
|
||||
deleteFromStore(path, bytesFreed, blocksFreed);
|
||||
results.bytesFreed += bytesFreed;
|
||||
results.blocksFreed += blocksFreed;
|
||||
|
||||
#ifndef __CYGWIN__
|
||||
if (fdLock != -1)
|
||||
|
|
@ -516,12 +520,8 @@ void LocalStore::tryToDelete(GCAction action, const PathSet & livePaths,
|
|||
}
|
||||
|
||||
|
||||
void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete,
|
||||
bool ignoreLiveness, PathSet & result, unsigned long long & bytesFreed)
|
||||
void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||
{
|
||||
result.clear();
|
||||
bytesFreed = 0;
|
||||
|
||||
bool gcKeepOutputs =
|
||||
queryBoolSetting("gc-keep-outputs", false);
|
||||
bool gcKeepDerivations =
|
||||
|
|
@ -537,7 +537,7 @@ void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete,
|
|||
/* Find the roots. Since we've grabbed the GC lock, the set of
|
||||
permanent roots cannot increase now. */
|
||||
printMsg(lvlError, format("finding garbage collector roots..."));
|
||||
Roots rootMap = ignoreLiveness ? Roots() : nix::findRoots(true);
|
||||
Roots rootMap = options.ignoreLiveness ? Roots() : nix::findRoots(true);
|
||||
|
||||
PathSet roots;
|
||||
for (Roots::iterator i = rootMap.begin(); i != rootMap.end(); ++i)
|
||||
|
|
@ -547,11 +547,11 @@ void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete,
|
|||
NIX_ROOT_FINDER environment variable. This is typically used
|
||||
to add running programs to the set of roots (to prevent them
|
||||
from being garbage collected). */
|
||||
if (!ignoreLiveness)
|
||||
if (!options.ignoreLiveness)
|
||||
addAdditionalRoots(roots);
|
||||
|
||||
if (action == gcReturnRoots) {
|
||||
result = roots;
|
||||
if (options.action == GCOptions::gcReturnRoots) {
|
||||
results.paths = roots;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -595,8 +595,8 @@ void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete,
|
|||
}
|
||||
}
|
||||
|
||||
if (action == gcReturnLive) {
|
||||
result = livePaths;
|
||||
if (options.action == GCOptions::gcReturnLive) {
|
||||
results.paths = livePaths;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -633,27 +633,25 @@ void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete,
|
|||
paths. */
|
||||
printMsg(lvlError, format("reading the Nix store..."));
|
||||
PathSet storePaths;
|
||||
if (action != gcDeleteSpecific) {
|
||||
if (options.action != GCOptions::gcDeleteSpecific) {
|
||||
Paths entries = readDirectory(nixStore);
|
||||
for (Paths::iterator i = entries.begin(); i != entries.end(); ++i)
|
||||
storePaths.insert(canonPath(nixStore + "/" + *i));
|
||||
} else {
|
||||
for (PathSet::iterator i = pathsToDelete.begin();
|
||||
i != pathsToDelete.end(); ++i)
|
||||
{
|
||||
foreach (PathSet::iterator, i, options.pathsToDelete) {
|
||||
assertStorePath(*i);
|
||||
storePaths.insert(*i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to delete store paths in the topologically sorted order. */
|
||||
printMsg(lvlError, action == gcReturnDead
|
||||
printMsg(lvlError, options.action == GCOptions::gcReturnDead
|
||||
? format("looking for garbage...")
|
||||
: format("deleting garbage..."));
|
||||
|
||||
PathSet done;
|
||||
foreach (PathSet::iterator, i, storePaths)
|
||||
tryToDelete(action, livePaths, tempRootsClosed, done, result, *i, bytesFreed);
|
||||
tryToDelete(options, results, livePaths, tempRootsClosed, done, *i);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -851,7 +851,8 @@ Path LocalStore::importPath(bool requireSignature, Source & source)
|
|||
}
|
||||
|
||||
|
||||
void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFreed)
|
||||
void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFreed,
|
||||
unsigned long long & blocksFreed)
|
||||
{
|
||||
bytesFreed = 0;
|
||||
|
||||
|
|
@ -871,7 +872,7 @@ void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFr
|
|||
invalidatePath(path);
|
||||
}
|
||||
|
||||
deletePathWrapped(path, bytesFreed);
|
||||
deletePathWrapped(path, bytesFreed, blocksFreed);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,10 +25,11 @@ struct OptimiseStats
|
|||
unsigned long sameContents;
|
||||
unsigned long filesLinked;
|
||||
unsigned long long bytesFreed;
|
||||
unsigned long long blocksFreed;
|
||||
OptimiseStats()
|
||||
{
|
||||
totalFiles = sameContents = filesLinked = 0;
|
||||
bytesFreed = 0;
|
||||
bytesFreed = blocksFreed = 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -89,11 +90,11 @@ public:
|
|||
|
||||
Roots findRoots();
|
||||
|
||||
void collectGarbage(GCAction action, const PathSet & pathsToDelete,
|
||||
bool ignoreLiveness, PathSet & result, unsigned long long & bytesFreed);
|
||||
void collectGarbage(const GCOptions & options, GCResults & results);
|
||||
|
||||
/* Delete a path from the Nix store. */
|
||||
void deleteFromStore(const Path & path, unsigned long long & bytesFreed);
|
||||
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
|
||||
files with the same contents. */
|
||||
|
|
@ -143,10 +144,9 @@ private:
|
|||
|
||||
void upgradeStore12();
|
||||
|
||||
void tryToDelete(GCAction action, const PathSet & livePaths,
|
||||
const PathSet & tempRootsClosed, PathSet & done, PathSet & deleted,
|
||||
const Path & path, unsigned long long & bytesFreed);
|
||||
|
||||
void tryToDelete(const GCOptions & options, GCResults & results,
|
||||
const PathSet & livePaths, const PathSet & tempRootsClosed, PathSet & done,
|
||||
const Path & path);
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -179,7 +179,7 @@ void getOwnership(const Path & path);
|
|||
/* Like deletePath(), but changes the ownership of `path' using the
|
||||
setuid wrapper if necessary (and possible). */
|
||||
void deletePathWrapped(const Path & path,
|
||||
unsigned long long & bytesFreed);
|
||||
unsigned long long & bytesFreed, unsigned long long & blocksFreed);
|
||||
|
||||
void deletePathWrapped(const Path & path);
|
||||
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ static void hashAndLink(bool dryRun, HashToPath & hashToPath,
|
|||
|
||||
stats.filesLinked++;
|
||||
stats.bytesFreed += st.st_size;
|
||||
stats.blocksFreed += st.st_blocks;
|
||||
}
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
|
|
|
|||
|
|
@ -372,24 +372,20 @@ Roots RemoteStore::findRoots()
|
|||
}
|
||||
|
||||
|
||||
void RemoteStore::collectGarbage(GCAction action, const PathSet & pathsToDelete,
|
||||
bool ignoreLiveness, PathSet & result, unsigned long long & bytesFreed)
|
||||
void RemoteStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||
{
|
||||
result.clear();
|
||||
bytesFreed = 0;
|
||||
writeInt(wopCollectGarbage, to);
|
||||
writeInt(action, to);
|
||||
writeStringSet(pathsToDelete, to);
|
||||
writeInt(ignoreLiveness, to);
|
||||
writeInt(options.action, to);
|
||||
writeStringSet(options.pathsToDelete, to);
|
||||
writeInt(options.ignoreLiveness, to);
|
||||
writeLongLong(options.maxFreed, to);
|
||||
writeInt(options.maxLinks, to);
|
||||
|
||||
processStderr();
|
||||
|
||||
result = readStringSet(from);
|
||||
|
||||
/* Ugh - NAR integers are 64 bits, but read/writeInt() aren't. */
|
||||
unsigned int lo = readInt(from);
|
||||
unsigned int hi = readInt(from);
|
||||
bytesFreed = (((unsigned long long) hi) << 32) | lo;
|
||||
results.paths = readStringSet(from);
|
||||
results.bytesFreed = readLongLong(from);
|
||||
results.blocksFreed = readLongLong(from);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -65,8 +65,7 @@ public:
|
|||
|
||||
Roots findRoots();
|
||||
|
||||
void collectGarbage(GCAction action, const PathSet & pathsToDelete,
|
||||
bool ignoreLiveness, PathSet & result, unsigned long long & bytesFreed);
|
||||
void collectGarbage(const GCOptions & options, GCResults & results);
|
||||
|
||||
private:
|
||||
AutoCloseFD fdSocket;
|
||||
|
|
|
|||
|
|
@ -16,14 +16,82 @@ namespace nix {
|
|||
typedef std::map<Path, Path> Roots;
|
||||
|
||||
|
||||
/* Garbage collector operation. */
|
||||
typedef enum {
|
||||
gcReturnRoots,
|
||||
gcReturnLive,
|
||||
gcReturnDead,
|
||||
gcDeleteDead,
|
||||
gcDeleteSpecific,
|
||||
} GCAction;
|
||||
|
||||
|
||||
struct GCOptions
|
||||
{
|
||||
/* Garbage collector operation:
|
||||
|
||||
- `gcReturnRoots': find and return the set of roots for the
|
||||
garbage collector. These are the store paths symlinked to in
|
||||
the `gcroots' directory.
|
||||
|
||||
- `gcReturnLive': return the set of paths reachable from
|
||||
(i.e. in the closure of) the roots.
|
||||
|
||||
- `gcReturnDead': return the set of paths not reachable from
|
||||
the roots.
|
||||
|
||||
- `gcDeleteDead': actually delete the latter set.
|
||||
|
||||
- `gcDeleteSpecific': delete the paths listed in
|
||||
`pathsToDelete', insofar as they are not reachable.
|
||||
*/
|
||||
typedef enum {
|
||||
gcReturnRoots,
|
||||
gcReturnLive,
|
||||
gcReturnDead,
|
||||
gcDeleteDead,
|
||||
gcDeleteSpecific,
|
||||
} GCAction;
|
||||
|
||||
GCAction action;
|
||||
|
||||
/* If `ignoreLiveness' is set, then reachability from the roots is
|
||||
ignored (dangerous!). However, the paths must still be
|
||||
unreferenced *within* the store (i.e., there can be no other
|
||||
store paths that depend on them). */
|
||||
bool ignoreLiveness;
|
||||
|
||||
/* For `gcDeleteSpecific', the paths to delete. */
|
||||
PathSet pathsToDelete;
|
||||
|
||||
/* Stop after at least `maxFreed' bytes have been freed. */
|
||||
unsigned long long maxFreed;
|
||||
|
||||
/* Stop after the number of hard links to the Nix store directory
|
||||
has dropped to at least `maxLinks'. */
|
||||
unsigned int maxLinks;
|
||||
|
||||
GCOptions()
|
||||
{
|
||||
action = gcDeleteDead;
|
||||
ignoreLiveness = false;
|
||||
maxFreed = ULLONG_MAX;
|
||||
maxLinks = UINT_MAX;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct GCResults
|
||||
{
|
||||
/* Depending on the action, the GC roots, or the paths that would
|
||||
be or have been deleted. */
|
||||
PathSet paths;
|
||||
|
||||
/* For `gcReturnDead', `gcDeleteDead' and `gcDeleteSpecific', the
|
||||
number of bytes that would be or was freed. */
|
||||
unsigned long long bytesFreed;
|
||||
|
||||
/* The number of file system blocks that would be or was freed. */
|
||||
unsigned long long blocksFreed;
|
||||
|
||||
GCResults()
|
||||
{
|
||||
bytesFreed = 0;
|
||||
blocksFreed = 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class StoreAPI
|
||||
|
|
@ -137,33 +205,8 @@ public:
|
|||
outside of the Nix store that point to `storePath'. */
|
||||
virtual Roots findRoots() = 0;
|
||||
|
||||
/* Depending on `action', this function does the following:
|
||||
|
||||
- `gcReturnRoots': find and return the set of roots for the
|
||||
garbage collector. These are the store paths symlinked to in
|
||||
the `gcroots' directory.
|
||||
|
||||
- `gcReturnLive': return the set of paths reachable from
|
||||
(i.e. in the closure of) the roots.
|
||||
|
||||
- `gcReturnDead': return the set of paths not reachable from
|
||||
the roots.
|
||||
|
||||
- `gcDeleteDead': actually delete the latter set.
|
||||
|
||||
- `gcDeleteSpecific': delete the paths listed in
|
||||
`pathsToDelete', insofar as they are not reachable.
|
||||
|
||||
If `ignoreLiveness' is set, then reachability from the roots is
|
||||
ignored (dangerous!). However, the paths must still be
|
||||
unreferenced *within* the store (i.e., there can be no other
|
||||
store paths that depend on them).
|
||||
|
||||
For `gcReturnDead', `gcDeleteDead' and `gcDeleteSpecific', the
|
||||
number of bytes that would be or was freed is returned in
|
||||
`bytesFreed'. */
|
||||
virtual void collectGarbage(GCAction action, const PathSet & pathsToDelete,
|
||||
bool ignoreLiveness, PathSet & result, unsigned long long & bytesFreed) = 0;
|
||||
/* Perform a garbage collection. */
|
||||
virtual void collectGarbage(const GCOptions & options, GCResults & results) = 0;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -15,24 +15,24 @@ namespace nix {
|
|||
|
||||
typedef enum {
|
||||
wopQuit = 0,
|
||||
wopIsValidPath,
|
||||
wopIsValidPath = 1,
|
||||
wopHasSubstitutes = 3,
|
||||
wopQueryPathHash,
|
||||
wopQueryReferences,
|
||||
wopQueryReferrers,
|
||||
wopAddToStore,
|
||||
wopAddTextToStore,
|
||||
wopBuildDerivations,
|
||||
wopEnsurePath,
|
||||
wopAddTempRoot,
|
||||
wopAddIndirectRoot,
|
||||
wopSyncWithGC,
|
||||
wopFindRoots,
|
||||
wopCollectGarbage,
|
||||
wopExportPath,
|
||||
wopImportPath,
|
||||
wopQueryDeriver,
|
||||
wopSetOptions,
|
||||
wopQueryPathHash = 4,
|
||||
wopQueryReferences = 5,
|
||||
wopQueryReferrers = 6,
|
||||
wopAddToStore = 7,
|
||||
wopAddTextToStore = 8,
|
||||
wopBuildDerivations = 9,
|
||||
wopEnsurePath = 10,
|
||||
wopAddTempRoot = 11,
|
||||
wopAddIndirectRoot = 12,
|
||||
wopSyncWithGC = 13,
|
||||
wopFindRoots = 14,
|
||||
wopExportPath = 16,
|
||||
wopImportPath = 17,
|
||||
wopQueryDeriver = 18,
|
||||
wopSetOptions = 19,
|
||||
wopCollectGarbage = 20,
|
||||
} WorkerOp;
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue