新增大量渠道适配器相关的协议、策略、工具类与基础设施代码,包括: 1. 多协议定义:认证、消息、配置、网关等核心接口 2. 策略模块:上下文、群聊、去重、防抖等业务策略 3. 工具集:重试、去重、文本分块、消息格式化等SDK工具 4. 基础设施:外部进程管理、事件广播、熔断机制等 5. 账户与管道系统:账户管理、消息处理管道实现 6. 运行时服务:状态收集、维护任务、日志等后台服务
88 lines
3.1 KiB
Python
88 lines
3.1 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import time
|
|
from collections.abc import Awaitable, Callable
|
|
from enum import StrEnum
|
|
from typing import Any
|
|
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
|
|
class CircuitState(StrEnum):
|
|
CLOSED = "closed"
|
|
OPEN = "open"
|
|
HALF_OPEN = "half_open"
|
|
|
|
|
|
class CircuitBreakerOpenError(Exception):
|
|
pass
|
|
|
|
|
|
class CircuitBreaker:
|
|
def __init__(
|
|
self,
|
|
failure_threshold: int = 5,
|
|
recovery_timeout: float = 60.0,
|
|
half_open_max_calls: int = 3,
|
|
):
|
|
self.failure_threshold = failure_threshold
|
|
self.recovery_timeout = recovery_timeout
|
|
self.half_open_max_calls = half_open_max_calls
|
|
|
|
self.state = CircuitState.CLOSED
|
|
self._failure_count = 0
|
|
self._last_failure_time: float = 0.0
|
|
self._half_open_calls = 0
|
|
self._lock = asyncio.Lock()
|
|
|
|
async def call(self, func: Callable[[], Awaitable[Any]]) -> Any:
|
|
async with self._lock:
|
|
if self.state == CircuitState.OPEN:
|
|
elapsed = time.monotonic() - self._last_failure_time
|
|
if elapsed >= self.recovery_timeout:
|
|
self.state = CircuitState.HALF_OPEN
|
|
self._half_open_calls = 0
|
|
logger.info("Circuit breaker transitioned to HALF_OPEN")
|
|
else:
|
|
raise CircuitBreakerOpenError(
|
|
f"Circuit breaker is OPEN, retry after {self.recovery_timeout - elapsed:.0f}s"
|
|
)
|
|
|
|
if self.state == CircuitState.HALF_OPEN:
|
|
if self._half_open_calls >= self.half_open_max_calls:
|
|
raise CircuitBreakerOpenError("Circuit breaker HALF_OPEN max calls reached")
|
|
self._half_open_calls += 1
|
|
|
|
try:
|
|
result = await func()
|
|
await self.record_success()
|
|
return result
|
|
except Exception:
|
|
await self.record_failure()
|
|
raise
|
|
|
|
async def record_success(self) -> None:
|
|
async with self._lock:
|
|
if self.state == CircuitState.HALF_OPEN:
|
|
self.state = CircuitState.CLOSED
|
|
self._failure_count = 0
|
|
self._half_open_calls = 0
|
|
logger.info("Circuit breaker closed after successful HALF_OPEN probe")
|
|
elif self.state == CircuitState.CLOSED and self._failure_count > 0:
|
|
self._failure_count = 0
|
|
|
|
async def record_failure(self) -> None:
|
|
async with self._lock:
|
|
self._failure_count += 1
|
|
self._last_failure_time = time.monotonic()
|
|
|
|
if self.state == CircuitState.CLOSED and self._failure_count >= self.failure_threshold:
|
|
self.state = CircuitState.OPEN
|
|
logger.warning(
|
|
f"Circuit breaker OPEN after {self._failure_count} failures (threshold={self.failure_threshold})"
|
|
)
|
|
elif self.state == CircuitState.HALF_OPEN:
|
|
self.state = CircuitState.OPEN
|
|
logger.warning("Circuit breaker re-OPENed after HALF_OPEN failure")
|