libstore: add async FdLock constructor

the local store doesn't have to block the entire thread waiting for a
lock with such support. we'll still block *some* thread, but there is
currently no good way around that without e.g. a lock manager daemon.

Change-Id: I037fdc749e1b56cf9e0f1ca7ac70274442fb2b8c
This commit is contained in:
eldritch horrors
2025-02-08 08:54:16 +00:00
parent 8f5f33c9d8
commit 80189b3c26
3 changed files with 65 additions and 1 deletions
+3 -1
View File
@@ -1527,7 +1527,9 @@ try {
/* Acquire the global GC lock to get a consistent snapshot of
existing and valid paths. */
auto fdGCLock = openGCLock();
FdLock gcLock(fdGCLock, ltRead, "waiting for the big garbage collector lock...");
auto gcLock = TRY_AWAIT(
FdLock::lockAsync(fdGCLock, ltRead, "waiting for the big garbage collector lock...")
);
StorePathSet validPaths;
+53
View File
@@ -1,4 +1,5 @@
#include "lix/libstore/pathlocks.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/file-descriptor.hh"
#include "lix/libutil/logging.hh"
#include "lix/libutil/signals.hh"
@@ -55,6 +56,47 @@ void lockFile(int fd, LockType lockType)
}
}
static kj::Promise<Result<void>> lockFileAsyncInner(int fd, LockType lockType)
try {
// start a thread to lock the file synchronously, waiting for SIGUSR1 to signal
// that the call was canceled. SIGUSR1 is already set aside for such signaling.
int type = convertLockType(lockType);
auto pfp = kj::newPromiseAndCrossThreadFulfiller<Result<void>>();
std::thread locker([&] {
while (flock(fd, type) != 0 && pfp.fulfiller->isWaiting()) {
if (errno != EINTR) {
pfp.fulfiller->fulfill(std::make_exception_ptr(SysError("acquiring lock")));
return;
}
}
pfp.fulfiller->fulfill(result::success());
});
auto cancel = kj::defer([&] {
pthread_kill(locker.native_handle(), SIGUSR1);
locker.join();
});
TRY_AWAIT(makeInterruptible(std::move(pfp.promise)));
locker.join();
cancel.cancel();
co_return result::success();
} catch (...) {
co_return result::current_exception();
}
kj::Promise<Result<void>> lockFileAsync(int fd, LockType lockType)
try {
if (tryLockFile(fd, lockType)) {
return {result::success()};
}
return lockFileAsyncInner(fd, lockType);
} catch (...) {
return {result::current_exception()};
}
bool unsafeLockFileSingleThreaded(int fd, LockType lockType, std::chrono::seconds timeout)
{
int type = convertLockType(lockType);
@@ -225,5 +267,16 @@ FdLock::FdLock(AutoCloseFD & fd, LockType lockType, std::string_view waitMsg)
}
}
kj::Promise<Result<FdLock>>
FdLock::lockAsync(AutoCloseFD & fd, LockType lockType, std::string_view waitMsg)
try {
if (!tryLockFile(fd.get(), lockType)) {
printInfo("%s", waitMsg);
TRY_AWAIT(lockFileAsyncInner(fd.get(), lockType));
}
co_return FdLock{fd};
} catch (...) {
co_return result::current_exception();
}
}
+9
View File
@@ -3,7 +3,10 @@
#include "lix/libutil/error.hh"
#include "lix/libutil/file-descriptor.hh"
#include "lix/libutil/result.hh"
#include <chrono>
#include <kj/async.h>
#include <kj/common.h>
namespace nix {
@@ -22,6 +25,7 @@ void deleteLockFile(const Path & path, int fd);
enum LockType { ltRead, ltWrite };
void lockFile(int fd, LockType lockType);
kj::Promise<Result<void>> lockFileAsync(int fd, LockType lockType);
/**
* Same as `lockFile`, but with a timeout. This timeout uses the POSIX `alarm`
* facility and a `SIGALRM` handler. Using this function from multiple threads
@@ -73,12 +77,17 @@ class FdLock
std::unique_ptr<AutoCloseFD, Unlocker> fd;
explicit FdLock(AutoCloseFD & fd): fd(&fd) {}
public:
static constexpr struct DontWait { explicit DontWait() = default; } dont_wait;
FdLock(AutoCloseFD & fd, LockType lockType, DontWait);
FdLock(AutoCloseFD & fd, LockType lockType, std::string_view waitMsg);
static kj::Promise<Result<FdLock>>
lockAsync(AutoCloseFD & fd, LockType lockType, std::string_view waitMsg);
bool valid() const { return bool(fd); }
};