mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 12:45:42 +02:00
Fase 2
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user