Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/borg/storelocking.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,9 @@ def acquire(self):
return self
time.sleep(self.other_locks_go_away_delay)
logger.debug("LOCK-ACQUIRE: timeout while waiting for non-exclusive locks to go away.")
# we won't get the exclusive lock, so do not leave our lock behind:
# it would needlessly block other clients until it expired as stale.
self._delete_lock(key, ignore_not_found=True, update_last_refresh=True)
break # timeout
else:
logger.debug("LOCK-ACQUIRE: someone else also created an exclusive lock, deleting ours.")
Expand Down
11 changes: 11 additions & 0 deletions src/borg/testsuite/storelocking_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ def test_exclusive_lock(self, lockstore):
with pytest.raises(LockTimeout):
Lock(lockstore, exclusive=True, id=ID2).acquire()

def test_exclusive_lock_timeout_leaves_no_lock(self, lockstore):
# When acquiring an exclusive lock times out because a non-exclusive lock does not go away,
# the not-acquired exclusive lock must not stay behind in the store: it would block all
# other clients (even on other hosts) until it expired as stale.
with Lock(lockstore, exclusive=False, id=ID1) as shared_lock:
with pytest.raises(LockTimeout):
Lock(lockstore, exclusive=True, id=ID2).acquire()
locks = shared_lock._get_locks()
assert len(locks) == 1 # only the non-exclusive lock of ID1 is left
assert not any(lock["exclusive"] for lock in locks.values())

def test_double_nonexclusive_lock_succeeds(self, lockstore):
with Lock(lockstore, exclusive=False, id=ID1):
with Lock(lockstore, exclusive=False, id=ID2):
Expand Down
Loading