use std::time::Duration;
use std::time::Instant;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CircuitState {
Closed,
Open,
HalfOpen,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CircuitAdmission {
Regular,
Probe,
Rejected,
}
#[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 {
self.connection_admission() != CircuitAdmission::Rejected
}
pub(crate) fn connection_admission(&mut self) -> CircuitAdmission {
match self.state {
CircuitState::Closed => CircuitAdmission::Regular,
CircuitState::HalfOpen => CircuitAdmission::Probe,
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;
CircuitAdmission::Probe
} else {
CircuitAdmission::Rejected
}
} else {
CircuitAdmission::Rejected
}
}
}
}
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(crate) fn state(&self) -> CircuitState {
self.state
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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);
}
}