Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
"""Regression test for a Windows-only PermissionError under lock contention.
|
||||
|
||||
``_file_lock()`` (backing ``_auth_store_lock()``) ensures its lock file has
|
||||
at least 1 byte of content before opening it for ``msvcrt.locking()`` (which
|
||||
requires a non-empty file). That "ensure content" write was unguarded: under
|
||||
real concurrency, one thread/process's ``msvcrt.locking()`` byte-range lock
|
||||
can already be held on the file at the exact moment another thread runs the
|
||||
same ensure-content check, and the write collides and raises
|
||||
``PermissionError`` -- uncaught, since it happens before the retry loop the
|
||||
rest of the function uses for exactly this kind of contention.
|
||||
|
||||
This was found by a stress test for the Anthropic OAuth credential-pool fix
|
||||
(``tests/agent/test_anthropic_oauth_stress.py``), which reliably reproduces
|
||||
it (16/20 concurrent refreshes hit PermissionError pre-fix, deterministically
|
||||
across repeated runs) because the surrounding CredentialPool work widens the
|
||||
race window enough for real OS-level thread interleaving to land on it. That
|
||||
test is the authoritative regression guard for the exact failure mode. This
|
||||
file is deliberately a minimal, isolated stress test against
|
||||
``_auth_store_lock()`` directly -- best-effort coverage that does not always
|
||||
land on the same narrow window in isolation, but exercises the SAME shared
|
||||
primitive Codex/xAI/Nous already depend on for their single-use-refresh-token
|
||||
protection, so this bug was never Anthropic-specific.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
|
||||
import pytest
|
||||
|
||||
from hermes_cli.auth import _auth_store_lock
|
||||
|
||||
CONCURRENCY = 40
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def hermes_home(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
||||
return tmp_path
|
||||
|
||||
|
||||
@pytest.mark.windows_only
|
||||
def test_many_concurrent_lock_acquisitions_do_not_raise_permission_error(hermes_home):
|
||||
"""CONCURRENCY threads race to acquire/release the same auth-store lock.
|
||||
|
||||
None of them should ever see an uncaught PermissionError/OSError from
|
||||
the lock-file "ensure content" pre-check -- only the intended
|
||||
TimeoutError (never expected here, since each critical section is
|
||||
instant) should be a possible failure mode.
|
||||
"""
|
||||
errors: dict[int, BaseException] = {}
|
||||
entered = 0
|
||||
entered_lock = threading.Lock()
|
||||
# Synchronize all threads to hit the lock-file "ensure content" pre-check
|
||||
# at (as close as the OS scheduler allows to) the exact same instant --
|
||||
# the race only exists on the very first acquisition against a fresh
|
||||
# lock file, so a tight barrier maximizes the odds of reproducing it
|
||||
# instead of relying on incidental thread-start jitter.
|
||||
barrier = threading.Barrier(CONCURRENCY)
|
||||
|
||||
def _run(idx: int) -> None:
|
||||
nonlocal entered
|
||||
try:
|
||||
barrier.wait(timeout=10)
|
||||
with _auth_store_lock(timeout_seconds=10.0):
|
||||
with entered_lock:
|
||||
entered += 1
|
||||
except BaseException as exc: # pragma: no cover - failure diagnostics
|
||||
errors[idx] = exc
|
||||
|
||||
threads = [threading.Thread(target=_run, args=(i,)) for i in range(CONCURRENCY)]
|
||||
for t in threads:
|
||||
t.start()
|
||||
for t in threads:
|
||||
t.join(timeout=30)
|
||||
|
||||
still_alive = [t for t in threads if t.is_alive()]
|
||||
assert not still_alive, f"{len(still_alive)}/{CONCURRENCY} threads never finished"
|
||||
assert not errors, f"unexpected exceptions acquiring the lock concurrently: {errors!r}"
|
||||
assert entered == CONCURRENCY
|
||||
Reference in New Issue
Block a user