39 lines
1.1 KiB
Python
39 lines
1.1 KiB
Python
"""Bounded retry with exponential backoff + jitter."""
|
|
from __future__ import annotations
|
|
|
|
import random
|
|
import time
|
|
from typing import Callable, Optional, Tuple, Type
|
|
|
|
|
|
def retry(
|
|
fn: Callable,
|
|
*,
|
|
max_attempts: int = 3,
|
|
base_delay: float = 0.1,
|
|
max_delay: float = 10.0,
|
|
retryable: Tuple[Type[BaseException], ...] = (Exception,),
|
|
sleep: Callable[[float], None] = time.sleep,
|
|
rng: Optional[random.Random] = None,
|
|
) -> object:
|
|
if max_attempts < 1:
|
|
raise ValueError("max_attempts must be >= 1")
|
|
if base_delay < 0 or max_delay < 0:
|
|
raise ValueError("delays must be >= 0")
|
|
r = rng if rng is not None else random.Random()
|
|
last_exc = None
|
|
for attempt in range(max_attempts):
|
|
try:
|
|
return fn()
|
|
except retryable as e:
|
|
last_exc = e
|
|
if attempt == max_attempts - 1:
|
|
break
|
|
exp = min(max_delay, base_delay * (2 ** attempt))
|
|
delay = r.uniform(0, exp) if exp > 0 else 0.0
|
|
sleep(delay)
|
|
except BaseException:
|
|
raise
|
|
assert last_exc is not None
|
|
raise last_exc
|