use crate::clock::{Clock, SystemClock};
use crate::constants::{
DEFAULT_CIRCUIT_BREAKER_FAILURE_THRESHOLD, DEFAULT_CIRCUIT_BREAKER_HALF_OPEN_MAX_CALLS,
DEFAULT_CIRCUIT_BREAKER_SLOW_CALL_DURATION_MILLIS,
DEFAULT_CIRCUIT_BREAKER_SLOW_CALL_RATE_THRESHOLD, DEFAULT_CIRCUIT_BREAKER_SUCCESS_THRESHOLD,
DEFAULT_CIRCUIT_BREAKER_TIMEOUT_SECS,
};
use crate::error::{CircuitBreakerStats, CircuitState, LimiteronError};
use log::{info, trace, warn};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
pub trait ErrorClassifier: Send + Sync + std::fmt::Debug {
fn is_counted_as_failure(&self, error: &LimiteronError) -> bool;
}
#[derive(Debug)]
pub struct DefaultErrorClassifier;
impl ErrorClassifier for DefaultErrorClassifier {
fn is_counted_as_failure(&self, error: &LimiteronError) -> bool {
match error {
LimiteronError::StorageError(storage_err) => storage_err.is_transient(),
LimiteronError::LimitError(_) | LimiteronError::CircuitBreakerError(_) => false,
LimiteronError::ValidationError(_) => false,
_ => true,
}
}
}
#[derive(Debug, Clone)]
pub struct CircuitBreakerConfig {
pub failure_threshold: u64,
pub success_threshold: u64,
pub timeout: Duration,
pub half_open_max_calls: u64,
pub slow_call_duration_threshold: Duration,
pub slow_call_rate_threshold: f64,
pub error_classifier: Arc<dyn ErrorClassifier>,
}
impl Default for CircuitBreakerConfig {
fn default() -> Self {
Self {
failure_threshold: DEFAULT_CIRCUIT_BREAKER_FAILURE_THRESHOLD,
success_threshold: DEFAULT_CIRCUIT_BREAKER_SUCCESS_THRESHOLD,
timeout: Duration::from_secs(DEFAULT_CIRCUIT_BREAKER_TIMEOUT_SECS),
half_open_max_calls: DEFAULT_CIRCUIT_BREAKER_HALF_OPEN_MAX_CALLS,
slow_call_duration_threshold: Duration::from_millis(
DEFAULT_CIRCUIT_BREAKER_SLOW_CALL_DURATION_MILLIS,
),
slow_call_rate_threshold: DEFAULT_CIRCUIT_BREAKER_SLOW_CALL_RATE_THRESHOLD,
error_classifier: Arc::new(DefaultErrorClassifier),
}
}
}
impl CircuitBreakerConfig {
pub fn new(failure_threshold: u64, success_threshold: u64, timeout: Duration) -> Self {
Self {
failure_threshold,
success_threshold,
timeout,
half_open_max_calls: DEFAULT_CIRCUIT_BREAKER_HALF_OPEN_MAX_CALLS,
slow_call_duration_threshold: Duration::from_millis(
DEFAULT_CIRCUIT_BREAKER_SLOW_CALL_DURATION_MILLIS,
),
slow_call_rate_threshold: DEFAULT_CIRCUIT_BREAKER_SLOW_CALL_RATE_THRESHOLD,
error_classifier: Arc::new(DefaultErrorClassifier),
}
}
pub fn half_open_max_calls(mut self, max_calls: u64) -> Self {
self.half_open_max_calls = max_calls;
self
}
pub fn slow_call_duration_threshold(mut self, threshold: Duration) -> Self {
self.slow_call_duration_threshold = threshold;
self
}
pub fn slow_call_rate_threshold(mut self, threshold: f64) -> Self {
self.slow_call_rate_threshold = threshold;
self
}
pub fn error_classifier(mut self, classifier: Arc<dyn ErrorClassifier>) -> Self {
self.error_classifier = classifier;
self
}
}
pub struct CircuitBreaker {
state: Arc<RwLock<CircuitState>>,
failure_count: Arc<AtomicU64>,
success_count: Arc<AtomicU64>,
total_calls: Arc<AtomicU64>,
slow_call_count: Arc<AtomicU64>,
last_failure_time: Arc<RwLock<Option<Instant>>>,
last_state_change: Arc<RwLock<Option<Instant>>>,
half_open_calls: Arc<AtomicU64>,
config: CircuitBreakerConfig,
clock: Arc<dyn Clock>,
#[cfg(feature = "event-system")]
event_emitter: Option<Arc<crate::events::EventEmitter>>,
}
#[derive(Debug, Clone)]
pub struct CircuitBreakerBuilder {
config: CircuitBreakerConfig,
}
impl CircuitBreakerBuilder {
pub fn new() -> Self {
Self {
config: CircuitBreakerConfig::default(),
}
}
pub fn failure_threshold(mut self, failure_threshold: u64) -> Self {
self.config.failure_threshold = failure_threshold;
self
}
pub fn success_threshold(mut self, success_threshold: u64) -> Self {
self.config.success_threshold = success_threshold;
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.config.timeout = timeout;
self
}
pub fn half_open_max_calls(mut self, max_calls: u64) -> Self {
self.config.half_open_max_calls = max_calls;
self
}
pub fn slow_call_duration_threshold(mut self, threshold: Duration) -> Self {
self.config.slow_call_duration_threshold = threshold;
self
}
pub fn slow_call_rate_threshold(mut self, threshold: f64) -> Self {
self.config.slow_call_rate_threshold = threshold;
self
}
pub fn error_classifier(mut self, classifier: Arc<dyn ErrorClassifier>) -> Self {
self.config.error_classifier = classifier;
self
}
pub fn build(&self) -> CircuitBreaker {
CircuitBreaker::with_dependencies(self.config.clone())
}
}
impl Default for CircuitBreakerBuilder {
fn default() -> Self {
Self::new()
}
}
impl CircuitBreaker {
pub fn with_dependencies(config: CircuitBreakerConfig) -> Self {
Self::with_clock(config, Arc::new(SystemClock))
}
pub fn with_clock(config: CircuitBreakerConfig, clock: Arc<dyn Clock>) -> Self {
info!(
"创建熔断器: failure_threshold={}, success_threshold={}, timeout={:?}",
config.failure_threshold, config.success_threshold, config.timeout
);
Self {
state: Arc::new(RwLock::new(CircuitState::Closed)),
failure_count: Arc::new(AtomicU64::new(0)),
success_count: Arc::new(AtomicU64::new(0)),
total_calls: Arc::new(AtomicU64::new(0)),
slow_call_count: Arc::new(AtomicU64::new(0)),
last_failure_time: Arc::new(RwLock::new(None)),
last_state_change: Arc::new(RwLock::new(Some(clock.now()))),
half_open_calls: Arc::new(AtomicU64::new(0)),
config,
clock,
#[cfg(feature = "event-system")]
event_emitter: None,
}
}
pub fn builder() -> CircuitBreakerBuilder {
CircuitBreakerBuilder::new()
}
pub fn new(config: CircuitBreakerConfig) -> Self {
Self::with_dependencies(config)
}
}
impl Default for CircuitBreaker {
fn default() -> Self {
Self::with_dependencies(CircuitBreakerConfig::default())
}
}
impl CircuitBreaker {
pub async fn execute<F, Fut, T>(&self, operation: F) -> Result<T, LimiteronError>
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = Result<T, LimiteronError>>,
{
self.total_calls.fetch_add(1, Ordering::Relaxed);
let state = self.state.read().await;
match *state {
CircuitState::Open => {
let last_failure = self.last_failure_time.read().await;
if let Some(last_failure) = *last_failure {
if self.clock.now().duration_since(last_failure) >= self.config.timeout {
drop(state);
self.transition_to_half_open().await;
} else {
drop(state);
warn!("熔断器打开,拒绝请求");
return Err(LimiteronError::LimitError(
"熔断器打开,请求被拒绝".to_string(),
));
}
}
}
CircuitState::HalfOpen => {
let calls = self.half_open_calls.load(Ordering::Relaxed);
if calls >= self.config.half_open_max_calls {
drop(state);
warn!("半开状态调用次数已达上限,拒绝请求");
return Err(LimiteronError::LimitError(
"半开状态调用次数已达上限".to_string(),
));
}
self.half_open_calls.fetch_add(1, Ordering::Relaxed);
drop(state);
}
CircuitState::Closed => {
drop(state);
}
}
let start_time = self.clock.now();
let result = operation().await;
let elapsed = start_time.elapsed();
self.record_call_duration(elapsed).await;
match result {
Ok(value) => {
self.on_success().await;
Ok(value)
}
Err(e) => {
self.on_failure(&e).await;
Err(e)
}
}
}
async fn on_success(&self) {
let state = self.state.read().await;
match *state {
CircuitState::Closed => {
self.failure_count.store(0, Ordering::Relaxed);
self.success_count.fetch_add(1, Ordering::Relaxed);
trace!("操作成功(关闭状态)");
}
CircuitState::HalfOpen => {
let success_count = self.success_count.fetch_add(1, Ordering::Relaxed) + 1;
if success_count >= self.config.success_threshold {
drop(state);
self.transition_to_closed().await;
} else {
trace!(
"操作成功(半开状态): {}/{}",
success_count, self.config.success_threshold
);
}
}
CircuitState::Open => {
warn!("熔断器打开状态下收到成功响应");
}
}
}
async fn on_failure(&self, error: &LimiteronError) {
if !self.config.error_classifier.is_counted_as_failure(error) {
trace!("错误不计入失败计数: {:?}", error);
return;
}
let state = self.state.read().await;
match *state {
CircuitState::Closed => {
let failure_count = self.failure_count.fetch_add(1, Ordering::Relaxed) + 1;
*self.last_failure_time.write().await = Some(self.clock.now());
if failure_count >= self.config.failure_threshold {
drop(state);
self.transition_to_open().await;
} else {
trace!(
"操作失败(关闭状态): {}/{}",
failure_count, self.config.failure_threshold
);
}
}
CircuitState::HalfOpen => {
drop(state);
self.transition_to_open().await;
}
CircuitState::Open => {
warn!("熔断器打开状态下收到失败响应");
}
}
}
async fn record_call_duration(&self, elapsed: Duration) {
if elapsed >= self.config.slow_call_duration_threshold {
let slow_calls = self.slow_call_count.fetch_add(1, Ordering::Relaxed) + 1;
let total_calls = self.total_calls.load(Ordering::Relaxed);
trace!(
"慢调用检测: elapsed={:?}, threshold={:?}, slow_calls={}/{}",
elapsed, self.config.slow_call_duration_threshold, slow_calls, total_calls
);
self.check_slow_call_rate(slow_calls, total_calls).await;
}
}
async fn check_slow_call_rate(&self, slow_calls: u64, total_calls: u64) {
if total_calls == 0 {
return;
}
let slow_call_rate = slow_calls as f64 / total_calls as f64;
if slow_call_rate >= self.config.slow_call_rate_threshold {
let state = self.state.read().await;
if *state == CircuitState::Closed {
drop(state);
warn!(
"慢调用率超过阈值: {:.2}% >= {:.2}%,触发熔断",
slow_call_rate * 100.0,
self.config.slow_call_rate_threshold * 100.0
);
self.transition_to_open().await;
}
}
}
async fn transition_to(&self, new_state: CircuitState) {
let old_state = *self.state.read().await;
if old_state == new_state {
return; }
*self.state.write().await = new_state;
*self.last_state_change.write().await = Some(self.clock.now());
match new_state {
CircuitState::Open => {
self.success_count.store(0, Ordering::Relaxed);
self.half_open_calls.store(0, Ordering::Relaxed);
warn!(
"熔断器状态变更: {:?} -> Open (failure_count={})",
old_state,
self.failure_count.load(Ordering::Relaxed)
);
}
CircuitState::HalfOpen => {
self.success_count.store(0, Ordering::Relaxed);
self.half_open_calls.store(1, Ordering::Relaxed);
info!("熔断器状态变更: {:?} -> HalfOpen", old_state);
}
CircuitState::Closed => {
self.failure_count.store(0, Ordering::Relaxed);
self.success_count.store(0, Ordering::Relaxed);
self.half_open_calls.store(0, Ordering::Relaxed);
self.slow_call_count.store(0, Ordering::Relaxed);
info!("熔断器状态变更: {:?} -> Closed", old_state);
}
}
#[cfg(feature = "event-system")]
{
if let Some(ref emitter) = self.event_emitter {
let old_state_str = format!("{:?}", old_state);
let new_state_str = format!("{:?}", new_state);
let event =
crate::events::Event::new(crate::events::EventType::CircuitStateChanged {
from: old_state_str,
to: new_state_str,
});
if let Err(e) = emitter.emit(event).await {
log::error!("Failed to emit circuit state change event: {}", e);
}
}
}
}
async fn transition_to_open(&self) {
self.transition_to(CircuitState::Open).await;
}
async fn transition_to_half_open(&self) {
self.transition_to(CircuitState::HalfOpen).await;
}
async fn transition_to_closed(&self) {
self.transition_to(CircuitState::Closed).await;
}
async fn is_state(&self, target_state: CircuitState) -> bool {
let state = self.state.read().await;
*state == target_state
}
pub async fn is_open(&self) -> bool {
self.is_state(CircuitState::Open).await
}
pub async fn is_half_open(&self) -> bool {
self.is_state(CircuitState::HalfOpen).await
}
pub async fn is_closed(&self) -> bool {
self.is_state(CircuitState::Closed).await
}
pub async fn get_state(&self) -> CircuitState {
*self.state.read().await
}
pub async fn reset(&self) {
info!("重置熔断器");
*self.state.write().await = CircuitState::Closed;
self.failure_count.store(0, Ordering::Relaxed);
self.success_count.store(0, Ordering::Relaxed);
self.total_calls.store(0, Ordering::Relaxed);
self.slow_call_count.store(0, Ordering::Relaxed);
*self.last_failure_time.write().await = None;
*self.last_state_change.write().await = Some(self.clock.now());
self.half_open_calls.store(0, Ordering::Relaxed);
}
pub async fn get_stats(&self) -> CircuitBreakerStats {
let state = *self.state.read().await;
let last_failure = self.last_failure_time.read().await;
let last_state_change = self.last_state_change.read().await;
CircuitBreakerStats {
state,
failure_count: self.failure_count.load(Ordering::Relaxed),
success_count: self.success_count.load(Ordering::Relaxed),
total_calls: self.total_calls.load(Ordering::Relaxed),
last_failure_time: last_failure.and_then(|t| {
let elapsed = self.clock.now().duration_since(t);
let duration = chrono::Duration::from_std(elapsed).ok()?;
Some(chrono::Utc::now() - duration)
}),
last_state_change: last_state_change.and_then(|t| {
let elapsed = self.clock.now().duration_since(t);
let duration = chrono::Duration::from_std(elapsed).ok()?;
Some(chrono::Utc::now() - duration)
}),
}
}
pub fn config(&self) -> &CircuitBreakerConfig {
&self.config
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_circuit_breaker_config_default() {
let config = CircuitBreakerConfig::default();
assert_eq!(config.failure_threshold, 5);
assert_eq!(config.success_threshold, 3);
assert_eq!(config.timeout, Duration::from_secs(30));
assert_eq!(config.half_open_max_calls, 3);
}
#[test]
fn test_circuit_breaker_config_new() {
let config = CircuitBreakerConfig::new(10, 3, Duration::from_secs(120));
assert_eq!(config.failure_threshold, 10);
assert_eq!(config.success_threshold, 3);
assert_eq!(config.timeout, Duration::from_secs(120));
}
#[test]
fn test_circuit_breaker_config_builder() {
let config =
CircuitBreakerConfig::new(5, 2, Duration::from_secs(60)).half_open_max_calls(5);
assert_eq!(config.half_open_max_calls, 5);
}
#[tokio::test]
async fn test_circuit_breaker_initial_state() {
let breaker = CircuitBreaker::default();
assert!(breaker.is_closed().await);
assert!(!breaker.is_open().await);
assert!(!breaker.is_half_open().await);
let stats = breaker.get_stats().await;
assert_eq!(stats.state, CircuitState::Closed);
assert_eq!(stats.failure_count, 0);
assert_eq!(stats.success_count, 0);
assert_eq!(stats.total_calls, 0);
}
#[tokio::test]
async fn test_circuit_breaker_success() {
let breaker = CircuitBreaker::default();
let result = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
assert!(result.is_ok());
let stats = breaker.get_stats().await;
assert_eq!(stats.success_count, 1);
assert_eq!(stats.failure_count, 0);
assert_eq!(stats.total_calls, 1);
assert!(breaker.is_closed().await);
}
#[tokio::test]
async fn test_circuit_breaker_failure() {
let config = CircuitBreakerConfig::new(3, 2, Duration::from_secs(60));
let breaker = CircuitBreaker::new(config);
let result = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::BanError("test error".to_string()))
})
.await;
assert!(result.is_err());
let stats = breaker.get_stats().await;
assert_eq!(stats.failure_count, 1);
assert!(breaker.is_closed().await);
let result = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::BanError("test error".to_string()))
})
.await;
assert!(result.is_err());
let stats = breaker.get_stats().await;
assert_eq!(stats.failure_count, 2);
assert!(breaker.is_closed().await);
let result = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::BanError("test error".to_string()))
})
.await;
assert!(result.is_err());
let stats = breaker.get_stats().await;
assert_eq!(stats.failure_count, 3);
assert!(breaker.is_open().await);
}
#[tokio::test]
async fn test_circuit_breaker_open_rejects_requests() {
let config = CircuitBreakerConfig::new(2, 2, Duration::from_secs(60));
let breaker = CircuitBreaker::new(config);
for _ in 0..2 {
let _ = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::BanError("test error".to_string()))
})
.await;
}
assert!(breaker.is_open().await);
let result = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("熔断器打开"));
}
#[tokio::test]
async fn test_circuit_breaker_half_open_recovery() {
let config = CircuitBreakerConfig::new(2, 2, Duration::from_millis(100));
let breaker = CircuitBreaker::new(config);
for _ in 0..2 {
let _ = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::BanError("test error".to_string()))
})
.await;
}
assert!(breaker.is_open().await);
tokio::time::sleep(Duration::from_millis(150)).await;
let result = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
assert!(result.is_ok());
assert!(breaker.is_half_open().await);
let result = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
assert!(result.is_ok());
assert!(breaker.is_closed().await);
}
#[tokio::test]
async fn test_circuit_breaker_half_open_failure() {
let config = CircuitBreakerConfig::new(2, 2, Duration::from_millis(100));
let breaker = CircuitBreaker::new(config);
for _ in 0..2 {
let _ = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::BanError("test error".to_string()))
})
.await;
}
assert!(breaker.is_open().await);
tokio::time::sleep(Duration::from_millis(150)).await;
let result = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
assert!(result.is_ok());
assert!(breaker.is_half_open().await);
let result = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::BanError("test error".to_string()))
})
.await;
assert!(result.is_err());
assert!(breaker.is_open().await);
}
#[tokio::test]
async fn test_circuit_breaker_reset() {
let config = CircuitBreakerConfig::new(2, 2, Duration::from_secs(60));
let breaker = CircuitBreaker::new(config);
for _ in 0..2 {
let _ = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::BanError("test error".to_string()))
})
.await;
}
assert!(breaker.is_open().await);
breaker.reset().await;
assert!(breaker.is_closed().await);
let stats = breaker.get_stats().await;
assert_eq!(stats.failure_count, 0);
assert_eq!(stats.success_count, 0);
assert_eq!(stats.total_calls, 0);
}
#[tokio::test]
async fn test_circuit_breaker_get_state() {
let breaker = CircuitBreaker::default();
assert_eq!(breaker.get_state().await, CircuitState::Closed);
}
#[tokio::test]
async fn test_circuit_breaker_get_stats() {
let breaker = CircuitBreaker::default();
let _ = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
let stats = breaker.get_stats().await;
assert_eq!(stats.state, CircuitState::Closed);
assert_eq!(stats.success_count, 1);
assert_eq!(stats.total_calls, 1);
assert!(stats.last_state_change.is_some());
}
#[tokio::test]
async fn test_circuit_breaker_half_open_max_calls() {
let config =
CircuitBreakerConfig::new(2, 3, Duration::from_millis(100)).half_open_max_calls(2);
let breaker = CircuitBreaker::new(config);
for _ in 0..2 {
let _ = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::BanError("test error".to_string()))
})
.await;
}
assert!(breaker.is_open().await);
tokio::time::sleep(Duration::from_millis(150)).await;
let result = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
assert!(result.is_ok());
let result = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
assert!(result.is_ok());
let result = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("半开状态调用次数已达上限")
);
}
#[tokio::test]
async fn test_circuit_breaker_config() {
let config = CircuitBreakerConfig::new(10, 5, Duration::from_secs(30));
let breaker = CircuitBreaker::new(config);
let breaker_config = breaker.config();
assert_eq!(breaker_config.failure_threshold, 10);
assert_eq!(breaker_config.success_threshold, 5);
assert_eq!(breaker_config.timeout, Duration::from_secs(30));
}
#[tokio::test]
async fn test_state_transition_closed_to_open() {
let config = CircuitBreakerConfig::new(3, 2, Duration::from_secs(60));
let breaker = CircuitBreaker::new(config);
assert!(breaker.is_closed().await, "初始状态应为 Closed");
assert_eq!(breaker.get_state().await, CircuitState::Closed);
let result = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::BanError("error 1".to_string()))
})
.await;
assert!(result.is_err());
assert!(breaker.is_closed().await, "第一次失败后仍应为 Closed");
let result = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::BanError("error 2".to_string()))
})
.await;
assert!(result.is_err());
assert!(breaker.is_closed().await, "第二次失败后仍应为 Closed");
let result = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::BanError("error 3".to_string()))
})
.await;
assert!(result.is_err());
assert!(breaker.is_open().await, "第三次失败后应转换为 Open");
assert_eq!(breaker.get_state().await, CircuitState::Open);
}
#[tokio::test]
async fn test_state_transition_open_to_half_open() {
let config = CircuitBreakerConfig::new(2, 2, Duration::from_millis(100));
let breaker = CircuitBreaker::new(config);
for i in 0..2 {
let _ = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::BanError(format!("error {}", i)))
})
.await;
}
assert!(breaker.is_open().await, "应处于 Open 状态");
let result = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
assert!(result.is_err());
assert!(breaker.is_open().await, "未超时应保持 Open 状态");
tokio::time::sleep(Duration::from_millis(150)).await;
let result = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
assert!(result.is_ok());
assert!(breaker.is_half_open().await, "超时后应转换为 HalfOpen 状态");
}
#[tokio::test]
async fn test_state_transition_half_open_to_closed() {
let config = CircuitBreakerConfig::new(2, 2, Duration::from_millis(100));
let breaker = CircuitBreaker::new(config);
for _ in 0..2 {
let _ = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::BanError("error".to_string()))
})
.await;
}
assert!(breaker.is_open().await);
tokio::time::sleep(Duration::from_millis(150)).await;
let result = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
assert!(result.is_ok());
assert!(breaker.is_half_open().await);
let result = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
assert!(result.is_ok());
assert!(
breaker.is_closed().await,
"成功次数达到阈值后应恢复到 Closed 状态"
);
}
#[tokio::test]
async fn test_state_transition_full_cycle() {
let config = CircuitBreakerConfig::new(2, 2, Duration::from_millis(100));
let breaker = CircuitBreaker::new(config);
for _ in 0..2 {
let _ = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::BanError("error".to_string()))
})
.await;
}
assert!(breaker.is_open().await, "阶段1: 应处于 Open 状态");
tokio::time::sleep(Duration::from_millis(150)).await;
let result = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
assert!(result.is_ok());
assert!(breaker.is_half_open().await, "阶段2: 应处于 HalfOpen 状态");
let result = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
assert!(result.is_ok());
assert!(breaker.is_closed().await, "阶段3: 应恢复到 Closed 状态");
}
#[tokio::test]
async fn test_builder_pattern() {
let breaker = CircuitBreaker::builder()
.failure_threshold(10)
.success_threshold(5)
.timeout(Duration::from_secs(30))
.half_open_max_calls(4)
.build();
let config = breaker.config();
assert_eq!(config.failure_threshold, 10);
assert_eq!(config.success_threshold, 5);
assert_eq!(config.timeout, Duration::from_secs(30));
assert_eq!(config.half_open_max_calls, 4);
assert!(breaker.is_closed().await);
}
#[tokio::test]
async fn test_slow_call_duration_threshold_config() {
let config = CircuitBreakerConfig::default()
.slow_call_duration_threshold(Duration::from_millis(100));
assert_eq!(
config.slow_call_duration_threshold,
Duration::from_millis(100)
);
}
#[tokio::test]
async fn test_slow_call_rate_threshold_config() {
let config = CircuitBreakerConfig::default().slow_call_rate_threshold(0.8);
assert_eq!(config.slow_call_rate_threshold, 0.8);
}
#[test]
fn test_default_error_classifier_storage_timeout() {
let classifier = DefaultErrorClassifier;
let error = LimiteronError::StorageError(crate::error::StorageError::TimeoutError(
"timeout".into(),
));
assert!(classifier.is_counted_as_failure(&error));
}
#[test]
fn test_default_error_classifier_connection_error() {
let classifier = DefaultErrorClassifier;
let error = LimiteronError::StorageError(crate::error::StorageError::ConnectionError(
"connection".into(),
));
assert!(classifier.is_counted_as_failure(&error));
}
#[test]
fn test_default_error_classifier_limit_error() {
let classifier = DefaultErrorClassifier;
let error = LimiteronError::LimitError("rate limited".into());
assert!(!classifier.is_counted_as_failure(&error));
}
#[test]
fn test_default_error_classifier_validation_error() {
let classifier = DefaultErrorClassifier;
let error = LimiteronError::ValidationError("invalid input".into());
assert!(!classifier.is_counted_as_failure(&error));
}
#[test]
fn test_default_error_classifier_circuit_breaker_error() {
let classifier = DefaultErrorClassifier;
let error = LimiteronError::CircuitBreakerError("circuit open".into());
assert!(!classifier.is_counted_as_failure(&error));
}
#[test]
fn test_default_error_classifier_other_errors() {
let classifier = DefaultErrorClassifier;
let error = LimiteronError::Other("unknown error".into());
assert!(classifier.is_counted_as_failure(&error));
}
#[tokio::test]
async fn test_custom_error_classifier() {
#[derive(Debug)]
struct CustomClassifier;
impl ErrorClassifier for CustomClassifier {
fn is_counted_as_failure(&self, error: &LimiteronError) -> bool {
matches!(error, LimiteronError::StorageError(_))
}
}
let config = CircuitBreakerConfig {
error_classifier: Arc::new(CustomClassifier),
failure_threshold: 2,
..Default::default()
};
let breaker = CircuitBreaker::new(config);
let _ = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::ValidationError("test".into()))
})
.await;
let stats = breaker.get_stats().await;
assert_eq!(stats.failure_count, 0);
let _ = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::StorageError(
crate::error::StorageError::TimeoutError("timeout".into()),
))
})
.await;
let stats = breaker.get_stats().await;
assert_eq!(stats.failure_count, 1);
}
#[tokio::test]
async fn test_builder_with_slow_call_config() {
let breaker = CircuitBreaker::builder()
.slow_call_duration_threshold(Duration::from_millis(200))
.slow_call_rate_threshold(0.6)
.build();
let config = breaker.config();
assert_eq!(
config.slow_call_duration_threshold,
Duration::from_millis(200)
);
assert_eq!(config.slow_call_rate_threshold, 0.6);
}
#[test]
fn test_circuit_breaker_builder_default() {
let builder = CircuitBreakerBuilder::default();
assert_eq!(builder.config.failure_threshold, 5);
assert_eq!(builder.config.success_threshold, 3);
}
#[test]
fn test_config_error_classifier_builder() {
let classifier: Arc<dyn ErrorClassifier> = Arc::new(DefaultErrorClassifier);
let config = CircuitBreakerConfig::default().error_classifier(classifier);
assert_eq!(config.failure_threshold, 5);
}
#[test]
fn test_config_all_builder_methods() {
let classifier: Arc<dyn ErrorClassifier> = Arc::new(DefaultErrorClassifier);
let config = CircuitBreakerConfig::new(10, 5, Duration::from_secs(30))
.half_open_max_calls(4)
.slow_call_duration_threshold(Duration::from_millis(100))
.slow_call_rate_threshold(0.7)
.error_classifier(classifier);
assert_eq!(config.failure_threshold, 10);
assert_eq!(config.success_threshold, 5);
assert_eq!(config.half_open_max_calls, 4);
assert_eq!(
config.slow_call_duration_threshold,
Duration::from_millis(100)
);
assert!((config.slow_call_rate_threshold - 0.7).abs() < f64::EPSILON);
}
#[tokio::test]
async fn test_slow_call_rate_triggers_open() {
let config = CircuitBreakerConfig {
slow_call_duration_threshold: Duration::ZERO,
slow_call_rate_threshold: 0.5,
failure_threshold: 100,
..Default::default()
};
let breaker = CircuitBreaker::new(config);
let _ = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
assert!(
breaker.is_open().await,
"Slow call rate should trigger Open state"
);
}
#[tokio::test]
async fn test_slow_call_rate_below_threshold_stays_closed() {
let config = CircuitBreakerConfig {
slow_call_duration_threshold: Duration::from_secs(60),
slow_call_rate_threshold: 0.5,
..Default::default()
};
let breaker = CircuitBreaker::new(config);
for _ in 0..5 {
let _ = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
}
assert!(breaker.is_closed().await);
}
#[tokio::test]
async fn test_on_success_in_open_state_logs_warning() {
let config = CircuitBreakerConfig::new(1, 1, Duration::from_secs(60));
let breaker = CircuitBreaker::new(config);
let _ = breaker
.execute(|| async { Err::<(), LimiteronError>(LimiteronError::BanError("e".into())) })
.await;
assert!(breaker.is_open().await);
let result = breaker
.execute(|| async { Ok::<(), LimiteronError>(()) })
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_circuit_breaker_with_clock() {
use crate::clock::MockClock;
let mock_clock = Arc::new(MockClock::new());
let clock: Arc<dyn Clock> = mock_clock.clone();
let config = CircuitBreakerConfig::new(2, 2, Duration::from_secs(60));
let breaker = CircuitBreaker::with_clock(config, clock);
assert!(breaker.is_closed().await);
let stats = breaker.get_stats().await;
assert_eq!(stats.state, CircuitState::Closed);
}
#[tokio::test]
async fn test_circuit_breaker_builder_error_classifier() {
let classifier: Arc<dyn ErrorClassifier> = Arc::new(DefaultErrorClassifier);
let breaker = CircuitBreaker::builder()
.failure_threshold(3)
.error_classifier(classifier)
.build();
assert_eq!(breaker.config().failure_threshold, 3);
}
#[tokio::test]
async fn test_get_stats_after_failure() {
let config = CircuitBreakerConfig::new(5, 2, Duration::from_secs(60));
let breaker = CircuitBreaker::new(config);
let _ = breaker
.execute(|| async { Err::<(), LimiteronError>(LimiteronError::BanError("e".into())) })
.await;
let stats = breaker.get_stats().await;
assert_eq!(stats.failure_count, 1);
assert_eq!(stats.total_calls, 1);
assert!(stats.last_failure_time.is_some());
}
#[test]
fn test_default_error_classifier_storage_not_transient() {
let classifier = DefaultErrorClassifier;
let error = LimiteronError::StorageError(crate::error::StorageError::NotFound("nf".into()));
assert!(!classifier.is_counted_as_failure(&error));
}
#[tokio::test]
async fn test_on_failure_when_open() {
let config = CircuitBreakerConfig::new(2, 2, Duration::from_secs(60));
let breaker = CircuitBreaker::new(config);
for _ in 0..2 {
let _ = breaker
.execute(|| async {
Err::<(), LimiteronError>(LimiteronError::BanError("e".to_string()))
})
.await;
}
assert!(breaker.is_open().await);
let error = LimiteronError::BanError("open-state failure".to_string());
breaker.on_failure(&error).await;
assert!(breaker.is_open().await);
}
}