File size: 1,680 Bytes
9f9d3dc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
"""Supervision utilities for long-lived background asyncio tasks."""
from __future__ import annotations

import asyncio
import logging
from collections.abc import Awaitable
from typing import Any

_logger = logging.getLogger("agente_ai.background_tasks")
_tasks: dict[str, asyncio.Task[Any]] = {}


def spawn_background_task(coro: Awaitable[Any], *, name: str) -> asyncio.Task[Any]:
    """Start one named background task and retain it for lifecycle shutdown.

    A live task with the same name is reused. The passed coroutine is closed in
    that case so duplicate startup calls do not leak an un-awaited coroutine.
    """
    current = _tasks.get(name)
    if current is not None and not current.done():
        close = getattr(coro, "close", None)
        if close is not None:
            close()
        return current

    task = asyncio.create_task(coro, name=name)
    _tasks[name] = task

    def _report(task_result: asyncio.Task[Any]) -> None:
        if task_result.cancelled():
            return
        try:
            error = task_result.exception()
        except asyncio.CancelledError:
            return
        if error is not None:
            _logger.error("background task %s failed: %s", name, error, exc_info=error)

    task.add_done_callback(_report)
    return task


async def shutdown_background_tasks() -> None:
    """Cancel and await all supervised background tasks."""
    tasks = [task for task in _tasks.values() if not task.done()]
    for task in tasks:
        task.cancel()
    if tasks:
        await asyncio.gather(*tasks, return_exceptions=True)
    _tasks.clear()


__all__ = ["spawn_background_task", "shutdown_background_tasks"]