use std::{sync::Arc, time::Duration};
use crate::error::EventStoreError;
#[derive(Clone, Debug)]
pub enum HaltReason {
BackpressureStall {
stalled_for: Duration,
threshold: Duration,
},
BackendDisk(String),
BackendCorrupted(String),
BackendError(String),
}
impl HaltReason {
#[must_use]
pub fn from_backend_error(err: &EventStoreError) -> Self {
match err {
EventStoreError::Disk(msg) => Self::BackendDisk(msg.clone()),
EventStoreError::Corrupted(msg) => Self::BackendCorrupted(msg.clone()),
other => Self::BackendError(other.to_string()),
}
}
}
pub type HaltCallback = Arc<dyn Fn(HaltReason) + Send + Sync + 'static>;
#[must_use]
pub fn noop_halt() -> HaltCallback {
Arc::new(|_reason| ())
}
#[cfg(test)]
mod tests {
use std::sync::Mutex;
use rstest::rstest;
use super::*;
#[rstest]
fn from_backend_error_classifies_disk_as_disk() {
let err = EventStoreError::Disk("ENOSPC".to_string());
let reason = HaltReason::from_backend_error(&err);
match reason {
HaltReason::BackendDisk(msg) => assert!(msg.contains("ENOSPC"), "msg was: {msg}"),
other => panic!("expected BackendDisk, was {other:?}"),
}
}
#[rstest]
fn from_backend_error_classifies_corrupted_as_corrupted() {
let err = EventStoreError::Corrupted("bad page".to_string());
let reason = HaltReason::from_backend_error(&err);
match reason {
HaltReason::BackendCorrupted(msg) => {
assert!(msg.contains("bad page"), "msg was: {msg}");
}
other => panic!("expected BackendCorrupted, was {other:?}"),
}
}
#[rstest]
fn from_backend_error_classifies_other_variants_as_error() {
let err = EventStoreError::Closed;
let reason = HaltReason::from_backend_error(&err);
match reason {
HaltReason::BackendError(_) => {}
other => panic!("expected BackendError, was {other:?}"),
}
}
#[rstest]
fn noop_halt_does_not_panic() {
let halt = noop_halt();
halt(HaltReason::BackendDisk("test".to_string()));
}
#[rstest]
fn callback_runs_on_invocation() {
let captured: Arc<Mutex<Option<HaltReason>>> = Arc::new(Mutex::new(None));
let captured_for_cb = Arc::clone(&captured);
let halt: HaltCallback = Arc::new(move |reason| {
*captured_for_cb.lock().expect("lock") = Some(reason);
});
halt(HaltReason::BackendDisk("stall".to_string()));
match captured.lock().expect("lock").take() {
Some(HaltReason::BackendDisk(msg)) => assert_eq!(msg, "stall"),
other => panic!("expected BackendDisk, was {other:?}"),
}
}
}