Skip to content

Commit 76150aa

Browse files
FanWu-aiByron
authored andcommitted
fix(util): wait for contended locks with relative filenames
`BlockingLockFile('resource')` incorrectly treats the empty `dirname` of `resource.lock` as a missing directory when another holder owns the lock. Use `os.curdir` for that case so the retry and timeout paths work normally. Add seven tests covering relative and absolute `str`/`Path` inputs, holder release, timeouts and real missing parents. The complete Linux/Python 3.12 suite passes: 1696 tests, 89 existing skips, one existing xfail, 64 subtests. Pinned hooks, mypy, basedpyright, docs, builds and strict Twine also pass. Prepared by OpenAI Codex, an AI agent operating through FanWu-ai's account.
1 parent d940ee7 commit 76150aa

2 files changed

Lines changed: 38 additions & 1 deletion

File tree

‎git/util.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1310,7 +1310,7 @@ def _obtain_lock(self) -> None:
13101310
# synity check: if the directory leading to the lockfile is not
13111311
# readable anymore, raise an exception
13121312
curtime = time.time()
1313-
if not osp.isdir(osp.dirname(self._lock_file_path())):
1313+
if not osp.isdir(osp.dirname(self._lock_file_path()) or osp.curdir):
13141314
msg = "Directory containing the lockfile %r was not readable anymore after waiting %g seconds" % (
13151315
self._lock_file_path(),
13161316
curtime - starttime,

‎test/test_util.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -882,3 +882,40 @@ def test_iterable_list_name_lookup_ignores_list_attributes(name, prefix):
882882
assert name not in values
883883
with pytest.raises(IndexError, match="No item found with id"):
884884
values[name]
885+
886+
887+
@pytest.mark.parametrize("path_type", [str, pathlib.Path])
888+
@pytest.mark.parametrize("relative", [True, False])
889+
def test_blocking_lock_waits_for_existing_holder(tmp_path, monkeypatch, path_type, relative):
890+
monkeypatch.chdir(tmp_path)
891+
path = path_type("resource" if relative else tmp_path / "resource")
892+
owner = LockFile(path)
893+
waiter = BlockingLockFile(path, check_interval_s=0.01, max_block_time_s=1)
894+
owner._obtain_lock()
895+
try:
896+
with mock.patch.object(time, "sleep", side_effect=lambda _: owner._release_lock()) as sleep:
897+
waiter._obtain_lock()
898+
sleep.assert_called_once_with(0.01)
899+
assert waiter._has_lock()
900+
finally:
901+
owner._release_lock()
902+
waiter._release_lock()
903+
904+
905+
@pytest.mark.parametrize("path_type", [str, pathlib.Path])
906+
def test_blocking_relative_lock_reports_timeout(tmp_path, monkeypatch, path_type):
907+
monkeypatch.chdir(tmp_path)
908+
owner = LockFile(path_type("resource"))
909+
waiter = BlockingLockFile(path_type("resource"), max_block_time_s=0)
910+
owner._obtain_lock()
911+
try:
912+
with pytest.raises(OSError, match="Waited .* seconds for lock"):
913+
waiter._obtain_lock()
914+
finally:
915+
owner._release_lock()
916+
917+
918+
def test_blocking_lock_still_rejects_missing_directory(tmp_path):
919+
waiter = BlockingLockFile(tmp_path / "missing" / "resource")
920+
with pytest.raises(OSError, match="Directory containing the lockfile"):
921+
waiter._obtain_lock()

0 commit comments

Comments
 (0)