diff --git a/doc/manual/release-notes/rl-1.12.xml b/doc/manual/release-notes/rl-1.12.xml
index d9bdd9edd..033c9b971 100644
--- a/doc/manual/release-notes/rl-1.12.xml
+++ b/doc/manual/release-notes/rl-1.12.xml
@@ -393,6 +393,10 @@ configureFlags = "--prefix=${placeholder "out"} --includedir=${placeholder "dev"
package repository.
+
+ Automatic garbage collection.
+
+
This release has contributions from TBD.
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index ce41752e6..ddf4bf00d 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -3957,6 +3957,8 @@ void Worker::run(const Goals & _topGoals)
checkInterrupt();
+ store.autoGC(false);
+
/* Call every wake goal (in the ordering established by
CompareGoalPtrs). */
while (!awake.empty() && !topGoals.empty()) {
@@ -4014,6 +4016,9 @@ void Worker::waitForInput()
is a build timeout, then wait for input until the first
deadline for any child. */
auto nearest = steady_time_point::max(); // nearest deadline
+ if (settings.minFree.get() != 0)
+ // Periodicallty wake up to see if we need to run the garbage collector.
+ nearest = before + std::chrono::seconds(10);
for (auto & i : children) {
if (!i.respectTimeouts) continue;
if (0 != settings.maxSilentTime)
diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc
index 534db8c6e..cf95f7f45 100644
--- a/src/libstore/gc.cc
+++ b/src/libstore/gc.cc
@@ -1,6 +1,7 @@
#include "derivations.hh"
#include "globals.hh"
#include "local-store.hh"
+#include "finally.hh"
#include
#include
@@ -9,6 +10,7 @@
#include
#include
+#include
#include
#include
#include
@@ -845,4 +847,72 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
}
+void LocalStore::autoGC(bool sync)
+{
+ auto getAvail = [this]() {
+ struct statvfs st;
+ if (statvfs(realStoreDir.c_str(), &st))
+ throw SysError("getting filesystem info about '%s'", realStoreDir);
+
+ return (uint64_t) st.f_bavail * st.f_bsize;
+ };
+
+ std::shared_future future;
+
+ {
+ auto state(_state.lock());
+
+ if (state->gcRunning) {
+ future = state->gcFuture;
+ debug("waiting for auto-GC to finish");
+ goto sync;
+ }
+
+ auto now = std::chrono::steady_clock::now();
+
+ if (now < state->lastGCCheck + std::chrono::seconds(5)) return;
+
+ auto avail = getAvail();
+
+ state->lastGCCheck = now;
+
+ if (avail >= settings.minFree || avail >= settings.maxFree) return;
+
+ if (avail > state->availAfterGC * 0.97) return;
+
+ state->gcRunning = true;
+
+ std::promise promise;
+ future = state->gcFuture = promise.get_future().share();
+
+ std::thread([promise{std::move(promise)}, this, avail, getAvail]() mutable {
+
+ /* Wake up any threads waiting for the auto-GC to finish. */
+ Finally wakeup([&]() {
+ auto state(_state.lock());
+ state->gcRunning = false;
+ state->lastGCCheck = std::chrono::steady_clock::now();
+ promise.set_value();
+ });
+
+ printInfo("running auto-GC to free %d bytes", settings.maxFree - avail);
+
+ GCOptions options;
+ options.maxFreed = settings.maxFree - avail;
+
+ GCResults results;
+
+ collectGarbage(options, results);
+
+ _state.lock()->availAfterGC = getAvail();
+
+ }).detach();
+ }
+
+ sync:
+ // Wait for the future outside of the state lock.
+ if (sync) future.get();
+}
+
+
}
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index c20d147f5..41d332311 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -4,8 +4,9 @@
#include "config.hh"
#include