From c10d45523adb96f570c24738a4953a333196f974 Mon Sep 17 00:00:00 2001 From: Walter Oggioni Date: Fri, 18 Sep 2026 13:48:34 +0800 Subject: [PATCH] Fix deadline consumer startup under granian RSGI The mixin received the event loop from Kaya but ignored it, calling asyncio.get_running_loop() instead. Under granian RSGI __rsgi_init__ runs before the loop starts, so that raised RuntimeError and killed the worker. Use the loop passed to setup(), falling back to the running loop for the lazy calls from sync_deadline(). --- server/src/tavolo/deadlines.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/server/src/tavolo/deadlines.py b/server/src/tavolo/deadlines.py index 4d238a8..356e236 100644 --- a/server/src/tavolo/deadlines.py +++ b/server/src/tavolo/deadlines.py @@ -214,14 +214,20 @@ async def _fire_hand_end(store: GameStore, state: GameState, entry: Dict[str, An # --- consumer lifecycle ------------------------------------------------------- -def ensure_consumer(store: GameStore) -> None: - """Start the deadline consumer on the running loop if not yet running. +def ensure_consumer( + store: GameStore, loop: Optional[asyncio.AbstractEventLoop] = None +) -> None: + """Start the deadline consumer on the given (or running) loop if not + yet running. Called lazily whenever a deadline is enqueued (the ASGI test transport never fires the lifespan hooks, so the mixin's ``setup`` alone is not - enough) and on application startup. + enough) and on application startup. The explicit ``loop`` matters at + startup: under RSGI granian calls ``setup`` before the loop runs, so + ``asyncio.get_running_loop()`` would fail there. """ - loop = asyncio.get_running_loop() + if loop is None: + loop = asyncio.get_running_loop() for old in list(_consumers): if old.is_closed(): _consumers.pop(old, None) @@ -284,7 +290,7 @@ class DeadlineSchedulerMixin(KayaMixin): pass def setup(self, loop: asyncio.AbstractEventLoop) -> None: - ensure_consumer(self._store) + ensure_consumer(self._store, loop) def shutdown(self, loop: asyncio.AbstractEventLoop) -> None: stop_consumer(loop)