use std::time::{Duration, Instant};
use async_trait::async_trait;
use dashmap::DashMap;
use tokio::sync::Mutex;
use crate::error::WxErrorException;
use crate::pipeline::CircuitBreakerLike;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Phase {
Closed,
Open {
open_at: Instant,
},
HalfOpen,
}
#[derive(Debug, Clone)]
struct BreakerState {
phase: Phase,
consecutive_failures: u32,
}
impl Default for BreakerState {
fn default() -> Self {
Self {
phase: Phase::Closed,
consecutive_failures: 0,
}
}
}
#[derive(Debug)]
pub struct CircuitBreaker {
failure_threshold: u32,
open_duration: Duration,
hosts: Mutex<DashMap<String, BreakerState>>,
}
impl CircuitBreaker {
pub fn new(failure_threshold: u32, open_duration: Duration) -> Self {
Self {
failure_threshold,
open_duration,
hosts: Mutex::new(DashMap::new()),
}
}
pub async fn before(&self, host: &str) -> Result<(), WxErrorException> {
let hosts = self.hosts.lock().await;
let mut state = hosts.entry(host.to_string()).or_default();
match state.phase {
Phase::Closed => Ok(()),
Phase::Open { open_at } => {
if open_at.elapsed() >= self.open_duration {
state.phase = Phase::HalfOpen;
Ok(())
} else {
Err(WxErrorException::from_code(
-99,
format!("熔断器开启:{host}"),
))
}
}
Phase::HalfOpen => Ok(()),
}
}
pub async fn after(&self, host: &str, ok: bool) {
let hosts = self.hosts.lock().await;
let mut state = hosts.entry(host.to_string()).or_default();
if ok {
state.phase = Phase::Closed;
state.consecutive_failures = 0;
return;
}
match state.phase {
Phase::Closed => {
state.consecutive_failures += 1;
if state.consecutive_failures >= self.failure_threshold {
state.phase = Phase::Open {
open_at: Instant::now(),
};
state.consecutive_failures = 0;
}
}
Phase::HalfOpen => {
state.phase = Phase::Open {
open_at: Instant::now(),
};
state.consecutive_failures = 0;
}
Phase::Open { .. } => {
}
}
}
}
#[async_trait]
impl CircuitBreakerLike for CircuitBreaker {
async fn before(&self, host: &str) -> Result<(), WxErrorException> {
CircuitBreaker::before(self, host).await
}
async fn after(&self, host: &str, ok: bool) {
CircuitBreaker::after(self, host, ok).await
}
}