about summary refs log tree commit diff
path: root/src/libstore/gc.cc
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2017-09-05T18·43+0200
committerEelco Dolstra <edolstra@gmail.com>2017-09-05T18·43+0200
commit0b606aad46e1d96da36d4831df63ad90f11d21c3 (patch)
tree5787f4f043f7316a8ad4625772abe9a4da3c1d57 /src/libstore/gc.cc
parentb932ea58ec610830ed3141bb14fbd812aa66b2c1 (diff)
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").
Diffstat (limited to 'src/libstore/gc.cc')
-rw-r--r--src/libstore/gc.cc70
1 files changed, 70 insertions, 0 deletions
diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc
index 534db8c6e4fe..cf95f7f450bd 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 <functional>
 #include <queue>
@@ -9,6 +10,7 @@
 
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/statvfs.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <unistd.h>
@@ -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<void> 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<void> 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();
+}
+
+
 }