mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-05-28 18:11:31 +02:00
668ce16dc7
Gate messages now propagate via the Infonet hashchain as encrypted blobs — every node syncs them through normal chain sync while only Gate members with MLS keys can decrypt. Added mesh reputation system, peer push workers, voluntary Wormhole opt-in for node participation, fork recovery, killwormhole scripts, obfuscated terminology, and hardened the self-updater to protect encryption keys and chain state during updates. New features: Shodan search, train tracking, Sentinel Hub imagery, 8 new intelligence layers, CCTV expansion to 11,000+ cameras across 6 countries, Mesh Terminal CLI, prediction markets, desktop-shell scaffold, and comprehensive mesh test suite (215 frontend + backend tests passing). Community contributors: @wa1id, @AlborzNazari, @adust09, @Xpirix, @imqdcr, @csysp, @suranyami, @chr0n1x, @johan-martensson, @singularfailure, @smithbh, @OrfeoTerkuci, @deuza, @tm-const, @Elhard1, @ttulttul
73 lines
2.4 KiB
Python
73 lines
2.4 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
|
|
import requests
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Only retry on transient network/OS errors — not on parse errors, key errors, etc.
|
|
TRANSIENT_ERRORS = (
|
|
TimeoutError,
|
|
ConnectionError,
|
|
OSError,
|
|
requests.RequestException,
|
|
)
|
|
|
|
|
|
def with_retry(max_retries: int = 3, base_delay: float = 2.0, max_delay: float = 30.0):
|
|
"""Decorator: retries the wrapped function on transient errors with exponential backoff + jitter.
|
|
|
|
Only retries on network/OS errors (TimeoutError, ConnectionError, OSError,
|
|
requests.RequestException). Non-transient errors (ValueError, KeyError, etc.)
|
|
propagate immediately.
|
|
|
|
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 TRANSIENT_ERRORS 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
|