Marwan
Restructure + add reverse face search (PimEyes-style)
f5eeb1c
Raw
History Blame Contribute Delete
2.6 kB
"""
Retry policy with exponential backoff + jitter.
Used by the orchestrator to wrap provider invocations.
"""
from __future__ import annotations
import asyncio
import random
import time
from dataclasses import dataclass
from typing import Awaitable, Callable, Tuple, Type
from loguru import logger
@dataclass
class RetryPolicy:
"""Exponential backoff with full jitter."""
max_attempts: int = 3
initial_backoff_seconds: float = 0.5
max_backoff_seconds: float = 8.0
retriable_exceptions: Tuple[Type[BaseException], ...] = (
TimeoutError, ConnectionError, OSError,
)
def backoff(self, attempt: int) -> float:
"""Compute backoff for the given attempt (1-indexed)."""
delay = self.initial_backoff_seconds * (2 ** (attempt - 1))
delay = min(delay, self.max_backoff_seconds)
# full jitter
return random.uniform(0, delay)
def with_retry_sync(
fn: Callable,
policy: RetryPolicy,
label: str = "",
on_retry: Callable[[int, Exception], None] | None = None,
):
"""Synchronous retry wrapper."""
attempt = 0
last_exc: Exception | None = None
while attempt < policy.max_attempts:
try:
attempt += 1
return fn()
except policy.retriable_exceptions as e:
last_exc = e
if attempt >= policy.max_attempts:
break
delay = policy.backoff(attempt)
if on_retry:
on_retry(attempt, e)
logger.warning(
f"[retry] {label} attempt {attempt}/{policy.max_attempts} "
f"failed: {e}; sleeping {delay:.2f}s"
)
time.sleep(delay)
raise last_exc # type: ignore
async def with_retry_async(
fn: Callable[[], Awaitable],
policy: RetryPolicy,
label: str = "",
on_retry: Callable[[int, Exception], None] | None = None,
):
"""Asynchronous retry wrapper."""
attempt = 0
last_exc: Exception | None = None
while attempt < policy.max_attempts:
try:
attempt += 1
return await fn()
except policy.retriable_exceptions as e:
last_exc = e
if attempt >= policy.max_attempts:
break
delay = policy.backoff(attempt)
if on_retry:
on_retry(attempt, e)
logger.warning(
f"[retry] {label} attempt {attempt}/{policy.max_attempts} "
f"failed: {e}; sleeping {delay:.2f}s"
)
await asyncio.sleep(delay)
raise last_exc # type: ignore