use std::future::Future;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use crate::utils::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CircuitState {
Closed, Open, #[allow(dead_code)] HalfOpen,
}
#[derive(Debug)]
struct CircuitBreaker {
state: CircuitState,
failures: usize,
threshold: usize,
last_failure: Option<Instant>,
open_duration: Duration,
}
impl CircuitBreaker {
fn new(threshold: usize, open_duration: Duration) -> Self {
Self {
state: CircuitState::Closed,
failures: 0,
threshold,
last_failure: None,
open_duration,
}
}
fn is_open(&self) -> bool {
if self.state == CircuitState::Open {
if let Some(last_failure) = self.last_failure {
if last_failure.elapsed() > self.open_duration {
return false;
}
}
return true;
}
false
}
fn record_success(&mut self) {
self.state = CircuitState::Closed;
self.failures = 0;
self.last_failure = None;
}
fn record_failure(&mut self) {
self.failures += 1;
self.last_failure = Some(Instant::now());
if self.failures >= self.threshold {
self.state = CircuitState::Open;
}
}
}
pub struct NetworkRetry {
max_retries: u32,
initial_backoff: Duration,
max_backoff: Duration,
circuit_breaker: Arc<Mutex<CircuitBreaker>>,
}
impl NetworkRetry {
pub fn new(max_retries: u32, initial_backoff: Duration) -> Self {
Self {
max_retries,
initial_backoff,
max_backoff: Duration::from_secs(60),
circuit_breaker: Arc::new(Mutex::new(CircuitBreaker::new(5, Duration::from_secs(30)))),
}
}
pub async fn execute<F, Fut, T>(&self, mut operation: F) -> Result<T>
where
F: FnMut() -> Fut,
Fut: Future<Output = Result<T>>,
{
{
let breaker = self
.circuit_breaker
.lock()
.map_err(|_| Error::network_error("Circuit breaker lock poisoned"))?;
if breaker.is_open() {
return Err(Error::network_error(
"Circuit breaker open - too many failures",
));
}
}
let mut attempt = 0;
let mut backoff = self.initial_backoff;
loop {
match operation().await {
Ok(result) => {
self.circuit_breaker
.lock()
.map_err(|_| Error::network_error("Circuit breaker lock poisoned"))?
.record_success();
return Ok(result);
}
Err(e) if self.is_retryable(&e) && attempt < self.max_retries => {
attempt += 1;
log::warn!(
"Network operation failed (attempt {}/{}), retrying after {:?}",
attempt,
self.max_retries,
backoff
);
std::thread::sleep(backoff);
backoff = (backoff * 2).min(self.max_backoff);
}
Err(e) => {
self.circuit_breaker
.lock()
.map_err(|_| Error::network_error("Circuit breaker lock poisoned"))?
.record_failure();
return Err(e);
}
}
}
}
fn is_retryable(&self, error: &Error) -> bool {
let msg = error.to_string().to_lowercase();
msg.contains("network")
|| msg.contains("timeout")
|| msg.contains("connection")
|| msg.contains("dns")
|| msg.contains("temporary")
}
}
impl Default for NetworkRetry {
fn default() -> Self {
Self::new(3, Duration::from_secs(1))
}
}