sjr666666 2fe325f8ae
fix(lark): retry the Windows install lock instead of failing after ~10s (#6881)
* fix(lark): retry the Windows install lock instead of failing after ~10s

msvcrt.LK_LOCK gives up after roughly ten seconds and surfaces contention
as Permission denied, while a real install (download + setup of the
lark/sandbox CLI) can hold the lock far longer. Retry the non-blocking
acquisition on the Windows fallback branch so concurrent Gateway workers
serialize instead of failing, mirroring the extensions manager's lock
introduced in #6020. The POSIX flock branch is unchanged.

Closes #6879

* test(lark): inject msvcrt stub with raising=False for Linux runners

The regression test patches the module-level ``msvcrt`` attribute, which only
exists on Windows (the POSIX branch imports ``fcntl`` instead), so
``monkeypatch.setattr`` with the default ``raising=True`` raised AttributeError
on Linux CI (backend-unit-tests shard 4).

Pass ``raising=False`` so the stub is injected on POSIX runners: ``fcntl`` is
patched to ``None`` first, and ``_exclusive_install_lock`` then takes the
Windows fallback branch against the fake. Windows behavior is unchanged.

Scope: backend/tests/test_lark_cli_install_lock.py, 1 line.
Verification: pytest tests/test_lark_cli_install_lock.py on Windows
(Python 3.12, pytest 9.0.3) -- 3 passed incl. the real-contention case;
Linux CI rerun is the authoritative check.

* fix(lark): harden and share advisory lock retries

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-15 08:03:10 +08:00

45 lines
1.5 KiB
Python

"""Contention-only retries for non-blocking advisory file locks."""
from __future__ import annotations
import errno
import time
from collections.abc import Callable
LOCK_RETRY_INTERVAL_SECONDS = 0.2
def retry_until_locked(
acquire: Callable[[], None],
*,
interval: float = LOCK_RETRY_INTERVAL_SECONDS,
deadline: float | None = None,
sleep: Callable[[float], None] = time.sleep,
monotonic: Callable[[], float] = time.monotonic,
check: Callable[[], None] | None = None,
timeout_message: str = "File lock acquisition timed out",
) -> None:
"""Retry only contention, optionally bounded by an absolute monotonic deadline.
Windows' blocking ``msvcrt.LK_LOCK`` gives up after about ten seconds.
Callers instead acquire with ``LK_NBLCK`` and choose their own deadline;
omitting it preserves an indefinite wait. Sleep/clock injection keeps
tests local rather than replacing functions on the shared time module.
"""
while True:
if check is not None:
check()
try:
acquire()
return
except OSError as error:
if error.errno not in (errno.EACCES, errno.EAGAIN, errno.EDEADLK):
raise
delay = interval
if deadline is not None:
remaining = deadline - monotonic()
if remaining <= 0:
raise TimeoutError(timeout_message) from None
delay = min(delay, remaining)
sleep(delay)