ForcePilot/backend/package/yuxi/channels/sdk/retry.py

43 lines
1.2 KiB
Python
Raw Normal View History

from __future__ import annotations
import asyncio
import random
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any
from yuxi.utils.logging_config import logger
@dataclass
class RetryConfig:
max_retries: int = 3
base_delay: float = 1.0
max_delay: float = 60.0
backoff_factor: float = 2.0
jitter: bool = True
retryable_exceptions: tuple[type[Exception], ...] = (Exception,)
async def with_retry(
func: Callable[[], Awaitable[Any]],
config: RetryConfig | None = None,
) -> Any:
cfg = config or RetryConfig()
last_exception: Exception | None = None
for attempt in range(cfg.max_retries + 1):
try:
return await func()
except cfg.retryable_exceptions as e:
last_exception = e
if attempt == cfg.max_retries:
break
delay = min(cfg.base_delay * (cfg.backoff_factor**attempt), cfg.max_delay)
if cfg.jitter:
delay *= random.uniform(0.5, 1.5)
logger.debug(f"[SDK/Retry] Attempt {attempt + 1}/{cfg.max_retries} failed: {e}, retrying in {delay:.2f}s")
await asyncio.sleep(delay)
raise last_exception # type: ignore[misc]