use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
pub struct DeadlockDetectorGuard {
shutdown: Arc<AtomicBool>,
}
impl Drop for DeadlockDetectorGuard {
fn drop(&mut self) {
self.shutdown.store(true, Ordering::Relaxed);
}
}
pub fn init_deadlock_detector_with_config(
interval: Duration,
panic_on_deadlock: bool,
) -> DeadlockDetectorGuard {
let shutdown = Arc::new(AtomicBool::new(false));
let shutdown_clone = shutdown.clone();
std::thread::Builder::new()
.name("deadlock-detector".into())
.spawn(move || {
while !shutdown_clone.load(Ordering::Relaxed) {
std::thread::sleep(interval);
let deadlocks = parking_lot::deadlock::check_deadlock();
if deadlocks.is_empty() {
continue;
}
let mut report = format!("{} deadlock(s) detected!\n", deadlocks.len());
for (i, threads) in deadlocks.iter().enumerate() {
report.push_str(&format!("Deadlock #{i}:\n"));
for t in threads {
report.push_str(&format!(
" Thread Id {:#?}\n Backtrace:\n{:#?}\n",
t.thread_id(),
t.backtrace()
));
}
}
eprintln!("{report}");
if panic_on_deadlock {
panic!("Deadlock detected! See error output above for details.");
}
}
})
.expect("failed to spawn deadlock detector thread");
DeadlockDetectorGuard { shutdown }
}
pub fn init_deadlock_detector() -> DeadlockDetectorGuard {
init_deadlock_detector_with_config(Duration::from_secs(1), true)
}
#[cfg(test)]
mod tests {
use super::*;
use parking_lot::Mutex;
use serial_test::serial;
use std::sync::Arc;
#[test]
#[serial(deadlock_detection)]
fn test_detector_lifecycle_no_false_positives() {
let guard = init_deadlock_detector_with_config(Duration::from_millis(100), false);
let mutex = Arc::new(Mutex::new(0));
let m = mutex.clone();
let handle = std::thread::spawn(move || {
let mut val = m.lock();
*val += 1;
});
handle.join().unwrap();
assert_eq!(*mutex.lock(), 1);
std::thread::sleep(Duration::from_millis(350));
drop(guard);
}
#[test]
#[serial(deadlock_detection)]
fn test_deadlock_is_detected() {
use std::sync::Barrier;
let _ = parking_lot::deadlock::check_deadlock();
let lock_a = Arc::new(Mutex::new(()));
let lock_b = Arc::new(Mutex::new(()));
let barrier = Arc::new(Barrier::new(2));
let la1 = lock_a.clone();
let lb1 = lock_b.clone();
let b1 = barrier.clone();
let _t1 = std::thread::spawn(move || {
let _a = la1.lock();
b1.wait();
let _b = lb1.lock();
});
let la2 = lock_a.clone();
let lb2 = lock_b.clone();
let b2 = barrier.clone();
let _t2 = std::thread::spawn(move || {
let _b = lb2.lock();
b2.wait();
let _a = la2.lock();
});
let mut detected = false;
for _ in 0..20 {
std::thread::sleep(Duration::from_millis(100));
let deadlocks = parking_lot::deadlock::check_deadlock();
if !deadlocks.is_empty() {
assert_eq!(deadlocks.len(), 1, "Expected exactly 1 deadlock cycle");
assert_eq!(
deadlocks[0].len(),
2,
"Expected 2 threads in the deadlock cycle"
);
detected = true;
break;
}
}
assert!(
detected,
"Expected deadlock to be detected within 2 seconds. \
This likely means the deadlock_detection feature is not enabled on parking_lot, \
or another test consumed the deadlock state (use nextest or --test-threads=1)."
);
}
}