"""Shared asyncio task registry so background jobs are not garbage-collected.""" from __future__ import annotations import asyncio import logging logger = logging.getLogger(__name__) _background_tasks: set[asyncio.Task] = set() # type: ignore[type-arg] def _log_task_outcome(task: asyncio.Task) -> None: # type: ignore[type-arg] """Surface exceptions from fire-and-forget tasks (handlers should still catch).""" try: exc = task.exception() except asyncio.CancelledError: return if exc is not None: logger.error( "Unhandled exception in background task: %s", exc, exc_info=exc, ) def spawn_background_task(coro) -> asyncio.Task: # type: ignore[no-untyped-def] """Schedule ``coro`` and retain the task until it completes.""" task = asyncio.create_task(coro) _background_tasks.add(task) task.add_done_callback(_background_tasks.discard) task.add_done_callback(_log_task_outcome) return task