use crate::core::error::{ErrorKind, ErrorSeverity, MemScopeError};
use std::collections::HashMap;
use std::time::{Duration, Instant};
pub struct RecoveryStrategy {
action_map: HashMap<ErrorKind, RecoveryAction>,
retry_config: RetryConfig,
fallback_registry: FallbackRegistry,
circuit_breaker: CircuitBreaker,
}
#[derive(Debug, Clone)]
pub enum RecoveryAction {
RetryWithBackoff {
max_attempts: u32,
initial_delay: Duration,
max_delay: Duration,
backoff_multiplier: f64,
},
Fallback {
strategy: FallbackStrategy,
timeout: Duration,
},
Degrade {
level: DegradationLevel,
duration: Duration,
},
Reset {
component: String,
preserve_data: bool,
},
Skip,
Terminate,
}
#[derive(Debug, Clone)]
pub enum FallbackStrategy {
UseCache,
SimplifiedAlgorithm,
MockData,
BackupSystem,
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum DegradationLevel {
Minimal,
Moderate,
Significant,
Severe,
}
#[derive(Debug, Clone)]
pub struct RetryConfig {
pub default_max_attempts: u32,
pub default_initial_delay: Duration,
pub default_max_delay: Duration,
pub default_backoff_multiplier: f64,
pub enable_jitter: bool,
}
pub struct FallbackRegistry {
strategies: HashMap<String, Box<dyn Fn() -> Result<(), MemScopeError> + Send + Sync>>,
}
pub struct CircuitBreaker {
state: CircuitState,
failure_count: u32,
failure_threshold: u32,
opened_at: Option<Instant>,
timeout: Duration,
window_duration: Duration,
window_start: Instant,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CircuitState {
Closed,
Open,
HalfOpen,
}
impl RecoveryStrategy {
pub fn new() -> Self {
let mut strategy = Self {
action_map: HashMap::new(),
retry_config: RetryConfig::default(),
fallback_registry: FallbackRegistry::new(),
circuit_breaker: CircuitBreaker::new(),
};
strategy.setup_default_actions();
strategy
}
pub fn recover(&mut self, error: &MemScopeError) -> RecoveryResult {
if !self.circuit_breaker.can_execute() {
return RecoveryResult::CircuitOpen;
}
let action = self.get_recovery_action(error);
let result = self.execute_action(action, error);
match &result {
RecoveryResult::Success => self.circuit_breaker.record_success(),
RecoveryResult::Failed(_) => self.circuit_breaker.record_failure(),
_ => {} }
result
}
pub fn register_action(&mut self, kind: ErrorKind, action: RecoveryAction) {
self.action_map.insert(kind, action);
}
pub fn register_fallback<F>(&mut self, name: String, strategy: F)
where
F: Fn() -> Result<(), MemScopeError> + Send + Sync + 'static,
{
self.fallback_registry.register(name, Box::new(strategy));
}
pub fn get_circuit_status(&self) -> CircuitState {
self.circuit_breaker.state.clone()
}
pub fn reset_circuit(&mut self) {
self.circuit_breaker.reset();
}
fn setup_default_actions(&mut self) {
self.action_map.insert(
ErrorKind::MemoryError,
RecoveryAction::RetryWithBackoff {
max_attempts: 3,
initial_delay: Duration::from_millis(100),
max_delay: Duration::from_secs(5),
backoff_multiplier: 2.0,
},
);
self.action_map.insert(
ErrorKind::ConfigurationError,
RecoveryAction::Reset {
component: "configuration".to_string(),
preserve_data: false,
},
);
self.action_map.insert(
ErrorKind::IoError,
RecoveryAction::Fallback {
strategy: FallbackStrategy::UseCache,
timeout: Duration::from_secs(30),
},
);
self.action_map.insert(
ErrorKind::SymbolResolutionError,
RecoveryAction::Degrade {
level: DegradationLevel::Minimal,
duration: Duration::from_secs(60),
},
);
self.action_map
.insert(ErrorKind::StackTraceError, RecoveryAction::Skip);
self.action_map.insert(
ErrorKind::CacheError,
RecoveryAction::Reset {
component: "cache".to_string(),
preserve_data: false,
},
);
self.action_map
.insert(ErrorKind::InternalError, RecoveryAction::Terminate);
}
fn get_recovery_action(&self, error: &MemScopeError) -> RecoveryAction {
if let Some(action) = self.action_map.get(&error.kind()) {
return action.clone();
}
match error.severity() {
ErrorSeverity::Warning => RecoveryAction::Skip,
ErrorSeverity::Error => RecoveryAction::RetryWithBackoff {
max_attempts: self.retry_config.default_max_attempts,
initial_delay: self.retry_config.default_initial_delay,
max_delay: self.retry_config.default_max_delay,
backoff_multiplier: self.retry_config.default_backoff_multiplier,
},
ErrorSeverity::Critical => RecoveryAction::Fallback {
strategy: FallbackStrategy::MockData,
timeout: Duration::from_secs(10),
},
ErrorSeverity::Fatal => RecoveryAction::Terminate,
}
}
fn execute_action(&mut self, action: RecoveryAction, error: &MemScopeError) -> RecoveryResult {
match action {
RecoveryAction::RetryWithBackoff { .. } => RecoveryResult::Retry {
action,
delay: self.calculate_retry_delay(error),
},
RecoveryAction::Fallback { strategy, .. } => {
if let Ok(()) = self.execute_fallback(&strategy) {
RecoveryResult::Success
} else {
RecoveryResult::Failed("Fallback strategy failed".to_string())
}
}
RecoveryAction::Degrade { level, duration } => {
RecoveryResult::Degraded { level, duration }
}
RecoveryAction::Reset {
component,
preserve_data,
} => RecoveryResult::Reset {
component,
preserve_data,
},
RecoveryAction::Skip => RecoveryResult::Skipped,
RecoveryAction::Terminate => RecoveryResult::Terminated,
}
}
fn calculate_retry_delay(&self, _error: &MemScopeError) -> Duration {
self.retry_config.default_initial_delay
}
fn execute_fallback(&self, strategy: &FallbackStrategy) -> Result<(), Box<MemScopeError>> {
match strategy {
FallbackStrategy::UseCache => {
Ok(())
}
FallbackStrategy::SimplifiedAlgorithm => {
Ok(())
}
FallbackStrategy::MockData => {
Ok(())
}
FallbackStrategy::BackupSystem => {
Ok(())
}
}
}
}
#[derive(Debug, Clone)]
pub enum RecoveryResult {
Success,
Retry {
action: RecoveryAction,
delay: Duration,
},
Degraded {
level: DegradationLevel,
duration: Duration,
},
Reset {
component: String,
preserve_data: bool,
},
Skipped,
Terminated,
CircuitOpen,
Failed(String),
}
impl Default for CircuitBreaker {
fn default() -> Self {
Self::new()
}
}
impl CircuitBreaker {
pub fn new() -> Self {
Self {
state: CircuitState::Closed,
failure_count: 0,
failure_threshold: 5,
opened_at: None,
timeout: Duration::from_secs(60),
window_duration: Duration::from_secs(60),
window_start: Instant::now(),
}
}
pub fn can_execute(&mut self) -> bool {
self.update_state();
match self.state {
CircuitState::Closed => true,
CircuitState::Open => false,
CircuitState::HalfOpen => true,
}
}
pub fn record_success(&mut self) {
match self.state {
CircuitState::HalfOpen => {
self.state = CircuitState::Closed;
self.failure_count = 0;
}
CircuitState::Closed => {
self.failure_count = 0;
}
CircuitState::Open => {} }
}
pub fn record_failure(&mut self) {
self.failure_count += 1;
if self.failure_count >= self.failure_threshold {
self.state = CircuitState::Open;
self.opened_at = Some(Instant::now());
}
}
pub fn reset(&mut self) {
self.state = CircuitState::Closed;
self.failure_count = 0;
self.opened_at = None;
self.window_start = Instant::now();
}
fn update_state(&mut self) {
if self.window_start.elapsed() > self.window_duration {
self.window_start = Instant::now();
self.failure_count = 0;
}
if self.state == CircuitState::Open {
if let Some(opened_at) = self.opened_at {
if opened_at.elapsed() > self.timeout {
self.state = CircuitState::HalfOpen;
}
}
}
}
}
impl Default for FallbackRegistry {
fn default() -> Self {
Self::new()
}
}
impl FallbackRegistry {
pub fn new() -> Self {
Self {
strategies: HashMap::new(),
}
}
pub fn register<F>(&mut self, name: String, strategy: F)
where
F: Fn() -> Result<(), MemScopeError> + Send + Sync + 'static,
{
self.strategies.insert(name, Box::new(strategy));
}
pub fn execute(&self, name: &str) -> Result<(), Box<MemScopeError>> {
if let Some(strategy) = self.strategies.get(name) {
strategy().map_err(Box::new)
} else {
Err(Box::new(MemScopeError::new(
ErrorKind::ConfigurationError,
format!("Fallback strategy '{}' not found", name),
)))
}
}
}
impl Default for RetryConfig {
fn default() -> Self {
Self {
default_max_attempts: 3,
default_initial_delay: Duration::from_millis(100),
default_max_delay: Duration::from_secs(10),
default_backoff_multiplier: 2.0,
enable_jitter: true,
}
}
}
impl Default for RecoveryStrategy {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_recovery_strategy_creation() {
let strategy = RecoveryStrategy::new();
assert!(strategy.action_map.contains_key(&ErrorKind::MemoryError));
assert!(strategy
.action_map
.contains_key(&ErrorKind::ConfigurationError));
}
#[test]
fn test_circuit_breaker_basic() {
let mut breaker = CircuitBreaker::new();
assert!(breaker.can_execute());
assert_eq!(breaker.state, CircuitState::Closed);
for _ in 0..5 {
breaker.record_failure();
}
assert!(!breaker.can_execute());
assert_eq!(breaker.state, CircuitState::Open);
}
#[test]
fn test_recovery_action_selection() {
let mut strategy = RecoveryStrategy::new();
let memory_error = MemScopeError::new(ErrorKind::MemoryError, "allocation failed");
let result = strategy.recover(&memory_error);
match result {
RecoveryResult::Retry { .. } => {} _ => panic!("Expected retry for memory error"),
}
}
#[test]
fn test_fallback_registry() {
let mut registry = FallbackRegistry::new();
registry.register("test_fallback".to_string(), || Ok(()));
assert!(registry.execute("test_fallback").is_ok());
assert!(registry.execute("nonexistent").is_err());
}
#[test]
fn test_degradation_levels() {
let levels = [
DegradationLevel::Minimal,
DegradationLevel::Moderate,
DegradationLevel::Significant,
DegradationLevel::Severe,
];
assert!(levels[0] < levels[1]);
assert!(levels[1] < levels[2]);
assert!(levels[2] < levels[3]);
}
}