colibri.assistant.ai / utils /circuit_breaker.py
Gouzi Mohaled
Ajoute des fichiers et sous-dossiers supplémentaires
fe4792e
raw
history blame
2.89 kB
import asyncio
from enum import Enum
from datetime import datetime, timedelta
import logging
from typing import Optional, Callable, Any
from utils.logging_utils import log_to_file
class CircuitState(Enum):
CLOSED = "CLOSED" # Circuit fermé - Opérations normales
OPEN = "OPEN" # Circuit ouvert - Échecs détectés
HALF_OPEN = "HALF_OPEN" # Circuit semi-ouvert - Test de récupération
class AsyncCircuitBreaker:
def __init__(
self,
failure_threshold: int = 5,
recovery_timeout: int = 60,
half_open_timeout: int = 30
):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.half_open_timeout = half_open_timeout
self.state = CircuitState.CLOSED
self.failure_count = 0
self.last_failure_time: Optional[datetime] = None
self.lock = asyncio.Lock()
async def call(self, func: Callable, *args, **kwargs) -> Any:
"""Exécute la fonction avec la logique du circuit breaker"""
async with self.lock:
if self.state == CircuitState.OPEN:
if self._should_attempt_recovery():
self.state = CircuitState.HALF_OPEN
log_to_file("Circuit breaker passé en état HALF_OPEN", level=logging.INFO)
else:
raise Exception("Circuit breaker ouvert - Service indisponible")
try:
result = await func(*args, **kwargs)
if self.state == CircuitState.HALF_OPEN:
self._reset()
return result
except Exception as e:
await self._handle_failure(e)
raise
def _should_attempt_recovery(self) -> bool:
"""Vérifie si on doit tenter une récupération"""
if not self.last_failure_time:
return True
recovery_time = self.last_failure_time + timedelta(seconds=self.recovery_timeout)
return datetime.now() > recovery_time
async def _handle_failure(self, exception: Exception) -> None:
"""Gère un échec d'appel"""
self.failure_count += 1
self.last_failure_time = datetime.now()
if self.state == CircuitState.HALF_OPEN or self.failure_count >= self.failure_threshold:
self.state = CircuitState.OPEN
log_to_file(f"Circuit breaker ouvert après {self.failure_count} échecs", level=logging.WARNING)
def _reset(self) -> None:
"""Réinitialise le circuit breaker"""
self.state = CircuitState.CLOSED
self.failure_count = 0
self.last_failure_time = None
log_to_file("Circuit breaker réinitialisé et fermé", level=logging.INFO)
def get_state(self) -> CircuitState:
"""Retourne l'état actuel du circuit breaker"""
return self.state