"""Timeout protection utilities for background tasks. Provides helpers to wrap async task functions with asyncio.wait_for() timeout protection. Ensures tasks complete within bounded time or fail gracefully with proper logging and error handling. """ from __future__ import annotations import asyncio import time from collections.abc import Awaitable from typing import TypeVar import structlog log: structlog.stdlib.BoundLogger = structlog.get_logger() T = TypeVar("T") async def run_with_timeout( task_name: str, coro: Awaitable[T], timeout_seconds: int, ) -> T: """Run an async coroutine with timeout protection. Args: task_name: Human-readable name of the task for logging. coro: The coroutine to execute. timeout_seconds: Maximum seconds to wait before timeout. Raises: asyncio.TimeoutError: If the task exceeds the timeout. Returns: The return value of the coroutine. """ start_time = time.monotonic() try: result: T = await asyncio.wait_for(coro, timeout=timeout_seconds) elapsed = time.monotonic() - start_time if elapsed > timeout_seconds * 0.8: log.warning( "task_approaching_timeout", task_name=task_name, timeout_seconds=timeout_seconds, elapsed_seconds=round(elapsed, 2), usage_percent=round((elapsed / timeout_seconds) * 100, 1), ) return result except TimeoutError: elapsed = time.monotonic() - start_time log.warning( "task_timeout", task_name=task_name, timeout_seconds=timeout_seconds, elapsed_seconds=round(elapsed, 2), ) raise