56 lines
1.6 KiB
Python
56 lines
1.6 KiB
Python
|
|
"""简单的重试工具:指数退避 + 抖动。
|
|||
|
|
课程设计场景不引入额外依赖(如 tenacity),保持轻量。
|
|||
|
|
"""
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import random
|
|||
|
|
import time
|
|||
|
|
from typing import Callable, TypeVar, Tuple
|
|||
|
|
|
|||
|
|
T = TypeVar("T")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def retry(
|
|||
|
|
*,
|
|||
|
|
max_retries: int = 3,
|
|||
|
|
initial_delay: float = 1.0,
|
|||
|
|
backoff_factor: float = 2.0,
|
|||
|
|
max_delay: float = 10.0,
|
|||
|
|
jitter: float = 0.2,
|
|||
|
|
retry_exceptions: Tuple[type[BaseException], ...] = (Exception,),
|
|||
|
|
):
|
|||
|
|
"""重试装饰器。
|
|||
|
|
|
|||
|
|
- 第一次失败后等待 initial_delay
|
|||
|
|
- 之后按照 backoff_factor 指数增长
|
|||
|
|
- 加一点 jitter 防止固定间隔
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def decorator(func: Callable[..., T]) -> Callable[..., T]:
|
|||
|
|
def wrapper(*args, **kwargs) -> T:
|
|||
|
|
delay = initial_delay
|
|||
|
|
last_exc: BaseException | None = None
|
|||
|
|
|
|||
|
|
for attempt in range(max_retries + 1):
|
|||
|
|
try:
|
|||
|
|
return func(*args, **kwargs)
|
|||
|
|
except retry_exceptions as exc: # noqa: PERF203
|
|||
|
|
last_exc = exc
|
|||
|
|
if attempt >= max_retries:
|
|||
|
|
raise
|
|||
|
|
|
|||
|
|
# jitter:delay*(1±jitter)
|
|||
|
|
factor = 1.0 + random.uniform(-jitter, jitter)
|
|||
|
|
sleep_s = min(max_delay, max(0.0, delay * factor))
|
|||
|
|
time.sleep(sleep_s)
|
|||
|
|
delay = min(max_delay, delay * backoff_factor)
|
|||
|
|
|
|||
|
|
# 理论上不会走到这里
|
|||
|
|
assert last_exc is not None
|
|||
|
|
raise last_exc
|
|||
|
|
|
|||
|
|
return wrapper
|
|||
|
|
|
|||
|
|
return decorator
|
|||
|
|
|