use crate::error::AedbError;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::LazyLock;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone)]
pub enum FaultPlan {
Always,
Once,
FailAfter(u64),
EveryNth(u64),
}
#[derive(Debug)]
struct ArmedFault {
plan: FaultPlan,
hits: u64,
message: String,
}
struct Registry {
faults: HashMap<String, ArmedFault>,
}
static REGISTRY: LazyLock<Mutex<Registry>> = LazyLock::new(|| {
Mutex::new(Registry {
faults: HashMap::new(),
})
});
static ARMED_COUNT: AtomicU64 = AtomicU64::new(0);
pub fn arm(name: &str, plan: FaultPlan) {
arm_with_message(name, plan, &format!("injected fault at '{name}'"));
}
pub fn arm_with_message(name: &str, plan: FaultPlan, message: &str) {
let mut registry = REGISTRY.lock();
let existed = registry.faults.contains_key(name);
registry.faults.insert(
name.to_string(),
ArmedFault {
plan,
hits: 0,
message: message.to_string(),
},
);
if !existed {
ARMED_COUNT.fetch_add(1, Ordering::Release);
}
}
pub fn disarm(name: &str) {
let mut registry = REGISTRY.lock();
if registry.faults.remove(name).is_some() {
ARMED_COUNT.fetch_sub(1, Ordering::Release);
}
}
pub fn reset() {
let mut registry = REGISTRY.lock();
let n = registry.faults.len() as u64;
registry.faults.clear();
ARMED_COUNT.fetch_sub(n, Ordering::Release);
}
#[inline]
pub fn any_armed() -> bool {
ARMED_COUNT.load(Ordering::Acquire) != 0
}
#[inline]
pub fn trip(name: &str) -> Result<(), AedbError> {
if !any_armed() {
return Ok(());
}
let mut registry = REGISTRY.lock();
let Some(fault) = registry.faults.get_mut(name) else {
return Ok(());
};
fault.hits += 1;
let fires = match fault.plan {
FaultPlan::Always => true,
FaultPlan::Once => true,
FaultPlan::FailAfter(n) => fault.hits > n,
FaultPlan::EveryNth(0) => false,
FaultPlan::EveryNth(n) => fault.hits.is_multiple_of(n),
};
if !fires {
return Ok(());
}
let message = fault.message.clone();
let disarm_now = matches!(fault.plan, FaultPlan::Once);
if disarm_now {
registry.faults.remove(name);
ARMED_COUNT.fetch_sub(1, Ordering::Release);
}
Err(AedbError::Io(std::io::Error::other(message)))
}
#[cfg(test)]
#[path = "faults_tests.rs"]
mod faults_tests;