use std::time::Duration;
use std::time::Instant;
#[derive(Debug, Clone)]
pub struct ExponentialBackoff {
initial_delay: Duration,
max_delay: Duration,
max_attempts: u32,
current_attempt: u32,
}
impl ExponentialBackoff {
pub fn new(initial_delay: Duration, max_delay: Duration, max_attempts: u32) -> Self {
Self {
initial_delay,
max_delay,
max_attempts,
current_attempt: 0,
}
}
pub fn default_strategy() -> Self {
Self::new(Duration::from_secs(1), Duration::from_secs(60), 0)
}
pub fn next_delay(&mut self) -> Option<Duration> {
if self.max_attempts > 0 && self.current_attempt >= self.max_attempts {
return None;
}
let delay_secs = self.initial_delay.as_secs() * 2u64.pow(self.current_attempt);
let delay = Duration::from_secs(delay_secs).min(self.max_delay);
self.current_attempt += 1;
Some(delay)
}
pub fn reset(&mut self) {
self.current_attempt = 0;
}
pub fn current_attempt(&self) -> u32 {
self.current_attempt
}
pub fn is_exhausted(&self) -> bool {
self.max_attempts > 0 && self.current_attempt >= self.max_attempts
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CircuitState {
Closed,
Open,
HalfOpen,
}
#[derive(Debug, Clone)]
pub struct CircuitBreaker {
state: CircuitState,
failure_count: u32,
failure_threshold: u32,
success_threshold: u32,
success_count: u32,
timeout: Duration,
opened_at: Option<Instant>,
}
impl CircuitBreaker {
pub fn new(failure_threshold: u32, success_threshold: u32, timeout: Duration) -> Self {
Self {
state: CircuitState::Closed,
failure_count: 0,
failure_threshold,
success_threshold,
success_count: 0,
timeout,
opened_at: None,
}
}
pub fn default_breaker() -> Self {
Self::new(5, 2, Duration::from_secs(30))
}
pub fn allow_request(&mut self) -> bool {
match self.state {
CircuitState::Closed => true,
CircuitState::HalfOpen => true,
CircuitState::Open => {
if let Some(opened_at) = self.opened_at {
if opened_at.elapsed() >= self.timeout {
self.state = CircuitState::HalfOpen;
self.success_count = 0;
self.failure_count = 0;
true
} else {
false }
} else {
false
}
}
}
}
pub fn record_success(&mut self) {
match self.state {
CircuitState::Closed => {
self.failure_count = 0;
}
CircuitState::HalfOpen => {
self.success_count += 1;
if self.success_count >= self.success_threshold {
self.state = CircuitState::Closed;
self.success_count = 0;
self.failure_count = 0;
self.opened_at = None;
}
}
CircuitState::Open => {
}
}
}
pub fn record_failure(&mut self) {
match self.state {
CircuitState::Closed => {
self.failure_count += 1;
if self.failure_count >= self.failure_threshold {
self.state = CircuitState::Open;
self.opened_at = Some(Instant::now());
}
}
CircuitState::HalfOpen => {
self.state = CircuitState::Open;
self.opened_at = Some(Instant::now());
self.success_count = 0;
}
CircuitState::Open => {
self.opened_at = Some(Instant::now());
}
}
}
pub fn state(&self) -> CircuitState {
self.state
}
pub fn reset(&mut self) {
self.state = CircuitState::Closed;
self.failure_count = 0;
self.success_count = 0;
self.opened_at = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_exponential_backoff() {
let mut backoff = ExponentialBackoff::new(Duration::from_secs(1), Duration::from_secs(60), 5);
assert_eq!(backoff.next_delay(), Some(Duration::from_secs(1)));
assert_eq!(backoff.next_delay(), Some(Duration::from_secs(2)));
assert_eq!(backoff.next_delay(), Some(Duration::from_secs(4)));
assert_eq!(backoff.next_delay(), Some(Duration::from_secs(8)));
assert_eq!(backoff.next_delay(), Some(Duration::from_secs(16)));
assert_eq!(backoff.next_delay(), None);
}
#[test]
fn test_backoff_reset() {
let mut backoff = ExponentialBackoff::default_strategy();
backoff.next_delay();
backoff.next_delay();
assert_eq!(backoff.current_attempt(), 2);
backoff.reset();
assert_eq!(backoff.current_attempt(), 0);
}
#[test]
fn test_circuit_breaker_closed_to_open() {
let mut breaker = CircuitBreaker::new(3, 2, Duration::from_secs(1));
assert_eq!(breaker.state(), CircuitState::Closed);
assert!(breaker.allow_request());
breaker.record_failure();
assert_eq!(breaker.state(), CircuitState::Closed);
breaker.record_failure();
assert_eq!(breaker.state(), CircuitState::Closed);
breaker.record_failure();
assert_eq!(breaker.state(), CircuitState::Open);
assert!(!breaker.allow_request()); }
#[test]
fn test_circuit_breaker_half_open() {
let mut breaker = CircuitBreaker::new(2, 2, Duration::from_millis(10));
breaker.record_failure();
breaker.record_failure();
assert_eq!(breaker.state(), CircuitState::Open);
std::thread::sleep(Duration::from_millis(15));
assert!(breaker.allow_request());
assert_eq!(breaker.state(), CircuitState::HalfOpen);
breaker.record_success();
assert_eq!(breaker.state(), CircuitState::HalfOpen);
breaker.record_success();
assert_eq!(breaker.state(), CircuitState::Closed);
}
#[test]
fn test_circuit_breaker_half_open_failure() {
let mut breaker = CircuitBreaker::new(2, 2, Duration::from_millis(10));
breaker.record_failure();
breaker.record_failure();
std::thread::sleep(Duration::from_millis(15));
breaker.allow_request();
assert_eq!(breaker.state(), CircuitState::HalfOpen);
breaker.record_failure();
assert_eq!(breaker.state(), CircuitState::Open);
}
}