93 lines
3.1 KiB
Python
93 lines
3.1 KiB
Python
import threading
|
|
from uuid import UUID
|
|
|
|
from repository.sandbox_lock import ProcessLocalSandboxLifecycleLocker
|
|
|
|
CHAT_ID = UUID('77777777-7777-7777-7777-777777777777')
|
|
|
|
|
|
class LockRace:
|
|
def __init__(self, locker: ProcessLocalSandboxLifecycleLocker) -> None:
|
|
self.locker = locker
|
|
self.entered_first = threading.Event()
|
|
self.second_requested = threading.Event()
|
|
self.second_entered = threading.Event()
|
|
self.release_first = threading.Event()
|
|
self.release_second = threading.Event()
|
|
self.errors: list[Exception] = []
|
|
self.order: list[str] = []
|
|
self.first_entry: object | None = None
|
|
|
|
def run_first(self) -> None:
|
|
try:
|
|
with self.locker.lock(CHAT_ID):
|
|
self.first_entry = self.locker._locks_by_chat_id[CHAT_ID]
|
|
self.order.append('first_entered')
|
|
self.entered_first.set()
|
|
assert self.release_first.wait(timeout=1)
|
|
self.order.append('first_releasing')
|
|
except Exception as exc:
|
|
self.errors.append(exc)
|
|
|
|
def run_second(self) -> None:
|
|
try:
|
|
assert self.entered_first.wait(timeout=1)
|
|
context = self.locker.lock(CHAT_ID)
|
|
self.second_requested.set()
|
|
|
|
with context:
|
|
self.order.append('second_entered')
|
|
self.second_entered.set()
|
|
assert self.release_second.wait(timeout=1)
|
|
self.order.append('second_releasing')
|
|
except Exception as exc:
|
|
self.errors.append(exc)
|
|
|
|
|
|
def test_process_local_sandbox_lifecycle_locker_evicts_idle_lock() -> None:
|
|
locker = ProcessLocalSandboxLifecycleLocker()
|
|
|
|
with locker.lock(CHAT_ID):
|
|
assert CHAT_ID in locker._locks_by_chat_id
|
|
assert len(locker._locks_by_chat_id) == 1
|
|
|
|
assert CHAT_ID not in locker._locks_by_chat_id
|
|
assert len(locker._locks_by_chat_id) == 0
|
|
|
|
|
|
def test_process_local_sandbox_lifecycle_locker_keeps_shared_lock_for_waiters() -> None:
|
|
locker = ProcessLocalSandboxLifecycleLocker()
|
|
race = LockRace(locker)
|
|
first_thread = threading.Thread(target=race.run_first)
|
|
second_thread = threading.Thread(target=race.run_second)
|
|
|
|
first_thread.start()
|
|
assert race.entered_first.wait(timeout=1)
|
|
|
|
second_thread.start()
|
|
assert race.second_requested.wait(timeout=1)
|
|
assert len(locker._locks_by_chat_id) == 1
|
|
assert locker._locks_by_chat_id[CHAT_ID] is race.first_entry
|
|
assert not race.second_entered.wait(timeout=0.1)
|
|
|
|
race.release_first.set()
|
|
assert race.second_entered.wait(timeout=1)
|
|
assert len(locker._locks_by_chat_id) == 1
|
|
assert locker._locks_by_chat_id[CHAT_ID] is race.first_entry
|
|
|
|
race.release_second.set()
|
|
|
|
first_thread.join(timeout=1)
|
|
second_thread.join(timeout=1)
|
|
|
|
assert not first_thread.is_alive()
|
|
assert not second_thread.is_alive()
|
|
assert race.errors == []
|
|
assert race.order == [
|
|
'first_entered',
|
|
'first_releasing',
|
|
'second_entered',
|
|
'second_releasing',
|
|
]
|
|
assert CHAT_ID not in locker._locks_by_chat_id
|
|
assert len(locker._locks_by_chat_id) == 0
|