use parking_lot::Mutex;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CircuitState {
Closed,
Open,
HalfOpen,
}
#[derive(Debug)]
struct Inner {
state: CircuitState,
consecutive_failures: u32,
opened_at: Option<Instant>,
}
pub struct CircuitBreaker {
failure_threshold: u32,
success_threshold: u32,
cooldown: Duration,
inner: Mutex<Inner>,
}
impl CircuitBreaker {
pub fn new(failure_threshold: u32, success_threshold: u32, cooldown: Duration) -> Self {
CircuitBreaker {
failure_threshold: failure_threshold.max(1),
success_threshold: success_threshold.max(1),
cooldown,
inner: Mutex::new(Inner {
state: CircuitState::Closed,
consecutive_failures: 0,
opened_at: None,
}),
}
}
pub fn state(&self) -> CircuitState {
let mut inner = self.inner.lock();
self.refresh(&mut inner);
inner.state
}
pub fn allow_request(&self) -> bool {
let mut inner = self.inner.lock();
self.refresh(&mut inner);
!matches!(inner.state, CircuitState::Open)
}
pub fn record_success(&self) {
let mut inner = self.inner.lock();
match inner.state {
CircuitState::HalfOpen => {
inner.consecutive_failures += 1;
if inner.consecutive_failures >= self.success_threshold {
inner.state = CircuitState::Closed;
inner.consecutive_failures = 0;
inner.opened_at = None;
}
}
_ => {
inner.state = CircuitState::Closed;
inner.consecutive_failures = 0;
inner.opened_at = None;
}
}
}
pub fn record_failure(&self) {
let mut inner = self.inner.lock();
match inner.state {
CircuitState::HalfOpen => {
inner.state = CircuitState::Open;
inner.consecutive_failures = 0;
inner.opened_at = Some(Instant::now());
}
_ => {
inner.consecutive_failures += 1;
if inner.consecutive_failures >= self.failure_threshold {
inner.state = CircuitState::Open;
inner.opened_at = Some(Instant::now());
}
}
}
}
fn refresh(&self, inner: &mut Inner) {
if inner.state == CircuitState::Open {
if let Some(opened) = inner.opened_at {
if opened.elapsed() >= self.cooldown {
inner.state = CircuitState::HalfOpen;
inner.consecutive_failures = 0;
}
}
}
}
}
impl Default for CircuitBreaker {
fn default() -> Self {
CircuitBreaker::new(5, 2, Duration::from_secs(30))
}
}