use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
use tokio::time::Instant;
use tracing::{info, warn};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CircuitState {
Closed,
Open,
HalfOpen,
}
#[derive(Debug, Clone)]
pub struct CircuitBreakerConfig {
pub failure_threshold: u32,
pub success_threshold: u32,
pub window_duration: Duration,
pub timeout_duration: Duration,
pub half_open_max_requests: u32,
}
impl Default for CircuitBreakerConfig {
fn default() -> Self {
Self {
failure_threshold: 5,
success_threshold: 3,
window_duration: Duration::from_secs(60),
timeout_duration: Duration::from_secs(30),
half_open_max_requests: 3,
}
}
}
#[derive(Debug, Clone)]
struct CircuitStats {
consecutive_failures: u32,
consecutive_successes: u32,
half_open_requests: u32,
last_failure_time: Option<Instant>,
circuit_opened_at: Option<Instant>,
}
impl CircuitStats {
fn new() -> Self {
Self {
consecutive_failures: 0,
consecutive_successes: 0,
half_open_requests: 0,
last_failure_time: None,
circuit_opened_at: None,
}
}
fn record_success(&mut self) {
self.consecutive_successes += 1;
self.consecutive_failures = 0;
}
fn record_failure(&mut self) {
self.consecutive_failures += 1;
self.consecutive_successes = 0;
self.last_failure_time = Some(Instant::now());
}
fn reset(&mut self) {
self.consecutive_failures = 0;
self.consecutive_successes = 0;
self.half_open_requests = 0;
self.last_failure_time = None;
self.circuit_opened_at = None;
}
}
pub struct CircuitBreaker {
name: String,
config: CircuitBreakerConfig,
state: Arc<RwLock<CircuitState>>,
stats: Arc<RwLock<CircuitStats>>,
}
impl CircuitBreaker {
pub fn new(name: String, config: CircuitBreakerConfig) -> Self {
Self {
name,
config,
state: Arc::new(RwLock::new(CircuitState::Closed)),
stats: Arc::new(RwLock::new(CircuitStats::new())),
}
}
pub async fn is_allowed(&self) -> bool {
let mut state = self.state.write().await;
let mut stats = self.stats.write().await;
match *state {
CircuitState::Closed => true,
CircuitState::Open => {
if let Some(opened_at) = stats.circuit_opened_at {
if Instant::now().duration_since(opened_at) >= self.config.timeout_duration {
info!(
"Circuit breaker '{}' transitioning from Open to HalfOpen",
self.name
);
*state = CircuitState::HalfOpen;
stats.half_open_requests = 0;
true
} else {
false
}
} else {
false
}
}
CircuitState::HalfOpen => {
if stats.half_open_requests < self.config.half_open_max_requests {
stats.half_open_requests += 1;
true
} else {
false
}
}
}
}
pub async fn record_success(&self) {
let mut state = self.state.write().await;
let mut stats = self.stats.write().await;
stats.record_success();
match *state {
CircuitState::Closed => {
}
CircuitState::Open => {
warn!(
"Success recorded while circuit breaker '{}' is open",
self.name
);
}
CircuitState::HalfOpen => {
if stats.consecutive_successes >= self.config.success_threshold {
info!(
"Circuit breaker '{}' closing after {} consecutive successes",
self.name, stats.consecutive_successes
);
*state = CircuitState::Closed;
stats.reset();
}
}
}
}
pub async fn record_failure(&self) {
let mut state = self.state.write().await;
let mut stats = self.stats.write().await;
stats.record_failure();
match *state {
CircuitState::Closed => {
if stats.consecutive_failures >= self.config.failure_threshold {
warn!(
"Circuit breaker '{}' opening after {} consecutive failures",
self.name, stats.consecutive_failures
);
*state = CircuitState::Open;
stats.circuit_opened_at = Some(Instant::now());
}
}
CircuitState::Open => {
}
CircuitState::HalfOpen => {
warn!(
"Circuit breaker '{}' reopening after failure in half-open state",
self.name
);
*state = CircuitState::Open;
stats.circuit_opened_at = Some(Instant::now());
stats.half_open_requests = 0;
}
}
}
pub async fn get_state(&self) -> CircuitState {
*self.state.read().await
}
pub async fn set_state(&self, new_state: CircuitState) {
let mut state = self.state.write().await;
let mut stats = self.stats.write().await;
info!(
"Circuit breaker '{}' state manually changed from {:?} to {:?}",
self.name, *state, new_state
);
*state = new_state;
match new_state {
CircuitState::Closed => {
stats.reset();
}
CircuitState::Open => {
stats.circuit_opened_at = Some(Instant::now());
}
CircuitState::HalfOpen => {
stats.half_open_requests = 0;
}
}
}
pub async fn call<F, T>(&self, operation: F) -> Result<T, CircuitBreakerError>
where
F: std::future::Future<Output = Result<T, Box<dyn std::error::Error + Send + Sync>>>,
{
if !self.is_allowed().await {
return Err(CircuitBreakerError::CircuitOpen);
}
match operation.await {
Ok(result) => {
self.record_success().await;
Ok(result)
}
Err(error) => {
self.record_failure().await;
Err(CircuitBreakerError::OperationFailed(error))
}
}
}
pub async fn get_stats(&self) -> CircuitBreakerStats {
let state = *self.state.read().await;
let stats = self.stats.read().await;
CircuitBreakerStats {
name: self.name.clone(),
state,
consecutive_failures: stats.consecutive_failures,
consecutive_successes: stats.consecutive_successes,
last_failure_time: stats.last_failure_time,
circuit_opened_at: stats.circuit_opened_at,
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum CircuitBreakerError {
#[error("Circuit breaker is open")]
CircuitOpen,
#[error("Operation failed: {0}")]
OperationFailed(Box<dyn std::error::Error + Send + Sync>),
}
#[derive(Debug, Clone)]
pub struct CircuitBreakerStats {
pub name: String,
pub state: CircuitState,
pub consecutive_failures: u32,
pub consecutive_successes: u32,
pub last_failure_time: Option<Instant>,
pub circuit_opened_at: Option<Instant>,
}
pub struct CircuitBreakerManager {
breakers: Arc<RwLock<std::collections::HashMap<String, Arc<CircuitBreaker>>>>,
}
impl CircuitBreakerManager {
pub fn new() -> Self {
Self {
breakers: Arc::new(RwLock::new(std::collections::HashMap::new())),
}
}
pub async fn get_or_create(
&self,
name: &str,
config: CircuitBreakerConfig,
) -> Arc<CircuitBreaker> {
let mut breakers = self.breakers.write().await;
if let Some(breaker) = breakers.get(name) {
breaker.clone()
} else {
let breaker = Arc::new(CircuitBreaker::new(name.to_string(), config));
breakers.insert(name.to_string(), breaker.clone());
breaker
}
}
pub async fn get_all_stats(&self) -> Vec<CircuitBreakerStats> {
let breakers = self.breakers.read().await;
let mut stats = Vec::new();
for breaker in breakers.values() {
stats.push(breaker.get_stats().await);
}
stats
}
pub async fn reset_all(&self) {
let breakers = self.breakers.read().await;
for breaker in breakers.values() {
breaker.set_state(CircuitState::Closed).await;
}
}
}
impl Default for CircuitBreakerManager {
fn default() -> Self {
Self::new()
}
}