use std::time::{Duration, Instant};
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{error, warn, info, debug};
#[derive(Debug, Clone)]
pub struct CircuitBreaker {
name: String,
failure_count: Arc<RwLock<u32>>,
last_failure: Arc<RwLock<Option<Instant>>>,
failure_threshold: u32,
timeout: Duration,
state: Arc<RwLock<CircuitBreakerState>>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CircuitBreakerState {
Closed, Open, HalfOpen, }
impl CircuitBreaker {
pub fn new(name: String, failure_threshold: u32, timeout: Duration) -> Self {
Self {
name,
failure_count: Arc::new(RwLock::new(0)),
last_failure: Arc::new(RwLock::new(None)),
failure_threshold,
timeout,
state: Arc::new(RwLock::new(CircuitBreakerState::Closed)),
}
}
pub async fn execute<F, T, E>(&self, operation: F) -> Result<T, CircuitBreakerError<E>>
where
F: FnOnce() -> Result<T, E>,
E: std::fmt::Debug,
{
let state = *self.state.read().await;
match state {
CircuitBreakerState::Open => {
let last_failure = *self.last_failure.read().await;
if let Some(last_fail_time) = last_failure {
if last_fail_time.elapsed() > self.timeout {
*self.state.write().await = CircuitBreakerState::HalfOpen;
debug!("Circuit breaker {} moving to half-open state", self.name);
} else {
return Err(CircuitBreakerError::CircuitOpen);
}
} else {
return Err(CircuitBreakerError::CircuitOpen);
}
}
CircuitBreakerState::Closed | CircuitBreakerState::HalfOpen => {
}
}
match operation() {
Ok(result) => {
*self.failure_count.write().await = 0;
*self.state.write().await = CircuitBreakerState::Closed;
Ok(result)
}
Err(error) => {
let mut count = self.failure_count.write().await;
*count += 1;
*self.last_failure.write().await = Some(Instant::now());
if *count >= self.failure_threshold {
*self.state.write().await = CircuitBreakerState::Open;
warn!("Circuit breaker {} opened after {} failures", self.name, count);
Err(CircuitBreakerError::CircuitOpened)
} else {
Err(CircuitBreakerError::OperationFailed(error))
}
}
}
}
pub async fn get_state(&self) -> CircuitBreakerState {
self.state.read().await.clone()
}
pub async fn reset(&self) {
*self.failure_count.write().await = 0;
*self.last_failure.write().await = None;
*self.state.write().await = CircuitBreakerState::Closed;
info!("Circuit breaker {} manually reset", self.name);
}
}
#[derive(Debug)]
pub enum CircuitBreakerError<E> {
CircuitOpen,
CircuitOpened,
OperationFailed(E),
}
#[derive(Debug, Clone)]
pub struct RetryPolicy {
max_attempts: u32,
base_delay: Duration,
max_delay: Duration,
backoff_multiplier: f64,
}
impl RetryPolicy {
pub fn new(max_attempts: u32, base_delay: Duration) -> Self {
Self {
max_attempts,
base_delay,
max_delay: Duration::from_secs(30),
backoff_multiplier: 2.0,
}
}
pub fn with_max_delay(mut self, max_delay: Duration) -> Self {
self.max_delay = max_delay;
self
}
pub fn with_backoff_multiplier(mut self, multiplier: f64) -> Self {
self.backoff_multiplier = multiplier;
self
}
pub async fn execute<F, Fut, T, E>(&self, mut operation: F) -> Result<T, E>
where
F: FnMut() -> Fut,
Fut: std::future::Future<Output = Result<T, E>>,
E: std::fmt::Debug,
{
let mut attempt = 1;
let mut delay = self.base_delay;
loop {
match operation().await {
Ok(result) => {
if attempt > 1 {
info!("Operation succeeded on attempt {}/{}", attempt, self.max_attempts);
}
return Ok(result);
}
Err(error) => {
if attempt >= self.max_attempts {
error!("Operation failed after {} attempts: {:?}", attempt, error);
return Err(error);
}
warn!("Operation failed on attempt {}/{}, retrying in {:?}: {:?}",
attempt, self.max_attempts, delay, error);
tokio::time::sleep(delay).await;
delay = std::cmp::min(
Duration::from_millis((delay.as_millis() as f64 * self.backoff_multiplier) as u64),
self.max_delay
);
attempt += 1;
}
}
}
}
}
pub struct GracefulDegradation {
feature_flags: Arc<RwLock<std::collections::HashMap<String, bool>>>,
circuit_breakers: Arc<RwLock<std::collections::HashMap<String, CircuitBreaker>>>,
}
impl GracefulDegradation {
pub fn new() -> Self {
Self {
feature_flags: Arc::new(RwLock::new(std::collections::HashMap::new())),
circuit_breakers: Arc::new(RwLock::new(std::collections::HashMap::new())),
}
}
pub async fn register_feature(&self, name: &str, enabled: bool) {
self.feature_flags.write().await.insert(name.to_string(), enabled);
}
pub async fn is_feature_enabled(&self, name: &str) -> bool {
self.feature_flags.read().await.get(name).copied().unwrap_or(false)
}
pub async fn disable_feature(&self, name: &str, reason: &str) {
self.feature_flags.write().await.insert(name.to_string(), false);
warn!("Feature '{}' disabled: {}", name, reason);
}
pub async fn enable_feature(&self, name: &str) {
self.feature_flags.write().await.insert(name.to_string(), true);
info!("Feature '{}' enabled", name);
}
pub async fn register_circuit_breaker(&self, name: &str, failure_threshold: u32, timeout: Duration) {
let breaker = CircuitBreaker::new(name.to_string(), failure_threshold, timeout);
self.circuit_breakers.write().await.insert(name.to_string(), breaker);
}
pub async fn get_circuit_breaker(&self, name: &str) -> Option<CircuitBreaker> {
self.circuit_breakers.read().await.get(name).cloned()
}
pub async fn health_check(&self) -> HealthStatus {
let mut status = HealthStatus::new();
let breakers = self.circuit_breakers.read().await;
for (name, breaker) in breakers.iter() {
let state = breaker.get_state().await;
match state {
CircuitBreakerState::Open => {
status.add_issue(name, "Circuit breaker is open", HealthSeverity::Critical);
self.disable_feature(name, "Circuit breaker protection").await;
}
CircuitBreakerState::HalfOpen => {
status.add_issue(name, "Circuit breaker is half-open", HealthSeverity::Warning);
}
CircuitBreakerState::Closed => {
status.add_healthy_component(name);
}
}
}
status.check_system_resources().await;
status
}
}
#[derive(Debug)]
pub struct HealthStatus {
pub overall_health: HealthLevel,
pub components: std::collections::HashMap<String, ComponentHealth>,
pub issues: Vec<HealthIssue>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum HealthLevel {
Healthy,
Degraded,
Critical,
}
#[derive(Debug)]
pub struct ComponentHealth {
pub name: String,
pub status: HealthLevel,
pub last_check: Instant,
pub message: Option<String>,
}
#[derive(Debug)]
pub struct HealthIssue {
pub component: String,
pub message: String,
pub severity: HealthSeverity,
pub timestamp: Instant,
}
#[derive(Debug, Clone, PartialEq)]
pub enum HealthSeverity {
Info,
Warning,
Critical,
}
impl HealthStatus {
pub fn new() -> Self {
Self {
overall_health: HealthLevel::Healthy,
components: std::collections::HashMap::new(),
issues: Vec::new(),
}
}
pub fn add_healthy_component(&mut self, name: &str) {
self.components.insert(name.to_string(), ComponentHealth {
name: name.to_string(),
status: HealthLevel::Healthy,
last_check: Instant::now(),
message: None,
});
}
pub fn add_issue(&mut self, component: &str, message: &str, severity: HealthSeverity) {
self.issues.push(HealthIssue {
component: component.to_string(),
message: message.to_string(),
severity: severity.clone(),
timestamp: Instant::now(),
});
let component_status = match severity {
HealthSeverity::Critical => HealthLevel::Critical,
HealthSeverity::Warning => HealthLevel::Degraded,
HealthSeverity::Info => HealthLevel::Healthy,
};
self.components.insert(component.to_string(), ComponentHealth {
name: component.to_string(),
status: component_status,
last_check: Instant::now(),
message: Some(message.to_string()),
});
if severity == HealthSeverity::Critical {
self.overall_health = HealthLevel::Critical;
} else if severity == HealthSeverity::Warning && self.overall_health == HealthLevel::Healthy {
self.overall_health = HealthLevel::Degraded;
}
}
pub async fn check_system_resources(&mut self) {
if let Ok(memory) = self.get_available_memory() {
if memory < 100 * 1024 * 1024 { self.add_issue("memory", "Low available memory", HealthSeverity::Warning);
} else {
self.add_healthy_component("memory");
}
}
if let Ok(disk_space) = self.get_available_disk_space() {
if disk_space < 500 * 1024 * 1024 { self.add_issue("disk", "Low disk space", HealthSeverity::Warning);
} else {
self.add_healthy_component("disk");
}
}
}
fn get_available_memory(&self) -> Result<u64, Box<dyn std::error::Error>> {
#[cfg(target_os = "linux")]
{
let meminfo = std::fs::read_to_string("/proc/meminfo")?;
for line in meminfo.lines() {
if line.starts_with("MemAvailable:") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
let kb: u64 = parts[1].parse()?;
return Ok(kb * 1024); }
}
}
}
Ok(1024 * 1024 * 1024) }
fn get_available_disk_space(&self) -> Result<u64, Box<dyn std::error::Error>> {
use std::fs;
let temp_dir = std::env::temp_dir();
if let Ok(_metadata) = fs::metadata(&temp_dir) {
Ok(1024 * 1024 * 1024) } else {
Err("Cannot access temp directory".into())
}
}
}
impl Default for GracefulDegradation {
fn default() -> Self {
Self::new()
}
}
#[macro_export]
macro_rules! with_error_recovery {
($operation:expr, $fallback:expr, $context:expr) => {
match $operation {
Ok(result) => result,
Err(error) => {
tracing::warn!("Operation failed in {}: {:?}, using fallback", $context, error);
$fallback
}
}
};
}
#[macro_export]
macro_rules! with_circuit_breaker {
($breaker:expr, $operation:expr) => {
match $breaker.execute(|| $operation).await {
Ok(result) => Ok(result),
Err(CircuitBreakerError::CircuitOpen) => {
Err(CargoCryptError::ServiceUnavailable {
service: "protected operation".to_string(),
reason: "Circuit breaker is open".to_string(),
})
}
Err(CircuitBreakerError::CircuitOpened) => {
Err(CargoCryptError::ServiceUnavailable {
service: "protected operation".to_string(),
reason: "Circuit breaker opened due to failures".to_string(),
})
}
Err(CircuitBreakerError::OperationFailed(error)) => Err(error),
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::time::sleep;
#[tokio::test]
async fn test_circuit_breaker() {
let breaker = CircuitBreaker::new("test".to_string(), 2, Duration::from_millis(100));
assert_eq!(breaker.get_state().await, CircuitBreakerState::Closed);
let _ = breaker.execute(|| Err::<(), &str>("test error")).await;
let _ = breaker.execute(|| Err::<(), &str>("test error")).await;
assert_eq!(breaker.get_state().await, CircuitBreakerState::Open);
let result = breaker.execute(|| Ok::<(), &str>(())).await;
assert!(matches!(result, Err(CircuitBreakerError::CircuitOpen)));
sleep(Duration::from_millis(150)).await;
let result = breaker.execute(|| Ok::<(), &str>(())).await;
assert!(result.is_ok());
assert_eq!(breaker.get_state().await, CircuitBreakerState::Closed);
}
#[tokio::test]
async fn test_retry_policy() {
let policy = RetryPolicy::new(3, Duration::from_millis(10));
let mut attempts = 0;
let result = policy.execute(|| {
attempts += 1;
async move {
if attempts < 3 {
Err("temporary failure")
} else {
Ok("success")
}
}
}).await;
assert_eq!(result, Ok("success"));
assert_eq!(attempts, 3);
}
#[tokio::test]
async fn test_graceful_degradation() {
let gd = GracefulDegradation::new();
gd.register_feature("tui", true).await;
gd.register_feature("git_integration", true).await;
assert!(gd.is_feature_enabled("tui").await);
gd.disable_feature("tui", "testing").await;
assert!(!gd.is_feature_enabled("tui").await);
}
}