mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 22:53:14 -04:00
* 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.
269 lines
7.5 KiB
Rust
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
|
|
}
|
|
}
|