From d7d489681afd530654a7bbd69735dbf29bb4e5d0 Mon Sep 17 00:00:00 2001 From: retoor Date: Sun, 16 Aug 2026 05:00:52 +0200 Subject: [PATCH] Update --- tests/conftest.py | 56 +++++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 4b6b8f46..30f95638 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -41,32 +41,50 @@ os.environ["DEVPLACE_RANKING_TTL"] = "0" os.environ["DEVPLACE_MARKET_SATURATION_TTL"] = "0" +_ASYNC_LOOP = None +_ASYNC_LOOP_LOCK = threading.Lock() + + +def _async_loop(): + # One shared background loop thread for the whole session, not one thread + # per run_async() call: dataset's NullPool caches one SQLite connection per + # OS thread ID forever (see the root CLAUDE.md NullPool note), so a fresh + # throwaway thread per call - this helper is invoked 270+ times across the + # suite - permanently claimed one file descriptor apiece for the rest of + # the session and could exhaust the process's open-file limit on a long run. + global _ASYNC_LOOP + with _ASYNC_LOOP_LOCK: + if _ASYNC_LOOP is None: + loop = asyncio.new_event_loop() + threading.Thread( + target=loop.run_forever, name="run-async-loop", daemon=True + ).start() + _ASYNC_LOOP = loop + return _ASYNC_LOOP + + def run_async(coro): - """Run a coroutine from sync test code on a dedicated thread and fresh loop.""" - result_box = [] - exc_box = [] + """Run a coroutine from sync test code on the shared background loop.""" + + async def _wrapped(): + from devplacepy.database import refresh_snapshot - def _run(): try: - result_box.append(asyncio.run(coro)) - except BaseException as e: - exc_box.append(e) + return await coro finally: - from devplacepy.database import refresh_snapshot - refresh_snapshot() - t = threading.Thread(target=_run, daemon=True) - t.start() - t.join(timeout=30) - from devplacepy.database import refresh_snapshot + future = asyncio.run_coroutine_threadsafe(_wrapped(), _async_loop()) + try: + return future.result(timeout=30) + except TimeoutError: + raise TimeoutError( + "run_async timed out waiting for coroutine to complete" + ) from None + finally: + from devplacepy.database import refresh_snapshot - refresh_snapshot() - if exc_box: - raise exc_box[0] - if result_box: - return result_box[0] - raise TimeoutError("run_async timed out waiting for coroutine to complete") + refresh_snapshot() @pytest.fixture(autouse=True)