use crate::circuit_breaker::{CircuitBreaker, CircuitBreakerConfig};
use crate::context::Context;
use crate::error::Result;
use crate::messages::Message;
use crate::rpc::RpcExt;
use serde::{de::DeserializeOwned, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
#[derive(Clone)]
pub struct CircuitBreakerRegistry {
breakers: Arc<RwLock<HashMap<String, CircuitBreaker>>>,
}
impl CircuitBreakerRegistry {
pub fn new() -> Self {
Self {
breakers: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn get_or_create(&self, endpoint: &str, config: CircuitBreakerConfig) -> CircuitBreaker {
let mut breakers = self.breakers.write().await;
breakers
.entry(endpoint.to_string())
.or_insert_with(|| CircuitBreaker::new(config))
.clone()
}
pub async fn stats(&self) -> HashMap<String, CircuitBreakerStats> {
let breakers = self.breakers.read().await;
let mut stats_map = HashMap::new();
for (endpoint, breaker) in breakers.iter() {
let (total_requests, total_successes, total_failures, total_rejected) = breaker.stats().await;
let success_rate = breaker.success_rate().await;
let state = if breaker.is_open().await {
"open".to_string()
} else if breaker.is_half_open().await {
"half-open".to_string()
} else {
"closed".to_string()
};
stats_map.insert(
endpoint.clone(),
CircuitBreakerStats {
state,
total_requests,
total_successes,
total_failures,
total_rejected,
success_rate,
},
);
}
stats_map
}
pub async fn reset_all(&self) {
let mut breakers = self.breakers.write().await;
breakers.clear();
}
}
impl Default for CircuitBreakerRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CircuitBreakerStats {
pub state: String,
pub total_requests: u64,
pub total_successes: u64,
pub total_failures: u64,
pub total_rejected: u64,
pub success_rate: f64,
}
pub trait RpcBreakerExt {
fn request_with_breaker<Req, Resp>(
&self,
topic: &str,
request: &Req,
config: CircuitBreakerConfig,
) -> BreakerRequestBuilder<'_, Req, Resp>
where
Req: Message + Serialize + Clone,
Resp: Message + DeserializeOwned + Send + 'static;
}
impl RpcBreakerExt for Context {
fn request_with_breaker<Req, Resp>(
&self,
topic: &str,
request: &Req,
config: CircuitBreakerConfig,
) -> BreakerRequestBuilder<'_, Req, Resp>
where
Req: Message + Serialize + Clone,
Resp: Message + DeserializeOwned + Send + 'static,
{
BreakerRequestBuilder {
ctx: self,
topic: topic.to_string(),
request: request.clone(),
config,
timeout: Duration::from_secs(10),
_phantom: std::marker::PhantomData,
}
}
}
pub struct BreakerRequestBuilder<'a, Req, Resp> {
ctx: &'a Context,
topic: String,
request: Req,
config: CircuitBreakerConfig,
timeout: Duration,
_phantom: std::marker::PhantomData<Resp>,
}
impl<'a, Req, Resp> BreakerRequestBuilder<'a, Req, Resp>
where
Req: Message + Serialize + Clone,
Resp: Message + DeserializeOwned + Send + 'static,
{
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub async fn execute(self) -> Result<Resp> {
let registry = get_global_registry();
let breaker = registry.get_or_create(&self.topic, self.config).await;
let ctx = self.ctx;
let topic = self.topic.clone();
let request = self.request.clone();
let timeout = self.timeout;
breaker
.call(|| async move { ctx.request(&topic, &request).timeout(timeout).execute().await })
.await
}
}
use once_cell::sync::Lazy;
static GLOBAL_REGISTRY: Lazy<CircuitBreakerRegistry> = Lazy::new(CircuitBreakerRegistry::new);
pub fn get_global_registry() -> &'static CircuitBreakerRegistry {
&GLOBAL_REGISTRY
}
pub async fn get_all_stats() -> HashMap<String, CircuitBreakerStats> {
GLOBAL_REGISTRY.stats().await
}
pub async fn reset_all_breakers() {
GLOBAL_REGISTRY.reset_all().await;
}
impl CircuitBreakerConfig {
pub fn critical(name: &str) -> Self {
Self {
failure_threshold: 3,
success_threshold: 3,
timeout: Duration::from_secs(60),
name: Some(name.to_string()),
}
}
pub fn standard(name: &str) -> Self {
Self {
failure_threshold: 5,
success_threshold: 2,
timeout: Duration::from_secs(30),
name: Some(name.to_string()),
}
}
pub fn resilient(name: &str) -> Self {
Self {
failure_threshold: 10,
success_threshold: 2,
timeout: Duration::from_secs(15),
name: Some(name.to_string()),
}
}
}