mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 20:55:41 +02:00
82 lines
2.5 KiB
Python
82 lines
2.5 KiB
Python
"""
|
|
Strategia backoff esponenziale per riconnessioni IMAP.
|
|
|
|
Parametri configurabili in WorkerSettings:
|
|
backoff_initial_seconds = 1.0 (primo wait)
|
|
backoff_multiplier = 2.0 (moltiplicatore)
|
|
backoff_max_seconds = 300.0 (tetto massimo: 5 minuti)
|
|
|
|
Sequenza di attesa: 1s → 2s → 4s → 8s → 16s → 32s → 64s → 128s → 256s → 300s → 300s → ...
|
|
"""
|
|
|
|
import asyncio
|
|
import logging
|
|
import random
|
|
|
|
from app.config import get_settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
settings = get_settings()
|
|
|
|
|
|
class ExponentialBackoff:
|
|
"""
|
|
Gestisce il backoff esponenziale con jitter opzionale.
|
|
|
|
Uso:
|
|
backoff = ExponentialBackoff(label="casella@pec.it")
|
|
while True:
|
|
try:
|
|
await connect()
|
|
backoff.reset() # connessione riuscita → resetta backoff
|
|
await run_loop()
|
|
except Exception as e:
|
|
await backoff.wait(e) # attende prima di ritentare
|
|
"""
|
|
|
|
def __init__(self, label: str = "", jitter: bool = True) -> None:
|
|
self.label = label
|
|
self.jitter = jitter
|
|
self._attempt = 0
|
|
self._current_wait = settings.backoff_initial_seconds
|
|
|
|
def reset(self) -> None:
|
|
"""Resetta il backoff dopo una connessione riuscita."""
|
|
if self._attempt > 0:
|
|
logger.info(
|
|
f"[{self.label}] Connessione ristabilita dopo {self._attempt} tentativi"
|
|
)
|
|
self._attempt = 0
|
|
self._current_wait = settings.backoff_initial_seconds
|
|
|
|
async def wait(self, error: Exception | None = None) -> None:
|
|
"""
|
|
Attende il tempo calcolato dal backoff, poi incrementa per il prossimo ciclo.
|
|
Aggiunge jitter ±10% per evitare thundering herd su N caselle.
|
|
"""
|
|
self._attempt += 1
|
|
wait_time = min(self._current_wait, settings.backoff_max_seconds)
|
|
|
|
if self.jitter:
|
|
jitter_range = wait_time * 0.1
|
|
wait_time += random.uniform(-jitter_range, jitter_range)
|
|
wait_time = max(0.5, wait_time)
|
|
|
|
logger.warning(
|
|
f"[{self.label}] Tentativo {self._attempt} fallito"
|
|
f"{f': {error}' if error else ''}. "
|
|
f"Riconnessione in {wait_time:.1f}s"
|
|
)
|
|
|
|
await asyncio.sleep(wait_time)
|
|
|
|
# Incrementa per il prossimo tentativo
|
|
self._current_wait = min(
|
|
self._current_wait * settings.backoff_multiplier,
|
|
settings.backoff_max_seconds,
|
|
)
|
|
|
|
@property
|
|
def attempt(self) -> int:
|
|
return self._attempt
|