Skip to content

Commit 4f09ce7

Browse files
committed
Fix 'deadlock: trying to re-acquire self-held lock'
This was caused by derivations with 'allowSubstitutes = false'. Such derivations will be built locally. However, if there is another SubstitionGoal that has the output of the first derivation in its closure, then the path will be simultaneously built and substituted. There was a check to catch this situation (via pathIsLockedByMe()), but it no longer worked reliably because substitutions are now done in another thread. (Thus the comment 'It can't happen between here and the lockPaths() call below because we're not allowing multi-threading' was no longer valid.) The fix is to handle the path already being locked in both SubstitutionGoal and DerivationGoal.
1 parent 35fd317 commit 4f09ce7

File tree

4 files changed

+24
-22
lines changed

4 files changed

+24
-22
lines changed

src/libstore/build.cc

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,19 +1335,6 @@ void DerivationGoal::tryToBuild()
13351335
{
13361336
trace("trying to build");
13371337

1338-
/* Check for the possibility that some other goal in this process
1339-
has locked the output since we checked in haveDerivation().
1340-
(It can't happen between here and the lockPaths() call below
1341-
because we're not allowing multi-threading.) If so, put this
1342-
goal to sleep until another goal finishes, then try again. */
1343-
for (auto & i : drv->outputs)
1344-
if (pathIsLockedByMe(worker.store.toRealPath(i.second.path))) {
1345-
debug(format("putting derivation '%1%' to sleep because '%2%' is locked by another goal")
1346-
% drvPath % i.second.path);
1347-
worker.waitForAnyGoal(shared_from_this());
1348-
return;
1349-
}
1350-
13511338
/* Obtain locks on all output paths. The locks are automatically
13521339
released when we exit this function or Nix crashes. If we
13531340
can't acquire the lock, then continue; hopefully some other
@@ -3739,6 +3726,17 @@ void SubstitutionGoal::tryToRun()
37393726
return;
37403727
}
37413728

3729+
/* If the store path is already locked (probably by a
3730+
DerivationGoal), then put this goal to sleep. Note: we don't
3731+
acquire a lock here since that breaks addToStore(), so below we
3732+
handle an AlreadyLocked exception from addToStore(). The check
3733+
here is just an optimisation to prevent having to redo a
3734+
download due to a locked path. */
3735+
if (pathIsLockedByMe(worker.store.toRealPath(storePath))) {
3736+
worker.waitForAWhile(shared_from_this());
3737+
return;
3738+
}
3739+
37423740
maintainRunningSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.runningSubstitutions);
37433741
worker.updateProgress();
37443742

@@ -3778,6 +3776,12 @@ void SubstitutionGoal::finished()
37783776

37793777
try {
37803778
promise.get_future().get();
3779+
} catch (AlreadyLocked & e) {
3780+
/* Probably a DerivationGoal is already building this store
3781+
path. Sleep for a while and try again. */
3782+
state = &SubstitutionGoal::init;
3783+
worker.waitForAWhile(shared_from_this());
3784+
return;
37813785
} catch (Error & e) {
37823786
printError(e.msg());
37833787

src/libstore/local-store.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -992,8 +992,8 @@ void LocalStore::addToStore(const ValidPathInfo & info, const ref<std::string> &
992992
/* Lock the output path. But don't lock if we're being called
993993
from a build hook (whose parent process already acquired a
994994
lock on this path). */
995-
Strings locksHeld = tokenizeString<Strings>(getEnv("NIX_HELD_LOCKS"));
996-
if (find(locksHeld.begin(), locksHeld.end(), info.path) == locksHeld.end())
995+
static auto locksHeld = tokenizeString<PathSet>(getEnv("NIX_HELD_LOCKS"));
996+
if (!locksHeld.count(info.path))
997997
outputLock.lockPaths({realPath});
998998

999999
if (repair || !isValidPath(info.path)) {

src/libstore/pathlocks.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,10 @@ bool PathLocks::lockPaths(const PathSet & _paths,
113113

114114
{
115115
auto lockedPaths(lockedPaths_.lock());
116-
if (lockedPaths->count(lockPath))
117-
throw Error("deadlock: trying to re-acquire self-held lock '%s'", lockPath);
116+
if (lockedPaths->count(lockPath)) {
117+
if (!wait) return false;
118+
throw AlreadyLocked("deadlock: trying to re-acquire self-held lock '%s'", lockPath);
119+
}
118120
lockedPaths->insert(lockPath);
119121
}
120122

src/libstore/pathlocks.hh

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22

33
#include "util.hh"
44

5-
65
namespace nix {
76

8-
97
/* Open (possibly create) a lock file and return the file descriptor.
108
-1 is returned if create is false and the lock could not be opened
119
because it doesn't exist. Any other error throws an exception. */
@@ -18,6 +16,7 @@ enum LockType { ltRead, ltWrite, ltNone };
1816

1917
bool lockFile(int fd, LockType lockType, bool wait);
2018

19+
MakeError(AlreadyLocked, Error);
2120

2221
class PathLocks
2322
{
@@ -38,9 +37,6 @@ public:
3837
void setDeletion(bool deletePaths);
3938
};
4039

41-
42-
// FIXME: not thread-safe!
4340
bool pathIsLockedByMe(const Path & path);
4441

45-
4642
}

0 commit comments

Comments
 (0)