Spaces:
Sleeping
Sleeping
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 |