* perf: bound concurrent full-session resolution * Release — bound concurrent full-session resolution (#7127) * ci: re-trigger fresh run (Playwright-install hang on prior run) * ci: re-trigger (Playwright-install CDN hang on 3 shards, 12/15 passed) * ci: re-trigger (sustained Playwright-CDN hang, run 4) * ci: supersede 2h20m-zombie Playwright-hung run (run 6) --------- Co-authored-by: Dr. Carlos Daniel Carvalho <292283903+Dandandad@users.noreply.github.com> Co-authored-by: n <a@n>
239 lines
7.5 KiB
Python
239 lines
7.5 KiB
Python
"""Regression coverage for bounded full-session resolution."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import threading
|
|
import time
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
import pytest
|
|
|
|
|
|
def _install_lightweight_resolve_stubs(monkeypatch, models) -> None:
|
|
"""Keep the tests focused on full sidecar materialization."""
|
|
monkeypatch.setattr(models, "_sync_sidecar_from_state_db_if_newer", lambda _session: False)
|
|
monkeypatch.setattr(models, "_repair_stale_pending", lambda _session: False)
|
|
monkeypatch.setattr(models, "_session_has_pending_journal_retry", lambda _session: False)
|
|
|
|
|
|
def _empty_session(models, sid: str):
|
|
return models.Session(session_id=sid, messages=[])
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clear_session_cache():
|
|
import api.models as models
|
|
|
|
with models.LOCK:
|
|
models.SESSIONS.clear()
|
|
yield
|
|
with models.LOCK:
|
|
models.SESSIONS.clear()
|
|
|
|
|
|
def test_same_session_cold_load_is_single_flight(monkeypatch):
|
|
import api.models as models
|
|
|
|
_install_lightweight_resolve_stubs(monkeypatch, models)
|
|
workers = 4
|
|
start = threading.Barrier(workers)
|
|
state_lock = threading.Lock()
|
|
calls = 0
|
|
active = 0
|
|
peak = 0
|
|
|
|
def fake_load(cls, sid):
|
|
nonlocal calls, active, peak
|
|
with state_lock:
|
|
calls += 1
|
|
active += 1
|
|
peak = max(peak, active)
|
|
time.sleep(0.08)
|
|
with state_lock:
|
|
active -= 1
|
|
return _empty_session(models, sid)
|
|
|
|
monkeypatch.setattr(models.Session, "load", classmethod(fake_load))
|
|
|
|
def resolve():
|
|
start.wait(timeout=2)
|
|
return models.get_session("same-session")
|
|
|
|
with ThreadPoolExecutor(max_workers=workers) as executor:
|
|
results = [future.result(timeout=3) for future in [executor.submit(resolve) for _ in range(workers)]]
|
|
|
|
assert calls == 1
|
|
assert peak == 1
|
|
assert {result.session_id for result in results} == {"same-session"}
|
|
|
|
|
|
def test_stale_cached_session_is_single_flight(monkeypatch):
|
|
import api.models as models
|
|
|
|
_install_lightweight_resolve_stubs(monkeypatch, models)
|
|
stale = _empty_session(models, "stale-session")
|
|
with models.LOCK:
|
|
models.SESSIONS[stale.session_id] = stale
|
|
monkeypatch.setattr(models, "_cached_session_lags_disk", lambda session: session is stale)
|
|
monkeypatch.setattr(models, "_inactive_cache_tail_needs_disk_check", lambda _session: False)
|
|
|
|
workers = 4
|
|
start = threading.Barrier(workers)
|
|
state_lock = threading.Lock()
|
|
calls = 0
|
|
active = 0
|
|
peak = 0
|
|
|
|
def fake_load(cls, sid):
|
|
nonlocal calls, active, peak
|
|
with state_lock:
|
|
calls += 1
|
|
active += 1
|
|
peak = max(peak, active)
|
|
time.sleep(0.08)
|
|
with state_lock:
|
|
active -= 1
|
|
return _empty_session(models, sid)
|
|
|
|
monkeypatch.setattr(models.Session, "load", classmethod(fake_load))
|
|
|
|
def resolve():
|
|
start.wait(timeout=2)
|
|
return models.get_session("stale-session")
|
|
|
|
with ThreadPoolExecutor(max_workers=workers) as executor:
|
|
results = [
|
|
future.result(timeout=3)
|
|
for future in [executor.submit(resolve) for _ in range(workers)]
|
|
]
|
|
|
|
assert calls == 1
|
|
assert peak == 1
|
|
assert all(result is results[0] for result in results)
|
|
assert results[0] is not stale
|
|
|
|
|
|
def test_distinct_cold_loads_have_a_global_concurrency_bound(monkeypatch):
|
|
import api.models as models
|
|
|
|
_install_lightweight_resolve_stubs(monkeypatch, models)
|
|
session_ids = ("session-a", "session-b", "session-c", "session-d")
|
|
start = threading.Barrier(len(session_ids))
|
|
state_lock = threading.Lock()
|
|
active = 0
|
|
peak = 0
|
|
|
|
def fake_load(cls, sid):
|
|
nonlocal active, peak
|
|
with state_lock:
|
|
active += 1
|
|
peak = max(peak, active)
|
|
time.sleep(0.08)
|
|
with state_lock:
|
|
active -= 1
|
|
return _empty_session(models, sid)
|
|
|
|
monkeypatch.setattr(models.Session, "load", classmethod(fake_load))
|
|
|
|
def resolve(sid):
|
|
start.wait(timeout=2)
|
|
return models.get_session(sid)
|
|
|
|
with ThreadPoolExecutor(max_workers=len(session_ids)) as executor:
|
|
results = [
|
|
future.result(timeout=3)
|
|
for future in [executor.submit(resolve, sid) for sid in session_ids]
|
|
]
|
|
|
|
assert peak == 2
|
|
assert {result.session_id for result in results} == set(session_ids)
|
|
|
|
|
|
def test_metadata_only_load_bypasses_full_resolve_slots(monkeypatch):
|
|
import api.models as models
|
|
|
|
_install_lightweight_resolve_stubs(monkeypatch, models)
|
|
heavy_entered = threading.Barrier(3)
|
|
release_heavy = threading.Event()
|
|
|
|
def fake_load(cls, sid):
|
|
heavy_entered.wait(timeout=2)
|
|
assert release_heavy.wait(timeout=2)
|
|
return _empty_session(models, sid)
|
|
|
|
def fake_load_metadata_only(cls, sid):
|
|
return _empty_session(models, sid)
|
|
|
|
monkeypatch.setattr(models.Session, "load", classmethod(fake_load))
|
|
monkeypatch.setattr(models.Session, "load_metadata_only", classmethod(fake_load_metadata_only))
|
|
|
|
with ThreadPoolExecutor(max_workers=3) as executor:
|
|
first = executor.submit(models.get_session, "heavy-a")
|
|
second = executor.submit(models.get_session, "heavy-b")
|
|
heavy_entered.wait(timeout=2)
|
|
metadata = executor.submit(models.get_session, "metadata", True)
|
|
try:
|
|
assert metadata.result(timeout=0.5).session_id == "metadata"
|
|
finally:
|
|
release_heavy.set()
|
|
assert first.result(timeout=2).session_id == "heavy-a"
|
|
assert second.result(timeout=2).session_id == "heavy-b"
|
|
|
|
|
|
def test_fresh_cache_hit_bypasses_full_resolve_slots(monkeypatch):
|
|
import api.models as models
|
|
|
|
_install_lightweight_resolve_stubs(monkeypatch, models)
|
|
cached = _empty_session(models, "cached")
|
|
with models.LOCK:
|
|
models.SESSIONS[cached.session_id] = cached
|
|
monkeypatch.setattr(models, "_cached_session_lags_disk", lambda _session: False)
|
|
monkeypatch.setattr(models, "_inactive_cache_tail_needs_disk_check", lambda _session: False)
|
|
|
|
heavy_entered = threading.Barrier(3)
|
|
release_heavy = threading.Event()
|
|
|
|
def fake_load(cls, sid):
|
|
heavy_entered.wait(timeout=2)
|
|
assert release_heavy.wait(timeout=2)
|
|
return _empty_session(models, sid)
|
|
|
|
monkeypatch.setattr(models.Session, "load", classmethod(fake_load))
|
|
|
|
with ThreadPoolExecutor(max_workers=3) as executor:
|
|
first = executor.submit(models.get_session, "heavy-a")
|
|
second = executor.submit(models.get_session, "heavy-b")
|
|
heavy_entered.wait(timeout=2)
|
|
cache_hit = executor.submit(models.get_session, "cached")
|
|
try:
|
|
assert cache_hit.result(timeout=0.5) is cached
|
|
finally:
|
|
release_heavy.set()
|
|
first.result(timeout=2)
|
|
second.result(timeout=2)
|
|
|
|
|
|
def test_failed_leader_releases_single_flight_state(monkeypatch):
|
|
import api.models as models
|
|
|
|
_install_lightweight_resolve_stubs(monkeypatch, models)
|
|
attempts = 0
|
|
|
|
def fake_load(cls, sid):
|
|
nonlocal attempts
|
|
attempts += 1
|
|
if attempts == 1:
|
|
raise OSError("simulated sidecar failure")
|
|
return _empty_session(models, sid)
|
|
|
|
monkeypatch.setattr(models.Session, "load", classmethod(fake_load))
|
|
|
|
with pytest.raises(OSError, match="simulated sidecar failure"):
|
|
models.get_session("retryable")
|
|
|
|
recovered = models.get_session("retryable")
|
|
|
|
assert recovered.session_id == "retryable"
|
|
assert attempts == 2
|
|
assert models._FULL_SESSION_RESOLVE_INFLIGHT == {}
|