#[cfg(test)]
std::thread_local! {
pub(super) static LINK_FAIL_AFTER: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
#[cfg(any(test, feature = "fault-injection"))]
std::thread_local! {
pub(super) static VECTOR_FAIL_AFTER: std::cell::Cell<Option<usize>> =
const { std::cell::Cell::new(None) };
}
#[cfg(any(test, feature = "fault-injection"))]
pub fn arm_vector_fail_after(n: usize) {
VECTOR_FAIL_AFTER.with(|cell| cell.set(Some(n)));
}
#[cfg(any(test, feature = "fault-injection"))]
pub(super) type FaultArmSet =
std::sync::Mutex<std::collections::HashMap<String, std::sync::Arc<()>>>;
#[cfg(any(test, feature = "fault-injection"))]
const MAX_FAULT_ARMS: usize = 64;
#[cfg(any(test, feature = "fault-injection"))]
pub(super) static FTS_FAIL_NS: std::sync::LazyLock<FaultArmSet> =
std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashMap::new()));
#[cfg(any(test, feature = "fault-injection"))]
pub(super) static VECTOR_FAIL_NS: std::sync::LazyLock<FaultArmSet> =
std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashMap::new()));
#[cfg(any(test, feature = "fault-injection"))]
pub(super) static ENTITY_COMPENSATION_FAIL_NS: std::sync::LazyLock<FaultArmSet> =
std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashMap::new()));
#[cfg(any(test, feature = "fault-injection"))]
pub(super) static FTS_FAIL_MANY_NS: std::sync::LazyLock<FaultArmSet> =
std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashMap::new()));
#[cfg(any(test, feature = "fault-injection"))]
pub(super) static FTS_FAIL_MANY_PARTIAL_NS: std::sync::LazyLock<FaultArmSet> =
std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashMap::new()));
#[cfg(any(test, feature = "fault-injection"))]
pub(super) static PREFIX_RESOLVE_FAIL_NS: std::sync::LazyLock<FaultArmSet> =
std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashMap::new()));
#[cfg(any(test, feature = "fault-injection"))]
#[must_use = "the fault injection is disarmed when this guard is dropped"]
pub struct FaultInjectionArm {
namespace: String,
token: std::sync::Arc<()>,
arms: &'static FaultArmSet,
}
#[cfg(any(test, feature = "fault-injection"))]
impl Drop for FaultInjectionArm {
fn drop(&mut self) {
let mut arms = self.arms.lock().unwrap();
if arms
.get(&self.namespace)
.is_some_and(|token| std::sync::Arc::ptr_eq(token, &self.token))
{
arms.remove(&self.namespace);
}
}
}
#[cfg(any(test, feature = "fault-injection"))]
pub(super) fn arm_fault(
arms: &'static FaultArmSet,
namespace: &str,
max_arms: usize,
) -> FaultInjectionArm {
let token = std::sync::Arc::new(());
let refusal = {
let mut active = arms.lock().unwrap();
if active.contains_key(namespace) {
Some("the namespace is already armed")
} else if active.len() >= max_arms {
Some("the arm set is at capacity")
} else {
active.insert(namespace.to_string(), std::sync::Arc::clone(&token));
None
}
};
if let Some(reason) = refusal {
panic!("cannot arm fault injection for namespace `{namespace}`: {reason}");
}
FaultInjectionArm {
namespace: namespace.to_string(),
token,
arms,
}
}
#[cfg(any(test, feature = "fault-injection"))]
pub(super) fn consume_fault(arms: &FaultArmSet, namespace: &str) -> bool {
arms.lock().unwrap().remove(namespace).is_some()
}
#[cfg(any(test, feature = "fault-injection"))]
pub(super) static FTS_SEARCH_FAIL_NS: std::sync::Mutex<Option<String>> =
std::sync::Mutex::new(None);
#[cfg(any(test, feature = "fault-injection"))]
pub fn arm_fts_fail_scoped(ns: &str) -> FaultInjectionArm {
arm_fault(&FTS_FAIL_NS, ns, MAX_FAULT_ARMS)
}
#[cfg(any(test, feature = "fault-injection"))]
pub fn arm_fts_fail_many_scoped(ns: &str) -> FaultInjectionArm {
arm_fault(&FTS_FAIL_MANY_NS, ns, MAX_FAULT_ARMS)
}
#[cfg(any(test, feature = "fault-injection"))]
pub fn arm_fts_fail_many_partial_scoped(ns: &str) -> FaultInjectionArm {
arm_fault(&FTS_FAIL_MANY_PARTIAL_NS, ns, MAX_FAULT_ARMS)
}
#[cfg(any(test, feature = "fault-injection"))]
pub fn arm_fts_search_fail(ns: &str) {
*FTS_SEARCH_FAIL_NS.lock().unwrap() = Some(ns.to_string());
}
#[cfg(any(test, feature = "fault-injection"))]
pub fn arm_vector_fail_scoped(ns: &str) -> FaultInjectionArm {
arm_fault(&VECTOR_FAIL_NS, ns, MAX_FAULT_ARMS)
}
#[cfg(any(test, feature = "fault-injection"))]
pub fn arm_entity_compensation_fail_scoped(ns: &str) -> FaultInjectionArm {
arm_fault(&ENTITY_COMPENSATION_FAIL_NS, ns, MAX_FAULT_ARMS)
}
#[cfg(any(test, feature = "fault-injection"))]
pub fn arm_prefix_resolve_fail_scoped(prefix: &str) -> FaultInjectionArm {
arm_fault(&PREFIX_RESOLVE_FAIL_NS, prefix, MAX_FAULT_ARMS)
}
#[cfg(any(test, feature = "fault-injection"))]
pub(super) static ROLLBACK_CLEANUP_FAIL_NS: std::sync::Mutex<Option<String>> =
std::sync::Mutex::new(None);
#[cfg(any(test, feature = "fault-injection"))]
pub fn arm_rollback_cleanup_fail(ns: &str) {
*ROLLBACK_CLEANUP_FAIL_NS.lock().unwrap() = Some(ns.to_string());
}
#[cfg(any(test, feature = "fault-injection"))]
pub(crate) fn consume_fts_fail_fault(ns: &str) -> bool {
consume_fault(&FTS_FAIL_NS, ns)
}
#[cfg(any(test, feature = "fault-injection"))]
pub(crate) fn consume_vector_fail_fault(ns: &str) -> bool {
consume_fault(&VECTOR_FAIL_NS, ns)
}