diff --git a/src/borg/storelocking.py b/src/borg/storelocking.py index ac1700e99..9f2e5302d 100644 --- a/src/borg/storelocking.py +++ b/src/borg/storelocking.py @@ -463,12 +463,15 @@ class Lock: if self.is_exclusive: if len(exclusive_locks) == 1 and exclusive_locks[0]["key"] == key: logger.debug("LOCK-ACQUIRE: we are the only exclusive lock!") - while time.monotonic() - started < self.timeout: + # check at least once, even if creating our lock already used up the timeout. + while True: locks = self._find_locks(only_exclusive=False) if len(locks) == 1 and locks[0]["key"] == key: logger.debug("LOCK-ACQUIRE: success! no non-exclusive locks are left!") return self blocking_locks = [lock for lock in locks if lock["key"] != key] + if time.monotonic() - started >= self.timeout: + break self._log_blocking_locks(blocking_locks) time.sleep(self.other_locks_go_away_delay) logger.debug("LOCK-ACQUIRE: timeout while waiting for non-exclusive locks to go away.") diff --git a/src/borg/testsuite/storelocking_test.py b/src/borg/testsuite/storelocking_test.py index 181a46b30..e1dbb0b6f 100644 --- a/src/borg/testsuite/storelocking_test.py +++ b/src/borg/testsuite/storelocking_test.py @@ -92,6 +92,19 @@ class TestLock: 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_exclusive_lock_on_slow_store(self, lockstore, monkeypatch): + # An exclusive lock is acquired if no other lock exists, even if writing our lock object + # takes longer than the timeout. + orig_store = lockstore.store + + def slow_store(name, value): + time.sleep(0.2) + return orig_store(name, value) + + monkeypatch.setattr(lockstore, "store", slow_store) + with Lock(lockstore, exclusive=True, id=ID1, timeout=0.1) as lock: + assert lock.got_exclusive_lock() + def test_double_nonexclusive_lock_succeeds(self, lockstore): with Lock(lockstore, exclusive=False, id=ID1): with Lock(lockstore, exclusive=False, id=ID2):