epaint/
mutex.rs

1//! Wrappers around `parking_lot` locks, with a simple deadlock detection mechanism.
2
3// ----------------------------------------------------------------------------
4
5const DEADLOCK_DURATION: std::time::Duration = std::time::Duration::from_secs(10);
6
7/// Provides interior mutability.
8///
9/// It's tailored for internal use in egui should only be used for short locks (as a guideline,
10/// locks should never be held longer than a single frame). In debug builds, when a lock can't
11/// be acquired within 10 seconds, we assume a deadlock and will panic.
12///
13/// This is a thin wrapper around [`parking_lot::Mutex`].
14#[derive(Default)]
15pub struct Mutex<T>(parking_lot::Mutex<T>);
16
17/// The lock you get from [`Mutex`].
18pub use parking_lot::MutexGuard;
19
20impl<T> Mutex<T> {
21    #[inline(always)]
22    pub fn new(val: T) -> Self {
23        Self(parking_lot::Mutex::new(val))
24    }
25
26    /// Try to acquire the lock.
27    ///
28    /// ## Panics
29    /// Will panic in debug builds if the lock can't be acquired within 10 seconds.
30    #[inline(always)]
31    #[cfg_attr(debug_assertions, track_caller)]
32    pub fn lock(&self) -> MutexGuard<'_, T> {
33        if cfg!(debug_assertions) {
34            self.0.try_lock_for(DEADLOCK_DURATION).unwrap_or_else(|| {
35                panic!(
36                    "DEBUG PANIC: Failed to acquire Mutex after {}s. Deadlock?",
37                    DEADLOCK_DURATION.as_secs()
38                )
39            })
40        } else {
41            self.0.lock()
42        }
43    }
44}
45
46// ----------------------------------------------------------------------------
47
48/// The lock you get from [`RwLock::read`].
49pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard;
50
51/// The lock you get from [`RwLock::write`].
52pub use parking_lot::MappedRwLockWriteGuard as RwLockWriteGuard;
53
54/// Provides interior mutability.
55///
56/// It's tailored for internal use in egui should only be used for short locks (as a guideline,
57/// locks should never be held longer than a single frame). In debug builds, when a lock can't
58/// be acquired within 10 seconds, we assume a deadlock and will panic.
59///
60/// This is a thin wrapper around [`parking_lot::RwLock`].
61#[derive(Default)]
62pub struct RwLock<T: ?Sized>(parking_lot::RwLock<T>);
63
64impl<T> RwLock<T> {
65    #[inline(always)]
66    pub fn new(val: T) -> Self {
67        Self(parking_lot::RwLock::new(val))
68    }
69}
70
71impl<T: ?Sized> RwLock<T> {
72    /// Try to acquire read-access to the lock.
73    ///
74    /// ## Panics
75    /// Will panic in debug builds if the lock can't be acquired within 10 seconds.
76    #[inline(always)]
77    #[cfg_attr(debug_assertions, track_caller)]
78    pub fn read(&self) -> RwLockReadGuard<'_, T> {
79        let guard = if cfg!(debug_assertions) {
80            self.0.try_read_for(DEADLOCK_DURATION).unwrap_or_else(|| {
81                panic!(
82                    "DEBUG PANIC: Failed to acquire RwLock read after {}s. Deadlock?",
83                    DEADLOCK_DURATION.as_secs()
84                )
85            })
86        } else {
87            self.0.read()
88        };
89        parking_lot::RwLockReadGuard::map(guard, |v| v)
90    }
91
92    /// Try to acquire write-access to the lock.
93    ///
94    /// ## Panics
95    /// Will panic in debug builds if the lock can't be acquired within 10 seconds.
96    #[inline(always)]
97    #[cfg_attr(debug_assertions, track_caller)]
98    pub fn write(&self) -> RwLockWriteGuard<'_, T> {
99        let guard = if cfg!(debug_assertions) {
100            self.0.try_write_for(DEADLOCK_DURATION).unwrap_or_else(|| {
101                panic!(
102                    "DEBUG PANIC: Failed to acquire RwLock write after {}s. Deadlock?",
103                    DEADLOCK_DURATION.as_secs()
104                )
105            })
106        } else {
107            self.0.write()
108        };
109        parking_lot::RwLockWriteGuard::map(guard, |v| v)
110    }
111}
112
113// ----------------------------------------------------------------------------
114
115impl<T> Clone for Mutex<T>
116where
117    T: Clone,
118{
119    fn clone(&self) -> Self {
120        Self::new(self.lock().clone())
121    }
122}
123
124// ----------------------------------------------------------------------------
125
126#[cfg(test)]
127mod tests {
128    #![allow(clippy::disallowed_methods)] // Ok for tests
129
130    use crate::mutex::Mutex;
131    use std::time::Duration;
132
133    #[test]
134    fn lock_two_different_mutexes_single_thread() {
135        let one = Mutex::new(());
136        let two = Mutex::new(());
137        let _a = one.lock();
138        let _b = two.lock();
139    }
140
141    #[test]
142    fn lock_multiple_threads() {
143        use std::sync::Arc;
144        let one = Arc::new(Mutex::new(()));
145        let our_lock = one.lock();
146        let other_thread = {
147            let one = Arc::clone(&one);
148            std::thread::spawn(move || {
149                let _lock = one.lock();
150            })
151        };
152        std::thread::sleep(Duration::from_millis(200));
153        drop(our_lock);
154        other_thread.join().unwrap();
155    }
156}
157
158#[cfg(not(target_arch = "wasm32"))]
159#[cfg(test)]
160mod tests_rwlock {
161    #![allow(clippy::disallowed_methods)] // Ok for tests
162
163    use crate::mutex::RwLock;
164    use std::time::Duration;
165
166    #[test]
167    fn lock_two_different_rwlocks_single_thread() {
168        let one = RwLock::new(());
169        let two = RwLock::new(());
170        let _a = one.write();
171        let _b = two.write();
172    }
173
174    #[test]
175    fn rwlock_multiple_threads() {
176        use std::sync::Arc;
177        let one = Arc::new(RwLock::new(()));
178        let our_lock = one.write();
179        let other_thread1 = {
180            let one = Arc::clone(&one);
181            std::thread::spawn(move || {
182                let _ = one.write();
183            })
184        };
185        let other_thread2 = {
186            let one = Arc::clone(&one);
187            std::thread::spawn(move || {
188                let _ = one.read();
189            })
190        };
191        std::thread::sleep(Duration::from_millis(200));
192        drop(our_lock);
193        other_thread1.join().unwrap();
194        other_thread2.join().unwrap();
195    }
196
197    #[test]
198    #[should_panic]
199    fn rwlock_write_write_reentrancy() {
200        let one = RwLock::new(());
201        let _a1 = one.write();
202        let _a2 = one.write(); // panics
203    }
204
205    #[test]
206    #[should_panic]
207    fn rwlock_write_read_reentrancy() {
208        let one = RwLock::new(());
209        let _a1 = one.write();
210        let _a2 = one.read(); // panics
211    }
212
213    #[test]
214    #[should_panic]
215    fn rwlock_read_write_reentrancy() {
216        let one = RwLock::new(());
217        let _a1 = one.read();
218        let _a2 = one.write(); // panics
219    }
220
221    #[test]
222    fn rwlock_read_read_reentrancy() {
223        let one = RwLock::new(());
224        let _a1 = one.read();
225        // This is legal: this test suite specifically targets native, which relies
226        // on parking_lot's rw-locks, which are reentrant.
227        let _a2 = one.read();
228    }
229
230    #[test]
231    fn rwlock_short_read_foreign_read_write_reentrancy() {
232        use std::sync::Arc;
233
234        let lock = Arc::new(RwLock::new(()));
235
236        // Thread #0 grabs a read lock
237        let t0r0 = lock.read();
238
239        // Thread #1 grabs the same read lock
240        let other_thread = {
241            let lock = Arc::clone(&lock);
242            std::thread::spawn(move || {
243                let _t1r0 = lock.read();
244            })
245        };
246        other_thread.join().unwrap();
247
248        // Thread #0 releases its read lock
249        drop(t0r0);
250
251        // Thread #0 now grabs a write lock, which is legal
252        let _t0w0 = lock.write();
253    }
254
255    #[test]
256    #[should_panic]
257    fn rwlock_read_foreign_read_write_reentrancy() {
258        use std::sync::Arc;
259
260        let lock = Arc::new(RwLock::new(()));
261
262        // Thread #0 grabs a read lock
263        let _t0r0 = lock.read();
264
265        // Thread #1 grabs the same read lock
266        let other_thread = {
267            let lock = Arc::clone(&lock);
268            std::thread::spawn(move || {
269                let _t1r0 = lock.read();
270            })
271        };
272        other_thread.join().unwrap();
273
274        // Thread #0 now grabs a write lock, which should panic (read-write)
275        let _t0w0 = lock.write(); // panics
276    }
277}