#[cfg(debug_assertions)]
use std::{cell::RefCell, thread_local};
#[cfg(debug_assertions)]
thread_local! {
static LOCK_LEVELS: RefCell<Vec<u32>> = const { RefCell::new(Vec::new()) };
}
#[derive(Debug)]
pub(crate) struct Level {
#[cfg(debug_assertions)]
pub(crate) level: u32,
}
impl Default for Level {
#[inline]
fn default() -> Self {
Self::new(0)
}
}
impl Level {
#[inline]
pub fn new(level: u32) -> Self {
#[cfg(not(debug_assertions))]
let _ = level;
Self {
#[cfg(debug_assertions)]
level,
}
}
#[inline]
pub fn lock(&self) -> LevelGuard {
#[cfg(debug_assertions)]
LOCK_LEVELS.with(|levels| {
let mut levels = levels.borrow_mut();
if let Some(&lowest) = levels.last() {
if lowest <= self.level {
panic!(
"Tried to acquire lock with level {} while a lock with level {} \
is acquired. This is a violation of lock hierarchies which \
could lead to deadlocks.",
self.level, lowest
)
}
}
levels.push(self.level);
});
LevelGuard {
#[cfg(debug_assertions)]
level: self.level,
}
}
}
pub struct LevelGuard {
#[cfg(debug_assertions)]
pub(crate) level: u32,
}
#[cfg(debug_assertions)]
impl Drop for LevelGuard {
#[inline]
fn drop(&mut self) {
LOCK_LEVELS.with(|levels| {
let mut levels = levels.borrow_mut();
let index = levels
.iter()
.rposition(|&level| level == self.level)
.expect("Position must exist, because we inserted it during lock!");
levels.remove(index);
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(debug_assertions)]
#[should_panic(
expected = "Tried to acquire lock with level 0 while a lock with level 0 is acquired. This is a violation of lock hierarchies which could lead to deadlocks."
)]
fn self_deadlock_detected() {
let mutex = Level::new(0);
let _guard_a = mutex.lock();
let _guard_b = mutex.lock();
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(
expected = "Tried to acquire lock with level 0 while a lock with level 0 is acquired. This is a violation of lock hierarchies which could lead to deadlocks."
)]
fn panic_if_two_mutexes_with_level_0_are_acquired() {
let mutex_a = Level::new(0);
let mutex_b = Level::new(0);
let _guard_a = mutex_a.lock();
let _guard_b = mutex_b.lock();
}
#[test]
#[cfg(debug_assertions)]
fn created_by_default_impl_should_be_level_0() {
let mutex = Level::default();
assert_eq!(mutex.level, 0);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(
expected = "Tried to acquire lock with level 1 while a lock with level 0 is acquired. This is a violation of lock hierarchies which could lead to deadlocks."
)]
fn panic_if_0_is_acquired_before_1() {
let mutex_a = Level::new(0); let mutex_b = Level::new(1);
let _guard_a = mutex_a.lock();
let _guard_b = mutex_b.lock();
}
#[test]
#[cfg(not(debug_assertions))]
fn should_not_check_in_release_build() {
let mutex_a = Level::new(0);
let mutex_b = Level::new(0);
let _guard_a = mutex_a.lock();
let _guard_b = mutex_b.lock();
}
#[test]
fn two_level_0_in_succession() {
let mutex_a = Level::new(5); let mutex_b = Level::new(42); {
let _guard_a = mutex_a.lock();
}
let _guard_b = mutex_b.lock();
}
#[test]
fn simultaneous_lock_if_higher_is_acquired_first() {
let mutex_a = Level::new(1);
let mutex_b = Level::new(0);
let _guard_a = mutex_a.lock();
let _guard_b = mutex_b.lock();
}
#[test]
fn any_order_of_release() {
let mutex_a = Level::new(2);
let mutex_b = Level::new(1);
let mutex_c = Level::new(0);
let _guard_a = mutex_a.lock();
let guard_b = mutex_b.lock();
let _guard_c = mutex_c.lock();
#[allow(clippy::drop_non_drop)]
drop(guard_b)
}
}