refactor(infra): 调整导入顺序并为熔断器添加事件回调功能
1. 调整text_chunker.py中导入包的顺序 2. 调整config_watcher.py的typing导入顺序 3. 重构__init__.py的导入排序 4. 为CircuitBreaker新增事件回调机制,支持状态变化通知
This commit is contained in:
parent
b2ea3426fd
commit
6cf9c8e9b6
@ -1,6 +1,6 @@
|
|||||||
|
from yuxi.channels.infra.broadcast import EventBroadcaster
|
||||||
from yuxi.channels.infra.circuit_breaker import CircuitBreaker, CircuitBreakerOpenError, CircuitState
|
from yuxi.channels.infra.circuit_breaker import CircuitBreaker, CircuitBreakerOpenError, CircuitState
|
||||||
from yuxi.channels.infra.config_watcher import ConfigWatcher
|
from yuxi.channels.infra.config_watcher import ConfigWatcher
|
||||||
from yuxi.channels.infra.broadcast import EventBroadcaster
|
|
||||||
from yuxi.channels.infra.external_process import ExternalProcessManager
|
from yuxi.channels.infra.external_process import ExternalProcessManager
|
||||||
from yuxi.channels.infra.text_chunker import ChunkMode, TextChunk, TextChunker
|
from yuxi.channels.infra.text_chunker import ChunkMode, TextChunk, TextChunker
|
||||||
|
|
||||||
|
|||||||
@ -19,16 +19,27 @@ class CircuitBreakerOpenError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
CircuitBreakerEventCallback = (
|
||||||
|
Callable[[str, str, dict[str, Any]], Awaitable[None]] | Callable[[str, str, dict[str, Any]], None] | None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CircuitBreaker:
|
class CircuitBreaker:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
failure_threshold: int = 5,
|
failure_threshold: int = 5,
|
||||||
recovery_timeout: float = 60.0,
|
recovery_timeout: float = 60.0,
|
||||||
half_open_max_calls: int = 3,
|
half_open_max_calls: int = 3,
|
||||||
|
channel_id: str = "",
|
||||||
|
operation: str = "default",
|
||||||
|
on_event: CircuitBreakerEventCallback = None,
|
||||||
):
|
):
|
||||||
self.failure_threshold = failure_threshold
|
self.failure_threshold = failure_threshold
|
||||||
self.recovery_timeout = recovery_timeout
|
self.recovery_timeout = recovery_timeout
|
||||||
self.half_open_max_calls = half_open_max_calls
|
self.half_open_max_calls = half_open_max_calls
|
||||||
|
self.channel_id = channel_id
|
||||||
|
self.operation = operation
|
||||||
|
self._on_event = on_event
|
||||||
|
|
||||||
self.state = CircuitState.CLOSED
|
self.state = CircuitState.CLOSED
|
||||||
self._failure_count = 0
|
self._failure_count = 0
|
||||||
@ -44,6 +55,7 @@ class CircuitBreaker:
|
|||||||
self.state = CircuitState.HALF_OPEN
|
self.state = CircuitState.HALF_OPEN
|
||||||
self._half_open_calls = 0
|
self._half_open_calls = 0
|
||||||
logger.info("Circuit breaker transitioned to HALF_OPEN")
|
logger.info("Circuit breaker transitioned to HALF_OPEN")
|
||||||
|
await self._emit_event("on_half_open", {})
|
||||||
else:
|
else:
|
||||||
raise CircuitBreakerOpenError(
|
raise CircuitBreakerOpenError(
|
||||||
f"Circuit breaker is OPEN, retry after {self.recovery_timeout - elapsed:.0f}s"
|
f"Circuit breaker is OPEN, retry after {self.recovery_timeout - elapsed:.0f}s"
|
||||||
@ -69,6 +81,7 @@ class CircuitBreaker:
|
|||||||
self._failure_count = 0
|
self._failure_count = 0
|
||||||
self._half_open_calls = 0
|
self._half_open_calls = 0
|
||||||
logger.info("Circuit breaker closed after successful HALF_OPEN probe")
|
logger.info("Circuit breaker closed after successful HALF_OPEN probe")
|
||||||
|
await self._emit_event("on_close", {})
|
||||||
elif self.state == CircuitState.CLOSED and self._failure_count > 0:
|
elif self.state == CircuitState.CLOSED and self._failure_count > 0:
|
||||||
self._failure_count = 0
|
self._failure_count = 0
|
||||||
|
|
||||||
@ -82,6 +95,24 @@ class CircuitBreaker:
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
f"Circuit breaker OPEN after {self._failure_count} failures (threshold={self.failure_threshold})"
|
f"Circuit breaker OPEN after {self._failure_count} failures (threshold={self.failure_threshold})"
|
||||||
)
|
)
|
||||||
|
await self._emit_event("on_open", {"failure_count": self._failure_count})
|
||||||
elif self.state == CircuitState.HALF_OPEN:
|
elif self.state == CircuitState.HALF_OPEN:
|
||||||
self.state = CircuitState.OPEN
|
self.state = CircuitState.OPEN
|
||||||
logger.warning("Circuit breaker re-OPENed after HALF_OPEN failure")
|
logger.warning("Circuit breaker re-OPENed after HALF_OPEN failure")
|
||||||
|
await self._emit_event("on_open", {"failure_count": self._failure_count, "from": "half_open"})
|
||||||
|
|
||||||
|
async def _emit_event(self, event: str, detail: dict[str, Any]) -> None:
|
||||||
|
if self._on_event is None:
|
||||||
|
return
|
||||||
|
payload = {
|
||||||
|
"channel_id": self.channel_id,
|
||||||
|
"operation": self.operation,
|
||||||
|
"state": self.state,
|
||||||
|
**detail,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
result = self._on_event(event, self.channel_id, payload) # type: ignore[call-arg]
|
||||||
|
if asyncio.iscoroutine(result):
|
||||||
|
await result
|
||||||
|
except Exception:
|
||||||
|
logger.debug(f"CircuitBreaker event callback error: {event}", exc_info=True)
|
||||||
|
|||||||
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
from typing import Any, TYPE_CHECKING
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from yuxi.utils.logging_config import logger
|
from yuxi.utils.logging_config import logger
|
||||||
|
|
||||||
|
|||||||
@ -2,8 +2,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from enum import StrEnum
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from enum import StrEnum
|
||||||
|
|
||||||
|
|
||||||
class ChunkMode(StrEnum):
|
class ChunkMode(StrEnum):
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user