""" 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