use std::{
collections::HashSet,
fmt::Debug,
sync::atomic::{AtomicBool, AtomicU32, Ordering},
};
use crate::ENABLED;
#[cfg(feature = "enabled")]
#[doc(hidden)]
#[linkme::distributed_slice]
pub static FAULT_CATALOG: [FaultEntry];
#[cfg(not(feature = "enabled"))]
#[doc(hidden)]
pub static FAULT_CATALOG: [&FaultEntry; 0] = [];
pub(crate) fn init_faults() {
let mut seen = HashSet::new();
for entry in FAULT_CATALOG {
if !seen.insert(entry.name) {
panic!("Duplicate Precept fault: {}", entry.name);
}
}
}
#[derive(Debug)]
pub struct FaultEntry {
name: &'static str,
enabled: AtomicBool,
pending_trips: AtomicU32,
}
impl FaultEntry {
pub const fn new(name: &'static str) -> Self {
Self {
name,
enabled: AtomicBool::new(true),
pending_trips: AtomicU32::new(0),
}
}
pub fn trip(&self) -> bool {
if self
.pending_trips
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |count| {
if count > 0 { Some(count - 1) } else { None }
})
.is_ok()
{
true
} else if self.enabled.load(Ordering::Acquire) {
let should_fault = crate::dispatch::choose(&[true, false]);
should_fault.is_some_and(|&t| t)
} else {
false
}
}
pub fn enable(&self) {
self.enabled.store(true, Ordering::Release);
}
pub fn disable(&self) {
self.enabled.store(false, Ordering::Release);
}
pub fn set_pending(&self, count: u32) {
self.pending_trips.store(count, Ordering::Release);
}
pub fn count_pending(&self) -> u32 {
self.pending_trips.load(Ordering::Acquire)
}
}
pub fn enable_all() {
assert!(ENABLED, "Precept is disabled");
for entry in FAULT_CATALOG {
entry.enable()
}
}
pub fn disable_all() {
tracing::warn!("Precept Faults disabled");
for entry in FAULT_CATALOG {
entry.disable();
}
}
pub fn get_fault_by_name(name: &str) -> Option<&'static FaultEntry> {
FAULT_CATALOG.into_iter().find(|&entry| entry.name == name)
}