Skip to content

Commit 0b606aa

Browse files
committed
Add automatic garbage collection
Nix can now automatically run the garbage collector during builds or while adding paths to the store. The option "min-free = <bytes>" specifies that Nix should run the garbage collector whenever free space in the Nix store drops below <bytes>. It will then delete garbage until "max-free" bytes are available. Garbage collection during builds is asynchronous; running builds are not paused and new builds are not blocked. However, there also is a synchronous GC run prior to the first build/substitution. Currently, no old GC roots are deleted (as in "nix-collect-garbage -d").
1 parent b932ea5 commit 0b606aa

File tree

6 files changed

+127
-1
lines changed

6 files changed

+127
-1
lines changed

doc/manual/release-notes/rl-1.12.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,10 @@ configureFlags = "--prefix=${placeholder "out"} --includedir=${placeholder "dev"
393393
package repository.</para>
394394
</listitem>
395395

396+
<listitem>
397+
<para>Automatic garbage collection.</para>
398+
</listitem>
399+
396400
</itemizedlist>
397401

398402
<para>This release has contributions from TBD.</para>

src/libstore/build.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3957,6 +3957,8 @@ void Worker::run(const Goals & _topGoals)
39573957

39583958
checkInterrupt();
39593959

3960+
store.autoGC(false);
3961+
39603962
/* Call every wake goal (in the ordering established by
39613963
CompareGoalPtrs). */
39623964
while (!awake.empty() && !topGoals.empty()) {
@@ -4014,6 +4016,9 @@ void Worker::waitForInput()
40144016
is a build timeout, then wait for input until the first
40154017
deadline for any child. */
40164018
auto nearest = steady_time_point::max(); // nearest deadline
4019+
if (settings.minFree.get() != 0)
4020+
// Periodicallty wake up to see if we need to run the garbage collector.
4021+
nearest = before + std::chrono::seconds(10);
40174022
for (auto & i : children) {
40184023
if (!i.respectTimeouts) continue;
40194024
if (0 != settings.maxSilentTime)

src/libstore/gc.cc

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "derivations.hh"
22
#include "globals.hh"
33
#include "local-store.hh"
4+
#include "finally.hh"
45

56
#include <functional>
67
#include <queue>
@@ -9,6 +10,7 @@
910

1011
#include <sys/types.h>
1112
#include <sys/stat.h>
13+
#include <sys/statvfs.h>
1214
#include <errno.h>
1315
#include <fcntl.h>
1416
#include <unistd.h>
@@ -845,4 +847,72 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
845847
}
846848

847849

850+
void LocalStore::autoGC(bool sync)
851+
{
852+
auto getAvail = [this]() {
853+
struct statvfs st;
854+
if (statvfs(realStoreDir.c_str(), &st))
855+
throw SysError("getting filesystem info about '%s'", realStoreDir);
856+
857+
return (uint64_t) st.f_bavail * st.f_bsize;
858+
};
859+
860+
std::shared_future<void> future;
861+
862+
{
863+
auto state(_state.lock());
864+
865+
if (state->gcRunning) {
866+
future = state->gcFuture;
867+
debug("waiting for auto-GC to finish");
868+
goto sync;
869+
}
870+
871+
auto now = std::chrono::steady_clock::now();
872+
873+
if (now < state->lastGCCheck + std::chrono::seconds(5)) return;
874+
875+
auto avail = getAvail();
876+
877+
state->lastGCCheck = now;
878+
879+
if (avail >= settings.minFree || avail >= settings.maxFree) return;
880+
881+
if (avail > state->availAfterGC * 0.97) return;
882+
883+
state->gcRunning = true;
884+
885+
std::promise<void> promise;
886+
future = state->gcFuture = promise.get_future().share();
887+
888+
std::thread([promise{std::move(promise)}, this, avail, getAvail]() mutable {
889+
890+
/* Wake up any threads waiting for the auto-GC to finish. */
891+
Finally wakeup([&]() {
892+
auto state(_state.lock());
893+
state->gcRunning = false;
894+
state->lastGCCheck = std::chrono::steady_clock::now();
895+
promise.set_value();
896+
});
897+
898+
printInfo("running auto-GC to free %d bytes", settings.maxFree - avail);
899+
900+
GCOptions options;
901+
options.maxFreed = settings.maxFree - avail;
902+
903+
GCResults results;
904+
905+
collectGarbage(options, results);
906+
907+
_state.lock()->availAfterGC = getAvail();
908+
909+
}).detach();
910+
}
911+
912+
sync:
913+
// Wait for the future outside of the state lock.
914+
if (sync) future.get();
915+
}
916+
917+
848918
}

