use std::sync::{
Arc, Mutex,
atomic::{AtomicUsize, Ordering},
};
pub struct SnapshotState {
inserts_since_snapshot: AtomicUsize,
poison: Mutex<Option<String>>,
}
impl SnapshotState {
pub fn new() -> Arc<Self> {
Arc::new(Self {
inserts_since_snapshot: AtomicUsize::new(0),
poison: Mutex::new(None),
})
}
pub fn record_inserts(&self, count: usize) {
self.inserts_since_snapshot
.fetch_add(count, Ordering::Relaxed);
}
pub fn inserts_since_snapshot(&self) -> usize {
self.inserts_since_snapshot.load(Ordering::Relaxed)
}
pub fn on_snapshot_success(&self) {
self.inserts_since_snapshot.store(0, Ordering::Relaxed);
}
pub fn on_snapshot_failure(&self, error: &str) {
let mut guard = self.poison.lock().unwrap();
if guard.is_none() {
*guard = Some(error.to_owned());
}
}
pub fn check_poison(&self) -> Option<String> {
self.poison.lock().unwrap().clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn counter_starts_at_zero() {
let state = SnapshotState::new();
assert_eq!(state.inserts_since_snapshot(), 0);
}
#[test]
fn counter_increments_on_record_inserts() {
let state = SnapshotState::new();
state.record_inserts(1);
assert_eq!(state.inserts_since_snapshot(), 1);
state.record_inserts(5);
assert_eq!(state.inserts_since_snapshot(), 6);
}
#[test]
fn successful_snapshot_resets_counter() {
let state = SnapshotState::new();
state.record_inserts(100);
state.on_snapshot_success();
assert_eq!(state.inserts_since_snapshot(), 0);
}
#[test]
fn failed_snapshot_does_not_reset_counter() {
let state = SnapshotState::new();
state.record_inserts(42);
state.on_snapshot_failure("disk full");
assert_eq!(state.inserts_since_snapshot(), 42);
}
#[test]
fn no_poison_initially() {
let state = SnapshotState::new();
assert!(state.check_poison().is_none());
}
#[test]
fn poison_stores_first_error_only() {
let state = SnapshotState::new();
state.on_snapshot_failure("first error");
state.on_snapshot_failure("second error");
assert_eq!(state.check_poison().unwrap(), "first error");
}
#[test]
fn poison_is_retained_after_multiple_checks() {
let state = SnapshotState::new();
state.on_snapshot_failure("boom");
assert_eq!(state.check_poison().unwrap(), "boom");
assert_eq!(state.check_poison().unwrap(), "boom");
}
}