about summary refs log tree commit diff
path: root/src/libstore/gc.cc
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2012-03-26T18·43+0200
committerEelco Dolstra <eelco.dolstra@logicblox.com>2012-03-26T18·43+0200
commit117670be570d775a18e4e35db3dae00abc24f729 (patch)
tree426cc5f0185457c719eee2287b769202311784d8 /src/libstore/gc.cc
parent8be1979f1ae4b85c36988f7b3cf8240c9682dc54 (diff)
Make the garbage collector more concurrent
Make the garbage collector more concurrent by deleting valid paths
outside the region where we're holding the global GC lock.  This
should greatly reduce the time during which new builds are blocked,
since the deletion accounts for the vast majority of the time spent in
the GC.

To ensure that this is safe, the valid paths are invalidated and
renamed to some arbitrary path while we're holding the lock.  This
ensures that we when we finally delete the path, it's not a (newly)
valid or locked path.
Diffstat (limited to 'src/libstore/gc.cc')
-rw-r--r--src/libstore/gc.cc53
1 files changed, 42 insertions, 11 deletions
diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc
index d81bf40c73..65c8dc210b 100644
--- a/src/libstore/gc.cc
+++ b/src/libstore/gc.cc
@@ -1,6 +1,7 @@
 #include "globals.hh"
 #include "misc.hh"
 #include "local-store.hh"
+#include "immutable.hh"
 
 #include <boost/shared_ptr.hpp>
 
@@ -396,11 +397,11 @@ struct LocalStore::GCState
     PathSet deleted;
     PathSet live;
     PathSet busy;
+    PathSet invalidated;
     bool gcKeepOutputs;
     bool gcKeepDerivations;
-    GCState(GCResults & results_) : results(results_)
-    {
-    }
+    unsigned long long bytesInvalidated;
+    GCState(GCResults & results_) : results(results_), bytesInvalidated(0) { }
 };
 
 
@@ -419,6 +420,16 @@ bool LocalStore::isActiveTempFile(const GCState & state,
 }
 
 
+void LocalStore::deleteGarbage(GCState & state, const Path & path)
+{
+    printMsg(lvlInfo, format("deleting `%1%'") % path);
+    unsigned long long bytesFreed, blocksFreed;
+    deletePathWrapped(path, bytesFreed, blocksFreed);
+    state.results.bytesFreed += bytesFreed;
+    state.results.blocksFreed += blocksFreed;
+}
+
+
 bool LocalStore::tryToDelete(GCState & state, const Path & path)
 {
     checkInterrupt();
@@ -502,15 +513,27 @@ bool LocalStore::tryToDelete(GCState & state, const Path & path)
 
     /* The path is garbage, so delete it. */
     if (shouldDelete(state.options.action)) {
-        printMsg(lvlInfo, format("deleting `%1%'") % path);
-
-        unsigned long long bytesFreed, blocksFreed;
-        deleteFromStore(path, bytesFreed, blocksFreed);
-        state.results.bytesFreed += bytesFreed;
-        state.results.blocksFreed += blocksFreed;
 
-        if (state.options.maxFreed && state.results.bytesFreed > state.options.maxFreed) {
-            printMsg(lvlInfo, format("deleted more than %1% bytes; stopping") % state.options.maxFreed);
+        if (isValidPath(path)) {
+            /* If it's a valid path, invalidate it, rename it, and
+               schedule it for deletion.  The renaming is to ensure
+               that later (when we're not holding the global GC lock)
+               we can delete the path without being afraid that the
+               path has become alive again. */
+            printMsg(lvlInfo, format("invalidating `%1%'") % path);
+            // Estimate the amount freed using the narSize field.
+            state.bytesInvalidated += queryPathInfo(path).narSize;
+            invalidatePathChecked(path);
+            Path tmp = (format("%1%-gc-%2%") % path % getpid()).str();
+            makeMutable(path.c_str());
+            if (rename(path.c_str(), tmp.c_str()))
+                throw SysError(format("unable to rename `%1%' to `%2%'") % path % tmp);
+            state.invalidated.insert(tmp);
+        } else
+            deleteGarbage(state, path);
+
+        if (state.options.maxFreed && state.results.bytesFreed + state.bytesInvalidated > state.options.maxFreed) {
+            printMsg(lvlInfo, format("deleted or invalidated more than %1% bytes; stopping") % state.options.maxFreed);
             throw GCLimitReached();
         }
 
@@ -635,6 +658,14 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
         } catch (GCLimitReached & e) {
         }
     }
+
+    /* Allow other processes to add to the store from here on. */
+    fdGCLock.close();
+
+    /* Delete the invalidated paths now that the lock has been
+       released. */
+    foreach (PathSet::iterator, i, state.invalidated)
+        deleteGarbage(state, *i);
 }