src/libstore/globals.hh

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
#include "config.hh"
55

66
#include <map>
7-
#include <sys/types.h>
7+
#include <limits>
88

9+
#include <sys/types.h>
910

1011
namespace nix {
1112

@@ -342,6 +343,13 @@ public:
342343

343344
Setting<Strings> hashedMirrors{this, {"http://tarballs.nixos.org/"}, "hashed-mirrors",
344345
"A list of servers used by builtins.fetchurl to fetch files by hash."};
346+
347+
Setting<uint64_t> minFree{this, 0, "min-free",
348+
"Automatically run the garbage collector when free disk space drops below the specified amount."};
349+
350+
Setting<uint64_t> maxFree{this, std::numeric_limits<uint64_t>::max(), "max-free",
351+
"Stop deleting garbage when free disk space is above the specified amount."};
352+
345353
};
346354

347355

src/libstore/local-store.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,18 @@ LocalStore::LocalStore(const Params & params)
244244

245245
LocalStore::~LocalStore()
246246
{
247+
std::shared_future<void> future;
248+
249+
{
250+
auto state(_state.lock());
251+
if (state->gcRunning)
252+
future = state->gcFuture;
253+
}
254+
255+
if (future.valid()) {
256+
printError("waiting for auto-GC to finish on exit...");
257+
future.get();
258+
}
247259

248260
try {
249261
auto state(_state.lock());
@@ -991,6 +1003,8 @@ void LocalStore::addToStore(const ValidPathInfo & info, const ref<std::string> &
9911003
StringSource source(*nar);
9921004
restorePath(realPath, source);
9931005

1006+
autoGC();
1007+
9941008
canonicalisePathMetaData(realPath, -1);
9951009

9961010
optimisePath(realPath); // FIXME: combine with hashPath()
@@ -1025,6 +1039,8 @@ Path LocalStore::addToStoreFromDump(const string & dump, const string & name,
10251039

10261040
deletePath(realPath);
10271041

1042+
autoGC();
1043+
10281044
if (recursive) {
10291045
StringSource source(dump);
10301046
restorePath(realPath, source);
@@ -1097,6 +1113,8 @@ Path LocalStore::addTextToStore(const string & name, const string & s,
10971113

10981114
deletePath(realPath);
10991115

1116+
autoGC();
1117+
11001118
writeFile(realPath, s);
11011119

11021120
canonicalisePathMetaData(realPath, -1);

src/libstore/local-store.hh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#include "sync.hh"
88
#include "util.hh"
99

10+
#include <chrono>
11+
#include <future>
1012
#include <string>
1113
#include <unordered_set>
1214

@@ -60,6 +62,21 @@ private:
6062

6163
/* The file to which we write our temporary roots. */
6264
AutoCloseFD fdTempRoots;
65+
66+
/* The last time we checked whether to do an auto-GC, or an
67+
auto-GC finished. */
68+
std::chrono::time_point<std::chrono::steady_clock> lastGCCheck;
69+
70+
/* Whether auto-GC is running. If so, get gcFuture to wait for
71+
the GC to finish. */
72+
bool gcRunning = false;
73+
std::shared_future<void> gcFuture;
74+
75+
/* How much disk space was available after the previous
76+
auto-GC. If the current available disk space is below
77+
minFree but not much below availAfterGC, then there is no
78+
point in starting a new GC. */
79+
uint64_t availAfterGC = std::numeric_limits<uint64_t>::max();
6380
};
6481

6582
Sync<State, std::recursive_mutex> _state;
@@ -196,6 +213,10 @@ public:
196213

197214
void addSignatures(const Path & storePath, const StringSet & sigs) override;
198215

216+
/* If free disk space in /nix/store if below minFree, delete
217+
garbage until it exceeds maxFree. */
218+
void autoGC(bool sync = true);
219+
199220
private:
200221

201222
int getSchema();

0 commit comments

Comments
 (0)