fix(conftest): register atexit handler for non-blocking pool shutdown
Fixes the run_tests_batched.py hang that occurs after batch 4.
The original conftest (commit 52ea2693) stored _warmup_app_controller
at module scope for the entire pytest session. When pytest exits,
GC of the AppController triggers ThreadPoolExecutor.__del__ ->
shutdown(wait=True). If warmup hasn't fully completed by then, the
shutdown blocks indefinitely, causing the batched test runner to
hang at the subprocess.run boundary.
Fix: register an atexit handler that captures the _io_pool reference
directly (default argument) and shuts it down with wait=False. The
pool reference is captured by closure, surviving even after the
AppController is GC'd. shutdown() is idempotent so the subsequent
shutdown(wait=True) in __del__ is a no-op.
This is part of sub-track 4 (warmup notification) cleanup; the
conftest's wait_for_warmup behavior is preserved, only the
exit-hang is fixed.
This commit is contained in:
@@ -33,6 +33,20 @@ install()
|
||||
# created in a session-scoped fixture; if it already exists (e.g.,
|
||||
# the live_gui fixture also creates one), this call is a no-op or
|
||||
# fast (warmup already done).
|
||||
#
|
||||
# FIX (startup_speedup_20260606 sub-track 4 follow-up): The original
|
||||
# code held `_warmup_app_controller` at module scope for the entire
|
||||
# pytest session. When pytest exits, GC of the AppController triggers
|
||||
# ThreadPoolExecutor.__del__ -> shutdown(wait=True). If warmup hasn't
|
||||
# fully completed, shutdown blocks indefinitely, causing the batched
|
||||
# test runner to hang after pytest exits.
|
||||
#
|
||||
# Fix: register an atexit handler that captures the pool reference
|
||||
# directly (not the AppController) and shuts it down with wait=False.
|
||||
# shutdown() is idempotent, so the subsequent shutdown(wait=True) in
|
||||
# __del__ is a no-op. The pool reference is captured by closure so it
|
||||
# survives even after the AppController is GC'd.
|
||||
import atexit
|
||||
from src.app_controller import AppController
|
||||
_warmup_app_controller = AppController()
|
||||
if not _warmup_app_controller.wait_for_warmup(timeout=60.0):
|
||||
@@ -44,6 +58,12 @@ if not _warmup_app_controller.wait_for_warmup(timeout=60.0):
|
||||
RuntimeWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
_warmup_io_pool = getattr(_warmup_app_controller, "_io_pool", None)
|
||||
def _shutdown_warmup_pool(pool: object = _warmup_io_pool) -> None:
|
||||
if pool is not None:
|
||||
try: pool.shutdown(wait=False)
|
||||
except Exception: pass
|
||||
atexit.register(_shutdown_warmup_pool)
|
||||
|
||||
from src.gui_2 import App
|
||||
|
||||
|
||||
Reference in New Issue
Block a user