mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-07-13 15:27:25 +02:00
90c2e90e2c
- Parallelized startup (60s → 15s) via ThreadPoolExecutor - Adaptive polling engine with ETag caching (no more bbox interrupts) - useCallback optimization for interpolation functions - Sliding LAYERS/INTEL edge panels replace bulky Record Panel - Modular fetcher architecture (flights, geo, infrastructure, financial, earth_observation) - Stable entity IDs for GDELT & News popups (PR #63, credit @csysp) - Admin auth (X-Admin-Key), rate limiting (slowapi), auto-updater - Docker Swarm secrets support, env_check.py validation - 85+ vitest tests, CI pipeline, geoJSON builder extraction - Server-side viewport bbox filtering reduces payloads 80%+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Former-commit-id: f2883150b5bc78ebc139d89cc966a76f7d7c0408
50 lines
1.8 KiB
Python
50 lines
1.8 KiB
Python
"""Retry decorator with exponential backoff + jitter for network-bound fetcher functions.
|
|
|
|
Usage:
|
|
@with_retry(max_retries=3, base_delay=2)
|
|
def fetch_something():
|
|
...
|
|
"""
|
|
import time
|
|
import random
|
|
import logging
|
|
import functools
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def with_retry(max_retries: int = 3, base_delay: float = 2.0, max_delay: float = 30.0):
|
|
"""Decorator: retries the wrapped function on any exception with exponential backoff + jitter.
|
|
|
|
Args:
|
|
max_retries: Number of retry attempts after the initial failure.
|
|
base_delay: Base delay (seconds) for exponential backoff (2 → 4 → 8 …).
|
|
max_delay: Cap on the delay between retries.
|
|
"""
|
|
def decorator(func):
|
|
@functools.wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
last_exc = None
|
|
for attempt in range(1 + max_retries):
|
|
try:
|
|
return func(*args, **kwargs)
|
|
except Exception as exc:
|
|
last_exc = exc
|
|
if attempt < max_retries:
|
|
delay = min(base_delay * (2 ** attempt), max_delay)
|
|
jitter = random.uniform(0, delay * 0.25)
|
|
total = delay + jitter
|
|
logger.warning(
|
|
"%s failed (attempt %d/%d): %s — retrying in %.1fs",
|
|
func.__name__, attempt + 1, max_retries + 1, exc, total,
|
|
)
|
|
time.sleep(total)
|
|
else:
|
|
logger.error(
|
|
"%s failed after %d attempts: %s",
|
|
func.__name__, max_retries + 1, exc,
|
|
)
|
|
raise last_exc # type: ignore[misc]
|
|
return wrapper
|
|
return decorator
|