diff --git a/lix/libstore/local-store.cc b/lix/libstore/local-store.cc index e6bd5ab08..1fd23af41 100644 --- a/lix/libstore/local-store.cc +++ b/lix/libstore/local-store.cc @@ -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; diff --git a/lix/libstore/pathlocks.cc b/lix/libstore/pathlocks.cc index dba1e0d60..0d4cc1a81 100644 --- a/lix/libstore/pathlocks.cc +++ b/lix/libstore/pathlocks.cc @@ -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> 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>(); + + 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> 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> +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(); +} } diff --git a/lix/libstore/pathlocks.hh b/lix/libstore/pathlocks.hh index bd44549cf..51928ee10 100644 --- a/lix/libstore/pathlocks.hh +++ b/lix/libstore/pathlocks.hh @@ -3,7 +3,10 @@ #include "lix/libutil/error.hh" #include "lix/libutil/file-descriptor.hh" +#include "lix/libutil/result.hh" #include +#include +#include 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> 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 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> + lockAsync(AutoCloseFD & fd, LockType lockType, std::string_view waitMsg); + bool valid() const { return bool(fd); } };