from __future__ import annotations import asyncio import logging from collections.abc import Awaitable, Callable from typing import Any, TypeVar logger = logging.getLogger(__name__) T = TypeVar("T") async def _maybe_await(result: Any) -> None: if isinstance(result, Awaitable): await result async def wait_until_abort( abort_event: asyncio.Event | None = None, *, on_abort: Callable[[], Any] | None = None, ) -> None: if abort_event is None: if on_abort is not None: logger.warning("wait_until_abort: on_abort provided but abort_event is None, callback ignored") await asyncio.Event().wait() return await abort_event.wait() if on_abort is not None: await _maybe_await(on_abort()) async def run_passive_account_lifecycle( start: Callable[[], Awaitable[T]], *, abort_event: asyncio.Event | None = None, stop: Callable[[T], Awaitable[Any]] | None = None, on_stop: Callable[[], Awaitable[Any]] | None = None, ) -> None: handle = await start() try: await wait_until_abort(abort_event) finally: if stop is not None: await stop(handle) if on_stop is not None: await on_stop() async def keep_http_server_task_alive( server_close_event: asyncio.Event, *, abort_event: asyncio.Event | None = None, on_abort: Callable[[], Any] | None = None, ) -> None: abort_triggered = False async def _trigger_abort() -> None: nonlocal abort_triggered if abort_triggered: return abort_triggered = True if on_abort is not None: await _maybe_await(on_abort()) async def _on_abort_event() -> None: if abort_event is not None: await abort_event.wait() await _trigger_abort() abort_task = asyncio.create_task(_on_abort_event()) try: await server_close_event.wait() finally: abort_task.cancel() try: await abort_task except asyncio.CancelledError: pass await _trigger_abort()