use bon::Builder;
use jiff::{SignedDuration, Timestamp};
use std::num::NonZeroU32;
use tap::{Conv, Pipe};
#[derive(Builder)]
pub struct CircuitBreaker {
#[builder(skip)]
state: CircuitState,
#[builder(skip)]
failure_count: u32,
#[builder(default = unsafe { NonZeroU32::new_unchecked(25)})]
failure_threshold: NonZeroU32,
#[builder(skip = Timestamp::UNIX_EPOCH)]
last_failure: Timestamp,
#[builder(default = unsafe { NonZeroU32::new_unchecked(15_000)})]
failure_timeout_ms: NonZeroU32,
#[builder(skip)]
success_count: u32,
#[builder(default = unsafe { NonZeroU32::new_unchecked(3)})]
success_threshold: NonZeroU32,
}
impl CircuitBreaker {
pub fn new() -> Self {
Self::default()
}
pub(crate) fn update(&mut self) -> CircuitState {
match self.state {
CircuitState::Closed => {
if self.failure_count >= self.failure_threshold.get() {
self.enter(CircuitState::Open);
}
}
CircuitState::Open => {
if !self.has_recent_failure() {
self.enter(CircuitState::HalfOpen);
}
}
CircuitState::HalfOpen => {
if self.has_recent_failure() {
self.enter(CircuitState::Open);
} else if self.success_count >= self.success_threshold.get() {
self.enter(CircuitState::Closed);
}
}
}
self.state
}
fn enter(&mut self, state: CircuitState) {
debug_assert_ne!(self.state, state);
match state {
CircuitState::Closed => {
self.failure_count = 0;
}
CircuitState::Open | CircuitState::HalfOpen => {
self.success_count = 0;
}
}
self.state = state;
}
pub fn has_recent_failure(&self) -> bool {
let timeout = self
.failure_timeout_ms
.get()
.conv::<i64>()
.pipe(SignedDuration::from_millis);
Timestamp::now()
.duration_since(self.last_failure)
.le(&timeout)
}
pub(crate) fn record_failure(&mut self) {
self.last_failure = Timestamp::now();
if self.state == CircuitState::HalfOpen {
self.enter(CircuitState::Open);
} else {
self.failure_count += 1;
}
}
pub(crate) fn record_success(&mut self) {
if let CircuitState::HalfOpen = self.state {
self.success_count += 1;
}
}
}
impl Default for CircuitBreaker {
fn default() -> Self {
Self::builder().build()
}
}
#[derive(Copy, Debug)]
#[derive_const(Clone, Default, PartialEq, Eq)]
pub enum CircuitState {
#[default]
Closed,
Open,
HalfOpen,
}