1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 22:53:14 -04:00
Files
egui/crates/epaint/src/mutex.rs
Lucas Meurer 80d61a7c53 Remove the deadlock_detection feature (#7497)
* related #7494 

Removes the `deadlock_detection` feature, since we now have a more
primitive panic-after-30s deadlock detection which works well enough and
even detects kinds of deadlocks that the `deadlock_detection` feature
never supported.
2025-09-04 12:57:09 +02:00

269 lines
7.5 KiB
Rust

//! Wrappers around `parking_lot` locks, with a simple deadlock detection mechanism.
// ----------------------------------------------------------------------------
const DEADLOCK_DURATION: std::time::Duration = std::time::Duration::from_secs(10);
/// Provides interior mutability.
///
/// It's tailored for internal use in egui should only be used for short locks (as a guideline,
/// locks should never be held longer than a single frame). In debug builds, when a lock can't
/// be acquired within 10 seconds, we assume a deadlock and will panic.
///
/// This is a thin wrapper around [`parking_lot::Mutex`].
#[derive(Default)]
pub struct Mutex<T>(parking_lot::Mutex<T>);
/// The lock you get from [`Mutex`].
pub use parking_lot::MutexGuard;
impl<T> Mutex<T> {
#[inline(always)]
pub fn new(val: T) -> Self {
Self(parking_lot::Mutex::new(val))
}
/// Try to acquire the lock.
///
/// ## Panics
/// Will panic in debug builds if the lock can't be acquired within 10 seconds.
#[inline(always)]
#[cfg_attr(debug_assertions, track_caller)]
pub fn lock(&self) -> MutexGuard<'_, T> {
if cfg!(debug_assertions) {
self.0
.try_lock_for(DEADLOCK_DURATION)
.expect("Looks like a deadlock!")
} else {
self.0.lock()
}
}
}
// ----------------------------------------------------------------------------
/// The lock you get from [`RwLock::read`].
pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard;
/// The lock you get from [`RwLock::write`].
pub use parking_lot::MappedRwLockWriteGuard as RwLockWriteGuard;
/// Provides interior mutability.
///
/// It's tailored for internal use in egui should only be used for short locks (as a guideline,
/// locks should never be held longer than a single frame). In debug builds, when a lock can't
/// be acquired within 10 seconds, we assume a deadlock and will panic.
///
/// This is a thin wrapper around [`parking_lot::RwLock`].
#[derive(Default)]
pub struct RwLock<T: ?Sized>(parking_lot::RwLock<T>);
impl<T> RwLock<T> {
#[inline(always)]
pub fn new(val: T) -> Self {
Self(parking_lot::RwLock::new(val))
}
}
impl<T: ?Sized> RwLock<T> {
/// Try to acquire read-access to the lock.
///
/// ## Panics
/// Will panic in debug builds if the lock can't be acquired within 10 seconds.
#[inline(always)]
#[cfg_attr(debug_assertions, track_caller)]
pub fn read(&self) -> RwLockReadGuard<'_, T> {
let guard = if cfg!(debug_assertions) {
self.0
.try_read_for(DEADLOCK_DURATION)
.expect("Looks like a deadlock!")
} else {
self.0.read()
};
parking_lot::RwLockReadGuard::map(guard, |v| v)
}
/// Try to acquire write-access to the lock.
///
/// ## Panics
/// Will panic in debug builds if the lock can't be acquired within 10 seconds.
#[inline(always)]
#[cfg_attr(debug_assertions, track_caller)]
pub fn write(&self) -> RwLockWriteGuard<'_, T> {
let guard = if cfg!(debug_assertions) {
self.0
.try_write_for(DEADLOCK_DURATION)
.expect("Looks like a deadlock!")
} else {
self.0.write()
};
parking_lot::RwLockWriteGuard::map(guard, |v| v)
}
}
// ----------------------------------------------------------------------------
impl<T> Clone for Mutex<T>
where
T: Clone,
{
fn clone(&self) -> Self {
Self::new(self.lock().clone())
}
}
// ----------------------------------------------------------------------------
#[cfg(test)]
mod tests {
#![allow(clippy::disallowed_methods)] // Ok for tests
use crate::mutex::Mutex;
use std::time::Duration;
#[test]
fn lock_two_different_mutexes_single_thread() {
let one = Mutex::new(());
let two = Mutex::new(());
let _a = one.lock();
let _b = two.lock();
}
#[test]
fn lock_multiple_threads() {
use std::sync::Arc;
let one = Arc::new(Mutex::new(()));
let our_lock = one.lock();
let other_thread = {
let one = Arc::clone(&one);
std::thread::spawn(move || {
let _lock = one.lock();
})
};
std::thread::sleep(Duration::from_millis(200));
drop(our_lock);
other_thread.join().unwrap();
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(test)]
mod tests_rwlock {
#![allow(clippy::disallowed_methods)] // Ok for tests
use crate::mutex::RwLock;
use std::time::Duration;
#[test]
fn lock_two_different_rwlocks_single_thread() {
let one = RwLock::new(());
let two = RwLock::new(());
let _a = one.write();
let _b = two.write();
}
#[test]
fn rwlock_multiple_threads() {
use std::sync::Arc;
let one = Arc::new(RwLock::new(()));
let our_lock = one.write();
let other_thread1 = {
let one = Arc::clone(&one);
std::thread::spawn(move || {
let _ = one.write();
})
};
let other_thread2 = {
let one = Arc::clone(&one);
std::thread::spawn(move || {
let _ = one.read();
})
};
std::thread::sleep(Duration::from_millis(200));
drop(our_lock);
other_thread1.join().unwrap();
other_thread2.join().unwrap();
}
#[test]
#[should_panic]
fn rwlock_write_write_reentrancy() {
let one = RwLock::new(());
let _a1 = one.write();
let _a2 = one.write(); // panics
}
#[test]
#[should_panic]
fn rwlock_write_read_reentrancy() {
let one = RwLock::new(());
let _a1 = one.write();
let _a2 = one.read(); // panics
}
#[test]
#[should_panic]
fn rwlock_read_write_reentrancy() {
let one = RwLock::new(());
let _a1 = one.read();
let _a2 = one.write(); // panics
}
#[test]
fn rwlock_read_read_reentrancy() {
let one = RwLock::new(());
let _a1 = one.read();
// This is legal: this test suite specifically targets native, which relies
// on parking_lot's rw-locks, which are reentrant.
let _a2 = one.read();
}
#[test]
fn rwlock_short_read_foreign_read_write_reentrancy() {
use std::sync::Arc;
let lock = Arc::new(RwLock::new(()));
// Thread #0 grabs a read lock
let t0r0 = lock.read();
// Thread #1 grabs the same read lock
let other_thread = {
let lock = Arc::clone(&lock);
std::thread::spawn(move || {
let _t1r0 = lock.read();
})
};
other_thread.join().unwrap();
// Thread #0 releases its read lock
drop(t0r0);
// Thread #0 now grabs a write lock, which is legal
let _t0w0 = lock.write();
}
#[test]
#[should_panic]
fn rwlock_read_foreign_read_write_reentrancy() {
use std::sync::Arc;
let lock = Arc::new(RwLock::new(()));
// Thread #0 grabs a read lock
let _t0r0 = lock.read();
// Thread #1 grabs the same read lock
let other_thread = {
let lock = Arc::clone(&lock);
std::thread::spawn(move || {
let _t1r0 = lock.read();
})
};
other_thread.join().unwrap();
// Thread #0 now grabs a write lock, which should panic (read-write)
let _t0w0 = lock.write(); // panics
}
}