use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::PoisonError;
static WARNED: AtomicBool = AtomicBool::new(false);
fn warn_once() {
if !WARNED.swap(true, Ordering::Relaxed) {
eprintln!(
"bamboo-subagent: recovered from a poisoned std lock (a thread panicked \
while holding it); continuing with the last-good data. Further recoveries \
this process are silenced."
);
}
}
pub(crate) trait PoisonRecover<G> {
fn recover_poison(self) -> G;
}
impl<G> PoisonRecover<G> for Result<G, PoisonError<G>> {
fn recover_poison(self) -> G {
match self {
Ok(guard) => guard,
Err(poisoned) => {
warn_once();
poisoned.into_inner()
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::Mutex;
#[test]
fn recover_poison_returns_data_after_the_mutex_is_poisoned() {
let pending: Mutex<HashMap<String, u32>> =
Mutex::new(HashMap::from([("req-1".to_string(), 42)]));
let poisoned = catch_unwind(AssertUnwindSafe(|| {
let _guard = pending.lock().unwrap();
panic!("intentional poison for test");
}));
assert!(poisoned.is_err(), "setup panic should have been caught");
assert!(pending.lock().is_err(), "the mutex should now be poisoned");
assert_eq!(pending.lock().recover_poison().remove("req-1"), Some(42));
}
}