Skip to content

Commit 484fb32

Browse files
committed
Merge branch 'master' of https://github.com/NixOS/nix into upstream/master
2 parents d2c97b4 + 1277aab commit 484fb32

File tree

6 files changed

+169
-35
lines changed

6 files changed

+169
-35
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: 105 additions & 30 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>
@@ -18,7 +20,6 @@ namespace nix {
1820

1921

2022
static string gcLockName = "gc.lock";
21-
static string tempRootsDir = "temproots";
2223
static string gcRootsDir = "gcroots";
2324

2425

@@ -153,30 +154,25 @@ void LocalStore::addTempRoot(const Path & path)
153154
if (!state->fdTempRoots) {
154155

155156
while (1) {
156-
Path dir = (format("%1%/%2%") % stateDir % tempRootsDir).str();
157-
createDirs(dir);
158-
159-
state->fnTempRoots = (format("%1%/%2%") % dir % getpid()).str();
160-
161157
AutoCloseFD fdGCLock = openGCLock(ltRead);
162158

163-
if (pathExists(state->fnTempRoots))
159+
if (pathExists(fnTempRoots))
164160
/* It *must* be stale, since there can be no two
165161
processes with the same pid. */
166-
unlink(state->fnTempRoots.c_str());
162+
unlink(fnTempRoots.c_str());
167163

168-
state->fdTempRoots = openLockFile(state->fnTempRoots, true);
164+
state->fdTempRoots = openLockFile(fnTempRoots, true);
169165

170166
fdGCLock = -1;
171167

172-
debug(format("acquiring read lock on '%1%'") % state->fnTempRoots);
168+
debug(format("acquiring read lock on '%1%'") % fnTempRoots);
173169
lockFile(state->fdTempRoots.get(), ltRead, true);
174170

175171
/* Check whether the garbage collector didn't get in our
176172
way. */
177173
struct stat st;
178174
if (fstat(state->fdTempRoots.get(), &st) == -1)
179-
throw SysError(format("statting '%1%'") % state->fnTempRoots);
175+
throw SysError(format("statting '%1%'") % fnTempRoots);
180176
if (st.st_size == 0) break;
181177

182178
/* The garbage collector deleted this file before we could
@@ -188,14 +184,14 @@ void LocalStore::addTempRoot(const Path & path)
188184

189185
/* Upgrade the lock to a write lock. This will cause us to block
190186
if the garbage collector is holding our lock. */
191-
debug(format("acquiring write lock on '%1%'") % state->fnTempRoots);
187+
debug(format("acquiring write lock on '%1%'") % fnTempRoots);
192188
lockFile(state->fdTempRoots.get(), ltWrite, true);
193189

194190
string s = path + '\0';
195191
writeFull(state->fdTempRoots.get(), s);
196192

197193
/* Downgrade to a read lock. */
198-
debug(format("downgrading to read lock on '%1%'") % state->fnTempRoots);
194+
debug(format("downgrading to read lock on '%1%'") % fnTempRoots);
199195
lockFile(state->fdTempRoots.get(), ltRead, true);
200196
}
201197

@@ -204,11 +200,10 @@ void LocalStore::readTempRoots(PathSet & tempRoots, FDs & fds)
204200
{
205201
/* Read the `temproots' directory for per-process temporary root
206202
files. */
207-
DirEntries tempRootFiles = readDirectory(
208-
(format("%1%/%2%") % stateDir % tempRootsDir).str());
203+
DirEntries tempRootFiles = readDirectory(tempRootsDir);
209204

210205
for (auto & i : tempRootFiles) {
211-
Path path = (format("%1%/%2%/%3%") % stateDir % tempRootsDir % i.name).str();
206+
Path path = tempRootsDir + "/" + i.name;
212207

213208
debug(format("reading temporary root file '%1%'") % path);
214209
FDPtr fd(new AutoCloseFD(open(path.c_str(), O_CLOEXEC | O_RDWR, 0666)));
@@ -222,21 +217,25 @@ void LocalStore::readTempRoots(PathSet & tempRoots, FDs & fds)
222217
//FDPtr fd(new AutoCloseFD(openLockFile(path, false)));
223218
//if (*fd == -1) continue;
224219

225-
/* Try to acquire a write lock without blocking. This can
226-
only succeed if the owning process has died. In that case
227-
we don't care about its temporary roots. */
228-
if (lockFile(fd->get(), ltWrite, false)) {
229-
printError(format("removing stale temporary roots file '%1%'") % path);
230-
unlink(path.c_str());
231-
writeFull(fd->get(), "d");
232-
continue;
233-
}
220+
if (path != fnTempRoots) {
221+
222+
/* Try to acquire a write lock without blocking. This can
223+
only succeed if the owning process has died. In that case
224+
we don't care about its temporary roots. */
225+
if (lockFile(fd->get(), ltWrite, false)) {
226+
printError(format("removing stale temporary roots file '%1%'") % path);
227+
unlink(path.c_str());
228+
writeFull(fd->get(), "d");
229+
continue;
230+
}
231+
232+
/* Acquire a read lock. This will prevent the owning process
233+
from upgrading to a write lock, therefore it will block in
234+
addTempRoot(). */
235+
debug(format("waiting for read lock on '%1%'") % path);
236+
lockFile(fd->get(), ltRead, true);
234237

235-
/* Acquire a read lock. This will prevent the owning process
236-
from upgrading to a write lock, therefore it will block in
237-
addTempRoot(). */
238-
debug(format("waiting for read lock on '%1%'") % path);
239-
lockFile(fd->get(), ltRead, true);
238+
}
240239

241240
/* Read the entire file. */
242241
string contents = readFile(fd->get());
@@ -848,4 +847,80 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
848847
}
849848

850849

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+
try {
891+
892+
/* Wake up any threads waiting for the auto-GC to finish. */
893+
Finally wakeup([&]() {
894+
auto state(_state.lock());
895+
state->gcRunning = false;
896+
state->lastGCCheck = std::chrono::steady_clock::now();
897+
promise.set_value();
898+
});
899+
900+
printInfo("running auto-GC to free %d bytes", settings.maxFree - avail);
901+
902+
GCOptions options;
903+
options.maxFreed = settings.maxFree - avail;
904+
905+
GCResults results;
906+
907+
collectGarbage(options, results);
908+
909+
_state.lock()->availAfterGC = getAvail();
910+
911+
} catch (...) {
912+
// FIXME: we could propagate the exception to the
913+
// future, but we don't really care.
914+
ignoreException();
915+
}
916+
917+
}).detach();
918+
}
919+
920+
sync:
921+
// Wait for the future outside of the state lock.
922+
if (sync) future.get();
923+
}
924+
925+
851926
}

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: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ LocalStore::LocalStore(const Params & params)
5151
, reservedPath(dbDir + "/reserved")
5252
, schemaPath(dbDir + "/schema")
5353
, trashDir(realStoreDir + "/trash")
54+
, tempRootsDir(stateDir + "/temproots")
55+
, fnTempRoots(fmt("%s/%d", tempRootsDir, getpid()))
5456
, publicKeys(getDefaultPublicKeys())
5557
{
5658
auto state(_state.lock());
@@ -61,7 +63,7 @@ LocalStore::LocalStore(const Params & params)
6163
createDirs(linksDir);
6264
Path profilesDir = stateDir + "/profiles";
6365
createDirs(profilesDir);
64-
createDirs(stateDir + "/temproots");
66+
createDirs(tempRootsDir);
6567
createDirs(dbDir);
6668
Path gcRootsDir = stateDir + "/gcroots";
6769
if (!pathExists(gcRootsDir)) {
@@ -242,12 +244,24 @@ LocalStore::LocalStore(const Params & params)
242244

243245
LocalStore::~LocalStore()
244246
{
245-
auto state(_state.lock());
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+
}
246259

247260
try {
261+
auto state(_state.lock());
248262
if (state->fdTempRoots) {
249263
state->fdTempRoots = -1;
250-
unlink(state->fnTempRoots.c_str());
264+
unlink(fnTempRoots.c_str());
251265
}
252266
} catch (...) {
253267
ignoreException();
@@ -989,6 +1003,8 @@ void LocalStore::addToStore(const ValidPathInfo & info, const ref<std::string> &
9891003
StringSource source(*nar);
9901004
restorePath(realPath, source);
9911005

1006+
autoGC();
1007+
9921008
canonicalisePathMetaData(realPath, -1);
9931009

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

10241040
deletePath(realPath);
10251041

1042+
autoGC();
1043+
10261044
if (recursive) {
10271045
StringSource source(dump);
10281046
restorePath(realPath, source);
@@ -1095,6 +1113,8 @@ Path LocalStore::addTextToStore(const string & name, const string & s,
10951113

10961114
deletePath(realPath);
10971115

1116+
autoGC();
1117+
10981118
writeFile(realPath, s);
10991119

11001120
canonicalisePathMetaData(realPath, -1);

src/libstore/local-store.hh

Lines changed: 23 additions & 1 deletion
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

@@ -59,8 +61,22 @@ private:
5961
SQLiteStmt stmtQueryValidPaths;
6062

6163
/* The file to which we write our temporary roots. */
62-
Path fnTempRoots;
6364
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();
6480
};
6581

6682
Sync<State, std::recursive_mutex> _state;
@@ -75,6 +91,8 @@ public:
7591
const Path reservedPath;
7692
const Path schemaPath;
7793
const Path trashDir;
94+
const Path tempRootsDir;
95+
const Path fnTempRoots;
7896

7997
private:
8098

@@ -195,6 +213,10 @@ public:
195213

196214
void addSignatures(const Path & storePath, const StringSet & sigs) override;
197215

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+
198220
private:
199221

200222
int getSchema();

0 commit comments

Comments
 (0)