"""Shared asyncio scaffolding for the long-lived services. The worker and the reconciler are both a loop that runs until SIGTERM. They wake immediately on shutdown rather than sleeping through it, and they install the same loop-safe signal handlers. Both lived in each service before; keeping one copy means the shutdown behaviour cannot drift between them. """ from __future__ import annotations import asyncio import contextlib import signal async def sleep_or_stop(stop: asyncio.Event, seconds: float) -> None: """Sleep for `seconds`, but return the instant `stop` is set. `await asyncio.sleep(seconds)` would make every SIGTERM cost up to `seconds` of Kubernetes waiting on terminationGracePeriod for nothing. """ with contextlib.suppress(TimeoutError): await asyncio.wait_for(stop.wait(), timeout=seconds) def install_stop_signals(stop: asyncio.Event) -> None: """Set `stop` on SIGTERM and SIGINT, loop-safely. add_signal_handler, not signal.signal. signal.signal runs the handler at an arbitrary bytecode boundary on whatever thread the C-level handler lands on, and the loop does not notice until its next timer fires — up to a full sleep interval away. add_signal_handler schedules the callback as an ordinary loop callback, so the sleep_or_stop above returns at once. """ loop = asyncio.get_running_loop() for sig in (signal.SIGTERM, signal.SIGINT): loop.add_signal_handler(sig, stop.set)