#[cfg(feature = "failpoints")]
mod imp {
use std::collections::HashMap;
use std::sync::{LazyLock, Mutex};
use std::time::Duration;
#[derive(Debug, Clone)]
pub enum FailAction {
Panic,
Sleep(Duration),
}
static REGISTRY: LazyLock<Mutex<HashMap<String, FailAction>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
pub fn set(name: &str, action: FailAction) {
REGISTRY
.lock()
.unwrap_or_else(|p| p.into_inner())
.insert(name.to_string(), action);
}
pub fn clear(name: &str) {
REGISTRY
.lock()
.unwrap_or_else(|p| p.into_inner())
.remove(name);
}
pub fn lookup(name: &str) -> Option<FailAction> {
REGISTRY
.lock()
.unwrap_or_else(|p| p.into_inner())
.get(name)
.cloned()
}
pub fn eval(name: &str) {
if let Some(action) = lookup(name) {
match action {
FailAction::Panic => panic!("fail_point fired: {name}"),
FailAction::Sleep(d) => std::thread::sleep(d),
}
}
}
pub struct FailGuard {
name: String,
}
impl FailGuard {
pub fn install(name: &str, action: FailAction) -> Self {
set(name, action);
Self {
name: name.to_string(),
}
}
}
impl Drop for FailGuard {
fn drop(&mut self) {
clear(&self.name);
}
}
}
#[cfg(feature = "failpoints")]
pub use imp::{FailAction, FailGuard, clear, eval, lookup, set};
#[macro_export]
macro_rules! fail_point {
($name:expr) => {
#[cfg(feature = "failpoints")]
$crate::fail_point::eval($name);
};
}
#[cfg(all(test, feature = "failpoints"))]
mod tests {
use super::*;
#[test]
fn unset_fail_point_is_noop() {
eval("nodedb::test::unset");
}
#[test]
#[should_panic(expected = "fail_point fired: nodedb::test::panic_target")]
fn set_panic_fires() {
let _g = FailGuard::install("nodedb::test::panic_target", FailAction::Panic);
eval("nodedb::test::panic_target");
}
#[test]
fn fail_guard_clears_on_drop() {
{
let _g = FailGuard::install("nodedb::test::guard_clear", FailAction::Panic);
assert!(lookup("nodedb::test::guard_clear").is_some());
}
assert!(lookup("nodedb::test::guard_clear").is_none());
}
}