libstore: asyncify LocalStore::autoGC
sadly we need both a synchronous and an asynchronous promise for this since destructors cannot be async. we also cannot use forked promises since multiple threads may be waiting for an auto-gc to complete, but forked promises are bound to an event loop (and thus a single thread) Change-Id: I7b4fdbd229c4a1e01bbf80858e93347024f2e333
This commit is contained in:
@@ -313,7 +313,7 @@ kj::Promise<Result<Worker::Results>> Worker::boopGC(LocalStore & localStore)
|
||||
try {
|
||||
while (true) {
|
||||
co_await AIO().provider.getTimer().afterDelay(10 * kj::SECONDS);
|
||||
localStore.autoGC(false);
|
||||
TRY_AWAIT(localStore.autoGC(false));
|
||||
}
|
||||
} catch (...) {
|
||||
co_return result::current_exception();
|
||||
|
||||
+20
-9
@@ -8,6 +8,7 @@
|
||||
#include "lix/libutil/strings.hh"
|
||||
#include "lix/libutil/thread-name.hh"
|
||||
|
||||
#include <kj/async.h>
|
||||
#include <queue>
|
||||
#include <regex>
|
||||
|
||||
@@ -868,8 +869,8 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||
}
|
||||
|
||||
|
||||
void LocalStore::autoGC(bool sync)
|
||||
{
|
||||
kj::Promise<Result<void>> LocalStore::autoGC(bool sync)
|
||||
try {
|
||||
static auto fakeFreeSpaceFile = getEnv("_NIX_TEST_FREE_SPACE_FILE");
|
||||
|
||||
auto getAvail = [this]() -> uint64_t {
|
||||
@@ -883,33 +884,35 @@ void LocalStore::autoGC(bool sync)
|
||||
return (uint64_t) st.f_bavail * st.f_frsize;
|
||||
};
|
||||
|
||||
std::shared_future<void> future;
|
||||
auto pfp = kj::newPromiseAndCrossThreadFulfiller<void>();
|
||||
|
||||
{
|
||||
auto state(_gcState.lock());
|
||||
|
||||
if (state->gcRunning) {
|
||||
future = state->gcFuture;
|
||||
state->gcWaiters.push_back(std::move(pfp.fulfiller));
|
||||
debug("waiting for auto-GC to finish");
|
||||
goto sync;
|
||||
}
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (now < state->lastGCCheck + std::chrono::seconds(settings.minFreeCheckInterval)) return;
|
||||
if (now < state->lastGCCheck + std::chrono::seconds(settings.minFreeCheckInterval)) {
|
||||
co_return result::success();
|
||||
}
|
||||
|
||||
auto avail = getAvail();
|
||||
|
||||
state->lastGCCheck = now;
|
||||
|
||||
if (avail >= settings.minFree || avail >= settings.maxFree) return;
|
||||
if (avail >= settings.minFree || avail >= settings.maxFree) co_return result::success();
|
||||
|
||||
if (avail > state->availAfterGC * 0.97) return;
|
||||
if (avail > state->availAfterGC * 0.97) co_return result::success();
|
||||
|
||||
state->gcRunning = true;
|
||||
|
||||
std::promise<void> promise;
|
||||
future = state->gcFuture = promise.get_future().share();
|
||||
state->gcFuture = promise.get_future();
|
||||
|
||||
std::thread([promise{std::move(promise)}, this, avail, getAvail]() mutable {
|
||||
setCurrentThreadName("auto gc");
|
||||
@@ -922,6 +925,10 @@ void LocalStore::autoGC(bool sync)
|
||||
state->gcRunning = false;
|
||||
state->lastGCCheck = std::chrono::steady_clock::now();
|
||||
promise.set_value();
|
||||
for (auto & waiter : state->gcWaiters) {
|
||||
waiter->fulfill();
|
||||
}
|
||||
state->gcWaiters.clear();
|
||||
});
|
||||
|
||||
GCOptions options;
|
||||
@@ -946,7 +953,11 @@ void LocalStore::autoGC(bool sync)
|
||||
|
||||
sync:
|
||||
// Wait for the future outside of the state lock.
|
||||
if (sync) future.get();
|
||||
if (sync) co_await pfp.promise;
|
||||
|
||||
co_return result::success();
|
||||
} catch (...) {
|
||||
co_return result::current_exception();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -458,12 +458,12 @@ AutoCloseFD LocalStore::openGCLock()
|
||||
|
||||
LocalStore::~LocalStore()
|
||||
{
|
||||
std::shared_future<void> future;
|
||||
std::future<void> future;
|
||||
|
||||
{
|
||||
auto state(_gcState.lock());
|
||||
if (state->gcRunning)
|
||||
future = state->gcFuture;
|
||||
future = std::move(state->gcFuture);
|
||||
}
|
||||
|
||||
if (future.valid()) {
|
||||
@@ -1266,7 +1266,7 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
autoGC();
|
||||
TRY_AWAIT(autoGC());
|
||||
|
||||
canonicalisePathMetaData(realPath, {});
|
||||
|
||||
@@ -1381,7 +1381,7 @@ try {
|
||||
|
||||
deletePath(realPath);
|
||||
|
||||
autoGC();
|
||||
TRY_AWAIT(autoGC());
|
||||
|
||||
if (inMemory) {
|
||||
StringSource dumpSource { dump };
|
||||
@@ -1448,7 +1448,7 @@ try {
|
||||
|
||||
deletePath(realPath);
|
||||
|
||||
autoGC();
|
||||
TRY_AWAIT(autoGC());
|
||||
|
||||
writeFile(realPath, s);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <kj/async.h>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
@@ -118,7 +119,8 @@ private:
|
||||
* the GC to finish.
|
||||
*/
|
||||
bool gcRunning = false;
|
||||
std::shared_future<void> gcFuture;
|
||||
std::future<void> gcFuture;
|
||||
std::list<kj::Own<kj::CrossThreadPromiseFulfiller<void>>> gcWaiters;
|
||||
|
||||
/**
|
||||
* How much disk space was available after the previous
|
||||
@@ -297,7 +299,7 @@ public:
|
||||
* If free disk space in /nix/store if below minFree, delete
|
||||
* garbage until it exceeds maxFree.
|
||||
*/
|
||||
void autoGC(bool sync = true);
|
||||
kj::Promise<Result<void>> autoGC(bool sync = true);
|
||||
|
||||
/**
|
||||
* Register the store path 'output' as the output named 'outputName' of
|
||||
|
||||
Reference in New Issue
Block a user