Skip to content

Commit a1886ea

Browse files
committed
Refactor GCOptions and GCAction, add recursive flag
1 parent 0d0f7b8 commit a1886ea

File tree

9 files changed

+223
-147
lines changed

9 files changed

+223
-147
lines changed

src/libstore/daemon.cc

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -712,18 +712,36 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
712712

713713
case WorkerProto::Op::CollectGarbage: {
714714
GCOptions options;
715-
options.action = (GCOptions::GCAction) readInt(from);
716-
options.pathsToDelete = WorkerProto::Serialise<StorePathSet>::read(*store, rconn);
717-
from >> options.ignoreLiveness >> options.maxFreed;
715+
int action;
716+
bool ignoreLiveness;
717+
StorePathSet pathsToDelete;
718+
bool skipAlive {false};
719+
action = readInt(from);
720+
pathsToDelete = WorkerProto::Serialise<StorePathSet>::read(*store, rconn);
721+
from >> ignoreLiveness >> options.maxFreed;
718722
// obsolete fields
719723
readInt(from);
720724
readInt(from);
721725
readInt(from);
726+
if (GET_PROTOCOL_MINOR(clientVersion) >= 31) {
727+
from >> recursive;
728+
};
729+
730+
switch (action) {
731+
case 0:
732+
options.action = GCReturn::Live; break;
733+
case 1:
734+
options.action = GCReturn::Dead; break;
735+
case 2:
736+
options.action = GCDelete { .pathsToDelete = std::nullopt, .ignoreLiveness = ignoreLiveness }; break;
737+
case 3:
738+
options.action = GCDelete { .pathsToDelete = GCPathsToDelete { .paths = pathsToDelete, .skipAlive = skipAlive }, .ignoreLiveness = ignoreLiveness, }; break;
739+
};
722740

723741
GCResults results;
724742

725743
logger->startWork();
726-
if (options.ignoreLiveness)
744+
if (ignoreLiveness)
727745
throw Error("you are not allowed to ignore liveness");
728746
auto & gcStore = require<GcStore>(*store);
729747
gcStore.collectGarbage(options, results);

src/libstore/gc-store.hh

Lines changed: 37 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,51 +9,45 @@ namespace nix {
99

1010
typedef std::unordered_map<StorePath, std::unordered_set<std::string>> Roots;
1111

12+
/**
13+
* Return either live (reachable) or dead (unreachable) paths
14+
*/
15+
enum class GCReturn { Live, Dead };
16+
17+
/**
18+
* Set of paths to delete, and whether to skip paths which are alive
19+
*/
20+
struct GCPathsToDelete {
21+
StorePathSet paths;
22+
bool skipAlive;
23+
};
1224

13-
struct GCOptions
14-
{
15-
/**
16-
* Garbage collector operation:
17-
*
18-
* - `gcReturnLive`: return the set of paths reachable from
19-
* (i.e. in the closure of) the roots.
20-
*
21-
* - `gcReturnDead`: return the set of paths not reachable from
22-
* the roots.
23-
*
24-
* - `gcDeleteDead`: actually delete the latter set.
25-
*
26-
* - `gcDeleteSpecific`: delete the paths listed in
27-
* `pathsToDelete`, insofar as they are not reachable.
28-
*/
29-
typedef enum {
30-
gcReturnLive,
31-
gcReturnDead,
32-
gcDeleteDead,
33-
gcDeleteSpecific,
34-
} GCAction;
35-
36-
GCAction action{gcDeleteDead};
37-
38-
/**
39-
* If `ignoreLiveness` is set, then reachability from the roots is
40-
* ignored (dangerous!). However, the paths must still be
41-
* unreferenced *within* the store (i.e., there can be no other
42-
* store paths that depend on them).
43-
*/
44-
bool ignoreLiveness{false};
45-
46-
/**
47-
* For `gcDeleteSpecific`, the paths to delete.
48-
*/
49-
StorePathSet pathsToDelete;
50-
51-
/**
52-
* Stop after at least `maxFreed` bytes have been freed.
53-
*/
54-
uint64_t maxFreed{std::numeric_limits<uint64_t>::max()};
25+
/**
26+
Delete either a given set of paths, or all dead paths
27+
*/
28+
struct GCDelete {
29+
/* Delete this set, or all dead paths if it is std::nullopt */
30+
std::optional<GCPathsToDelete> pathsToDelete;
31+
/* If `ignoreLiveness' is set, then reachability from the roots is
32+
ignored (dangerous!). However, the paths must still be
33+
unreferenced *within* the store (i.e., there can be no other
34+
store paths that depend on them). */
35+
bool ignoreLiveness{false};
5536
};
5637

38+
/**
39+
* Garbage collection action: either return paths, or delete them
40+
*/
41+
using GCAction = std::variant<GCReturn, GCDelete>;
42+
43+
/**
44+
* Options for the garbage collector
45+
*/
46+
struct GCOptions {
47+
GCAction action;
48+
/* Stop after at least `maxFreed' bytes have been freed. */
49+
uint64_t maxFreed{std::numeric_limits<uint64_t>::max()};
50+
};
5751

5852
struct GCResults
5953
{
@@ -64,8 +58,7 @@ struct GCResults
6458
PathSet paths;
6559

6660
/**
67-
* For `gcReturnDead`, `gcDeleteDead` and `gcDeleteSpecific`, the
68-
* number of bytes that would be or was freed.
61+
* For `GCDelete', the number of bytes that would be or was freed.
6962
*/
7063
uint64_t bytesFreed = 0;
7164
};

src/libstore/gc.cc

Lines changed: 93 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,8 @@ void LocalStore::findRuntimeRoots(Roots & roots, bool censor)
367367
{
368368
UncheckedRoots unchecked;
369369

370+
std::string pids = std::to_string(getpid());
371+
370372
auto procDir = AutoCloseDir{opendir("/proc")};
371373
if (procDir) {
372374
struct dirent * ent;
@@ -375,7 +377,7 @@ void LocalStore::findRuntimeRoots(Roots & roots, bool censor)
375377
auto storePathRegex = std::regex(quoteRegexChars(storeDir) + R"(/[0-9a-z]+[0-9a-zA-Z\+\-\._\?=]*)");
376378
while (errno = 0, ent = readdir(procDir.get())) {
377379
checkInterrupt();
378-
if (std::regex_match(ent->d_name, digitsRegex)) {
380+
if (std::regex_match(ent->d_name, digitsRegex) && strcmp(pids.c_str(), ent->d_name)) {
379381
try {
380382
readProcLink(fmt("/proc/%s/exe" ,ent->d_name), unchecked);
381383
readProcLink(fmt("/proc/%s/cwd", ent->d_name), unchecked);
@@ -469,7 +471,7 @@ struct GCLimitReached { };
469471

470472
void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
471473
{
472-
bool shouldDelete = options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcDeleteSpecific;
474+
bool shouldDelete = std::holds_alternative<GCDelete>(options.action);
473475
bool gcKeepOutputs = settings.gcKeepOutputs;
474476
bool gcKeepDerivations = settings.gcKeepDerivations;
475477

@@ -494,10 +496,12 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
494496
consequences if `keep-outputs' or `keep-derivations' are true
495497
(the garbage collector will recurse into deleting the outputs
496498
or derivers, respectively). So disable them. */
497-
if (options.action == GCOptions::gcDeleteSpecific && options.ignoreLiveness) {
498-
gcKeepOutputs = false;
499-
gcKeepDerivations = false;
500-
}
499+
std::visit(overloaded{[&](GCDelete del){
500+
if (del.pathsToDelete.has_value() && del.ignoreLiveness) {
501+
gcKeepOutputs = false;
502+
gcKeepDerivations = false;
503+
}
504+
}, [](GCReturn arg){}}, options.action);
501505

502506
if (shouldDelete)
503507
deletePath(reservedPath);
@@ -617,8 +621,16 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
617621
permanent roots cannot increase now. */
618622
printInfo("finding garbage collector roots...");
619623
Roots rootMap;
620-
if (!options.ignoreLiveness)
621-
findRootsNoTemp(rootMap, true);
624+
625+
std::visit(overloaded{
626+
[&](GCDelete del) {
627+
if (!del.ignoreLiveness)
628+
findRootsNoTemp(rootMap, true);
629+
},
630+
[&](GCReturn r){
631+
findRootsNoTemp(rootMap, true);
632+
}},
633+
options.action);
622634

623635
for (auto & i : rootMap) roots.insert(i.first);
624636

@@ -721,9 +733,9 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
721733
return markAlive();
722734
}
723735

724-
if (options.action == GCOptions::gcDeleteSpecific
725-
&& !options.pathsToDelete.count(*path))
726-
return;
736+
if (const GCDelete * del = std::get_if<GCDelete>(&options.action))
737+
if (del->pathsToDelete.has_value() && !del->pathsToDelete->paths.count(*path))
738+
return;
727739

728740
{
729741
auto hashPart = std::string(path->hashPart());
@@ -781,27 +793,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
781793
if (auto p = getEnv("_NIX_TEST_GC_SYNC"))
782794
readFile(*p);
783795

784-
/* Either delete all garbage paths, or just the specified
785-
paths (for gcDeleteSpecific). */
786-
if (options.action == GCOptions::gcDeleteSpecific || options.action == GCOptions::gcDeleteDead) {
787-
788-
for (auto & i : options.pathsToDelete) {
789-
deleteReferrersClosure(i);
790-
if (options.action == GCOptions::gcDeleteSpecific && !dead.count(i))
791-
throw Error(
792-
"Cannot delete path '%1%' since it is still alive. "
793-
"To find out why, use: "
794-
"nix-store --query --roots",
795-
printStorePath(i));
796-
}
797-
798-
} else if (options.maxFreed > 0) {
799-
800-
if (shouldDelete)
801-
printInfo("deleting garbage...");
802-
else
803-
printInfo("determining live/dead paths...");
804-
796+
auto findOrDeleteRoots = [&](){
805797
try {
806798
AutoCloseDir dir(opendir(realStoreDir.get().c_str()));
807799
if (!dir) throw SysError("opening directory '%1%'", realStoreDir);
@@ -825,65 +817,87 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
825817
}
826818
} catch (GCLimitReached & e) {
827819
}
828-
}
820+
};
829821

830-
if (options.action == GCOptions::gcReturnLive) {
831-
for (auto & i : alive)
832-
results.paths.insert(printStorePath(i));
833-
return;
834-
}
822+
/* Either delete all garbage paths, or just the specified
823+
paths (for gcDeleteSpecific). */
824+
std::visit(overloaded{
825+
[&](GCDelete del){
826+
if (del.pathsToDelete.has_value()) {
827+
for (auto & i : del.pathsToDelete->paths) {
828+
deleteReferrersClosure(i);
829+
if (!del.pathsToDelete->skipAlive && !dead.count(i))
830+
throw Error(
831+
"Cannot delete path '%1%' since it is still alive. "
832+
"To find out why, use: "
833+
"nix-store --query --roots",
834+
printStorePath(i));
835+
}
836+
} else if (options.maxFreed > 0) {
837+
printInfo("deleting garbage...");
838+
findOrDeleteRoots();
839+
}
835840

836-
if (options.action == GCOptions::gcReturnDead) {
837-
for (auto & i : dead)
838-
results.paths.insert(printStorePath(i));
839-
return;
840-
}
841+
/* Unlink all files in /nix/store/.links that have a link count of 1,
842+
which indicates that there are no other links and so they can be
843+
safely deleted. FIXME: race condition with optimisePath(): we
844+
might see a link count of 1 just before optimisePath() increases
845+
the link count. */
841846

842-
/* Unlink all files in /nix/store/.links that have a link count of 1,
843-
which indicates that there are no other links and so they can be
844-
safely deleted. FIXME: race condition with optimisePath(): we
845-
might see a link count of 1 just before optimisePath() increases
846-
the link count. */
847-
if (options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcDeleteSpecific) {
848-
printInfo("deleting unused links...");
847+
printInfo("deleting unused links...");
849848

850-
AutoCloseDir dir(opendir(linksDir.c_str()));
851-
if (!dir) throw SysError("opening directory '%1%'", linksDir);
849+
AutoCloseDir dir(opendir(linksDir.c_str()));
850+
if (!dir) throw SysError("opening directory '%1%'", linksDir);
852851

853-
int64_t actualSize = 0, unsharedSize = 0;
852+
int64_t actualSize = 0, unsharedSize = 0;
854853

855-
struct dirent * dirent;
856-
while (errno = 0, dirent = readdir(dir.get())) {
857-
checkInterrupt();
858-
std::string name = dirent->d_name;
859-
if (name == "." || name == "..") continue;
860-
Path path = linksDir + "/" + name;
854+
struct dirent * dirent;
855+
while (errno = 0, dirent = readdir(dir.get())) {
856+
checkInterrupt();
857+
std::string name = dirent->d_name;
858+
if (name == "." || name == "..") continue;
859+
Path path = linksDir + "/" + name;
861860

862-
auto st = lstat(path);
861+
auto st = lstat(path);
863862

864-
if (st.st_nlink != 1) {
865-
actualSize += st.st_size;
866-
unsharedSize += (st.st_nlink - 1) * st.st_size;
867-
continue;
868-
}
863+
if (st.st_nlink != 1) {
864+
actualSize += st.st_size;
865+
unsharedSize += (st.st_nlink - 1) * st.st_size;
866+
continue;
867+
}
869868

870-
printMsg(lvlTalkative, "deleting unused link '%1%'", path);
869+
printMsg(lvlTalkative, "deleting unused link '%1%'", path);
871870

872-
if (unlink(path.c_str()) == -1)
873-
throw SysError("deleting '%1%'", path);
871+
if (unlink(path.c_str()) == -1)
872+
throw SysError("deleting '%1%'", path);
874873

875-
/* Do not accound for deleted file here. Rely on deletePath()
876-
accounting. */
877-
}
874+
/* Do not accound for deleted file here. Rely on deletePath()
875+
accounting. */
876+
}
878877

879-
struct stat st;
880-
if (stat(linksDir.c_str(), &st) == -1)
881-
throw SysError("statting '%1%'", linksDir);
882-
int64_t overhead = st.st_blocks * 512ULL;
878+
struct stat st;
879+
if (stat(linksDir.c_str(), &st) == -1)
880+
throw SysError("statting '%1%'", linksDir);
881+
int64_t overhead = st.st_blocks * 512ULL;
883882

884-
printInfo("note: currently hard linking saves %.2f MiB",
885-
((unsharedSize - actualSize - overhead) / (1024.0 * 1024.0)));
886-
}
883+
printInfo("note: currently hard linking saves %.2f MiB",
884+
((unsharedSize - actualSize - overhead) / (1024.0 * 1024.0)));
885+
},
886+
[&](GCReturn ret){
887+
printInfo("determining live/dead paths...");
888+
findOrDeleteRoots();
889+
if (ret == GCReturn::Live) {
890+
for (auto & i : alive)
891+
results.paths.insert(printStorePath(i));
892+
return;
893+
}
894+
895+
if (ret == GCReturn::Dead) {
896+
for (auto & i : dead)
897+
results.paths.insert(printStorePath(i));
898+
return;
899+
}
900+
}}, options.action);
887901

888902
/* While we're at it, vacuum the database. */
889903
//if (options.action == GCOptions::gcDeleteDead) vacuumDB();
@@ -945,8 +959,7 @@ void LocalStore::autoGC(bool sync)
945959
promise.set_value();
946960
});
947961

948-
GCOptions options;
949-
options.maxFreed = settings.maxFree - avail;
962+
GCOptions options{.action = GCAction{GCDelete{}}, .maxFreed = settings.maxFree - avail};
950963

951964
printInfo("running auto-GC to free %d bytes", options.maxFreed);
952965

0 commit comments

Comments
 (0)