use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, MutexGuard};
pub(crate) trait Clearable {
fn reset(&mut self);
}
impl<T> Clearable for Option<T> {
fn reset(&mut self) {
*self = None;
}
}
pub(crate) fn lock_recover<'a, T>(m: &'a Mutex<T>, what: &str) -> MutexGuard<'a, T> {
m.lock().unwrap_or_else(|e| {
log::error!("recovered poisoned scalar lock ({what}); continuing on inner value");
m.clear_poison();
e.into_inner()
})
}
pub(crate) fn lock_or_clear<'a, T: Clearable>(m: &'a Mutex<T>, what: &str) -> MutexGuard<'a, T> {
match m.lock() {
Ok(g) => g,
Err(e) => {
log::error!("cleared poisoned cache lock ({what})");
m.clear_poison();
let mut g = e.into_inner();
g.reset();
g
}
}
}
pub(crate) fn lock_or_flag<'a, T>(
m: &'a Mutex<T>,
needs_rebuild: &AtomicBool,
what: &str,
) -> MutexGuard<'a, T> {
m.lock().unwrap_or_else(|e| {
log::error!("poisoned VFS-state lock ({what}); scheduling full rebuild");
needs_rebuild.store(true, Ordering::Release);
m.clear_poison();
e.into_inner()
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
fn poison<T: Send + 'static>(m: &Arc<Mutex<T>>) {
let m2 = Arc::clone(m);
let _ = std::thread::spawn(move || {
let _g = m2.lock().unwrap();
panic!("poison it");
})
.join();
assert!(m.is_poisoned());
}
#[test]
fn recover_returns_inner_after_poison() {
let m = Arc::new(Mutex::new(7u32));
poison(&m);
assert_eq!(*lock_recover(&m, "scalar"), 7);
assert!(!m.is_poisoned(), "poison cleared after recovery");
}
#[test]
fn clear_empties_cache_after_poison() {
let m = Arc::new(Mutex::new(Some(42u32)));
poison(&m);
assert!(lock_or_clear(&m, "cache").is_none());
assert!(!m.is_poisoned(), "poison cleared after clearing the cache");
}
#[test]
fn flag_set_after_poison() {
let m = Arc::new(Mutex::new(0u32));
let flag = AtomicBool::new(false);
poison(&m);
{
let _g = lock_or_flag(&m, &flag, "vfs");
assert!(flag.load(Ordering::Acquire));
}
assert!(!m.is_poisoned(), "poison cleared after flagging a rebuild");
}
}