新增trace_ctx实现链路ID上下文管理与日志注入,添加boot.py启动管理器、startup.py启动任务执行器、backoff.py退避策略工具以及trace.py启动链路追踪模块,完善channel运行时基础能力
110 lines
3.7 KiB
Python
110 lines
3.7 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
import time
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, Awaitable, Callable
|
|
|
|
from yuxi.channel.runtime.backoff import BackoffConfig
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
StartupTask = Callable[[], Awaitable[Any]]
|
|
|
|
|
|
@dataclass
|
|
class StartupTaskResult:
|
|
name: str
|
|
success: bool
|
|
duration_ms: float
|
|
error: str | None = None
|
|
skipped: bool = False
|
|
|
|
|
|
_StartupBackoffConfig = BackoffConfig(
|
|
base_delay=0.5,
|
|
max_delay=5.0,
|
|
exponent=1.5,
|
|
jitter=True,
|
|
jitter_factor=0.1,
|
|
max_retries=3,
|
|
)
|
|
|
|
|
|
class StartupTaskRunner:
|
|
"""启动任务执行器 — 支持超时、重试、并行执行与结果追踪。"""
|
|
|
|
def __init__(self, default_timeout: float = 30.0, max_retries: int = 2, backoff_config: BackoffConfig | None = None):
|
|
self.default_timeout = default_timeout
|
|
self.max_retries = max_retries
|
|
self._backoff_config = backoff_config or _StartupBackoffConfig
|
|
self._results: list[StartupTaskResult] = []
|
|
self._results_lock = asyncio.Lock()
|
|
|
|
async def run(
|
|
self,
|
|
task: StartupTask,
|
|
*,
|
|
name: str | None = None,
|
|
timeout: float | None = None,
|
|
retries: int | None = None,
|
|
) -> StartupTaskResult:
|
|
task_name = name or getattr(task, "__name__", "unknown")
|
|
timeout_val = timeout if timeout is not None else self.default_timeout
|
|
retry_count = retries if retries is not None else self.max_retries
|
|
|
|
start = time.time()
|
|
last_error: Exception | None = None
|
|
|
|
for attempt in range(retry_count + 1):
|
|
try:
|
|
await asyncio.wait_for(task(), timeout=timeout_val)
|
|
duration_ms = (time.time() - start) * 1000
|
|
result = StartupTaskResult(
|
|
name=task_name,
|
|
success=True,
|
|
duration_ms=duration_ms,
|
|
)
|
|
async with self._results_lock:
|
|
self._results.append(result)
|
|
logger.info("Startup task '%s' completed in %.2fms", task_name, duration_ms)
|
|
return result
|
|
except asyncio.TimeoutError:
|
|
last_error = asyncio.TimeoutError(f"Task '{task_name}' timed out after {timeout_val}s")
|
|
logger.warning("Startup task '%s' timed out (attempt %d/%d)", task_name, attempt + 1, retry_count + 1)
|
|
except Exception as e:
|
|
last_error = e
|
|
logger.exception("Startup task '%s' failed (attempt %d/%d)", task_name, attempt + 1, retry_count + 1)
|
|
if attempt < retry_count:
|
|
await asyncio.sleep(self._backoff_config.compute_delay(attempt))
|
|
|
|
duration_ms = (time.time() - start) * 1000
|
|
result = StartupTaskResult(
|
|
name=task_name,
|
|
success=False,
|
|
duration_ms=duration_ms,
|
|
error=str(last_error) if last_error else "Unknown error",
|
|
)
|
|
async with self._results_lock:
|
|
self._results.append(result)
|
|
return result
|
|
|
|
async def run_parallel(
|
|
self,
|
|
tasks: list[tuple[StartupTask, str]],
|
|
*,
|
|
timeout: float | None = None,
|
|
) -> list[StartupTaskResult]:
|
|
"""并行执行多个启动任务。"""
|
|
coros = [self.run(task, name=name, timeout=timeout) for task, name in tasks]
|
|
return await asyncio.gather(*coros, return_exceptions=True)
|
|
|
|
async def get_results(self) -> list[StartupTaskResult]:
|
|
async with self._results_lock:
|
|
return list(self._results)
|
|
|
|
async def clear_results(self) -> None:
|
|
async with self._results_lock:
|
|
self._results.clear()
|