#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HealingAction {
Proceed,
SkipCommit,
RebuildCrdt,
}
pub struct HealthTracker {
consecutive_failures: u32,
rebuild_threshold: u32,
}
impl HealthTracker {
pub fn new() -> Self {
Self {
consecutive_failures: 0,
rebuild_threshold: 3,
}
}
pub fn with_threshold(rebuild_threshold: u32) -> Self {
Self {
consecutive_failures: 0,
rebuild_threshold,
}
}
pub fn record_success(&mut self) -> HealingAction {
self.consecutive_failures = 0;
HealingAction::Proceed
}
pub fn record_failure(&mut self) -> HealingAction {
self.consecutive_failures += 1;
if self.consecutive_failures >= self.rebuild_threshold {
HealingAction::RebuildCrdt
} else {
HealingAction::SkipCommit
}
}
pub fn consecutive_failures(&self) -> u32 {
self.consecutive_failures
}
pub fn is_healthy(&self) -> bool {
self.consecutive_failures == 0
}
}
impl Default for HealthTracker {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_tracker_is_healthy() {
let tracker = HealthTracker::new();
assert!(tracker.is_healthy());
assert_eq!(tracker.consecutive_failures(), 0);
}
#[test]
fn test_success_returns_proceed() {
let mut tracker = HealthTracker::new();
assert_eq!(tracker.record_success(), HealingAction::Proceed);
assert!(tracker.is_healthy());
}
#[test]
fn test_first_failure_returns_skip() {
let mut tracker = HealthTracker::new();
assert_eq!(tracker.record_failure(), HealingAction::SkipCommit);
assert!(!tracker.is_healthy());
assert_eq!(tracker.consecutive_failures(), 1);
}
#[test]
fn test_three_failures_returns_rebuild() {
let mut tracker = HealthTracker::new();
assert_eq!(tracker.record_failure(), HealingAction::SkipCommit);
assert_eq!(tracker.record_failure(), HealingAction::SkipCommit);
assert_eq!(tracker.record_failure(), HealingAction::RebuildCrdt);
assert_eq!(tracker.consecutive_failures(), 3);
}
#[test]
fn test_success_resets_counter() {
let mut tracker = HealthTracker::new();
tracker.record_failure();
tracker.record_failure();
assert_eq!(tracker.consecutive_failures(), 2);
tracker.record_success();
assert!(tracker.is_healthy());
assert_eq!(tracker.consecutive_failures(), 0);
assert_eq!(tracker.record_failure(), HealingAction::SkipCommit);
}
#[test]
fn test_custom_threshold() {
let mut tracker = HealthTracker::with_threshold(2);
assert_eq!(tracker.record_failure(), HealingAction::SkipCommit);
assert_eq!(tracker.record_failure(), HealingAction::RebuildCrdt);
}
#[test]
fn test_continued_failures_stay_rebuild() {
let mut tracker = HealthTracker::new();
for _ in 0..3 {
tracker.record_failure();
}
assert_eq!(tracker.record_failure(), HealingAction::RebuildCrdt);
assert_eq!(tracker.consecutive_failures(), 4);
}
}