wgpu_core/lock/ranked.rs
1//! Lock types that enforce well-ranked lock acquisition order.
2//!
3//! This module's [`Mutex`] and [`RwLock` types are instrumented to check that
4//! `wgpu-core` acquires locks according to their rank, to prevent deadlocks. To
5//! use it, put `--cfg wgpu_validate_locks` in `RUSTFLAGS`.
6//!
7//! The [`LockRank`] constants in the [`lock::rank`] module describe edges in a
8//! directed graph of lock acquisitions: each lock's rank says, if this is the most
9//! recently acquired lock that you are still holding, then these are the locks you
10//! are allowed to acquire next.
11//!
12//! As long as this graph doesn't have cycles, any number of threads can acquire
13//! locks along paths through the graph without deadlock:
14//!
15//! - Assume that if a thread is holding a lock, then it will either release it,
16//! or block trying to acquire another one. No thread just sits on its locks
17//! forever for unrelated reasons. If it did, then that would be a source of
18//! deadlock "outside the system" that we can't do anything about.
19//!
20//! - This module asserts that threads acquire and release locks in a stack-like
21//! order: a lock is dropped only when it is the *most recently acquired* lock
22//! *still held* - call this the "youngest" lock. This stack-like ordering
23//! isn't a Rust requirement; Rust lets you drop guards in any order you like.
24//! This is a restriction we impose.
25//!
26//! - Consider the directed graph whose nodes are locks, and whose edges go from
27//! each lock to its permitted followers, the locks in its [`LockRank::followers`]
28//! set. The definition of the [`lock::rank`] module's [`LockRank`] constants
29//! ensures that this graph has no cycles, including trivial cycles from a node to
30//! itself.
31//!
32//! - This module then asserts that each thread attempts to acquire a lock only if
33//! it is among its youngest lock's permitted followers. Thus, as a thread
34//! acquires locks, it must be traversing a path through the graph along its
35//! edges.
36//!
37//! - Because there are no cycles in the graph, whenever one thread is blocked
38//! waiting to acquire a lock, that lock must be held by a different thread: if
39//! you were allowed to acquire a lock you already hold, that would be a cycle in
40//! the graph.
41//!
42//! - Furthermore, because the graph has no cycles, as we work our way from each
43//! thread to the thread it is blocked waiting for, we must eventually reach an
44//! end point: there must be some thread that is able to acquire its next lock, or
45//! that is about to release a lock.
46//!
47//! Thus, the system as a whole is always able to make progress: it is free of
48//! deadlocks.
49//!
50//! Note that this validation only monitors each thread's behavior in isolation:
51//! there's only thread-local state, nothing communicated between threads. So we
52//! don't detect deadlocks, per se, only the potential to cause deadlocks. This
53//! means that the validation is conservative, but more reproducible, since it's not
54//! dependent on any particular interleaving of execution.
55//!
56//! [`lock::rank`]: crate::lock::rank
57
58use core::{cell::Cell, fmt, ops, panic::Location};
59
60use super::rank::LockRank;
61
62pub use LockState as RankData;
63
64/// A `Mutex` instrumented for deadlock prevention.
65///
66/// This is just a wrapper around a [`wgpu_sync::Mutex`], along with
67/// its rank in the `wgpu_core` lock ordering.
68///
69/// For details, see [the module documentation][self].
70pub struct Mutex<T> {
71 inner: wgpu_sync::Mutex<T>,
72 rank: LockRank,
73}
74
75/// A guard produced by locking [`Mutex`].
76///
77/// This is just a wrapper around a [`wgpu_sync::MutexGuard`], along
78/// with the state needed to track lock acquisition.
79///
80/// For details, see [the module documentation][self].
81pub struct MutexGuard<'a, T> {
82 inner: wgpu_sync::MutexGuard<'a, T>,
83 #[cfg_attr(not(miri), expect(unused))] // but `Drop` has important side effects
84 saved: LockStateGuard,
85}
86
87std::thread_local! {
88 static LOCK_STATE: Cell<LockState> = const { Cell::new(LockState::INITIAL) };
89}
90
91/// Per-thread state for the deadlock checker.
92#[derive(Debug, Copy, Clone)]
93pub struct LockState {
94 /// The last lock we acquired, and where.
95 last_acquired: Option<(LockRank, &'static Location<'static>)>,
96
97 /// The number of locks currently held.
98 ///
99 /// This is used to enforce stack-like lock acquisition and release.
100 depth: u32,
101}
102
103impl LockState {
104 const INITIAL: LockState = LockState {
105 last_acquired: None,
106 depth: 0,
107 };
108}
109
110/// A container that restores a [`LockState`] when dropped.
111///
112/// This type serves two purposes:
113///
114/// - Operations would like to be able to destructure lock guards and
115/// reassemble their pieces into new guards, but if the guard type
116/// itself implements `Drop`, we can't destructure it without unsafe
117/// code or pointless `Option`s whose state is almost always statically
118/// known.
119///
120/// - We can just implement `Drop` for this type once, and then use it in lock
121/// guards, rather than implementing `Drop` separately for each guard type.
122struct LockStateGuard(LockState);
123
124impl Drop for LockStateGuard {
125 fn drop(&mut self) {
126 release(self.0)
127 }
128}
129
130/// Check and record the acquisition of a lock with `new_rank`.
131///
132/// Check that acquiring a lock with `new_rank` is permitted at this point, and
133/// update the per-thread state accordingly.
134///
135/// Return the `LockState` that must be restored when this thread is released.
136fn acquire(new_rank: LockRank, location: &'static Location<'static>) -> LockState {
137 let state = LOCK_STATE.get();
138 // Initially, it's fine to acquire any lock. So we only
139 // need to check when `last_acquired` is `Some`.
140 if let Some((ref last_rank, ref last_location)) = state.last_acquired {
141 assert!(
142 last_rank.followers.contains(new_rank.bit),
143 "Attempt to acquire nested mutexes in wrong order:\n\
144 last locked {:<35} at {}\n\
145 now locking {:<35} at {}\n\
146 Locking {} after locking {} is not permitted.",
147 last_rank.bit.member_name(),
148 last_location,
149 new_rank.bit.member_name(),
150 location,
151 new_rank.bit.member_name(),
152 last_rank.bit.member_name(),
153 );
154 }
155 LOCK_STATE.set(LockState {
156 last_acquired: Some((new_rank, location)),
157 depth: state.depth + 1,
158 });
159 state
160}
161
162/// Record the release of a lock whose saved state was `saved`.
163///
164/// Check that locks are being acquired in stacking order, and update the
165/// per-thread state accordingly.
166fn release(saved: LockState) {
167 let saved_info = saved.last_acquired;
168
169 let prior = LOCK_STATE.replace(saved);
170
171 let (prior_rank, prior_location) = prior
172 .last_acquired
173 .expect("Releasing a lock, but no acquisition recorded");
174
175 // Although Rust allows mutex guards to be dropped in any
176 // order, this analysis requires that locks be acquired and
177 // released in stack order: the next lock to be released must be
178 // the most recently acquired lock still held.
179
180 match (saved.depth, saved_info) {
181 (saved_depth @ 0, None) => {
182 assert_eq!(
183 prior.depth,
184 saved_depth + 1,
185 "Lock not released in stacking order\n\
186 released {:<35} locked at {:?}\n\
187 when not expecting any locks to be held\n",
188 prior_rank.bit.member_name(),
189 prior_location,
190 );
191 }
192 (0, Some(_)) => {
193 panic!("Found previous lock acquisition information, but saved.depth = 0");
194 }
195 (saved_depth, Some((saved_rank, saved_location))) => {
196 assert_eq!(
197 prior.depth,
198 saved_depth + 1,
199 "Lock not released in stacking order\n\
200 expecting release of {:<35} locked at {:?}\n\
201 but instead released {:<35} locked at {:?}\n",
202 saved_rank.bit.member_name(),
203 saved_location,
204 prior_rank.bit.member_name(),
205 prior_location,
206 );
207 }
208 (saved_depth, None) => {
209 panic!(
210 "Found saved.depth = {saved_depth}, but no previous lock acquisition information"
211 );
212 }
213 }
214}
215
216impl<T> Mutex<T> {
217 pub fn new(rank: LockRank, value: T) -> Mutex<T> {
218 Mutex {
219 inner: wgpu_sync::Mutex::new(value),
220 rank,
221 }
222 }
223
224 #[track_caller]
225 pub fn lock(&self) -> MutexGuard<'_, T> {
226 let saved = acquire(self.rank, Location::caller());
227 MutexGuard {
228 inner: self.inner.lock(),
229 saved: LockStateGuard(saved),
230 }
231 }
232
233 pub fn get_mut(&mut self) -> &mut T {
234 self.inner.get_mut()
235 }
236
237 pub fn into_inner(self) -> T {
238 self.inner.into_inner()
239 }
240}
241
242impl<'a, T> ops::Deref for MutexGuard<'a, T> {
243 type Target = T;
244
245 fn deref(&self) -> &Self::Target {
246 self.inner.deref()
247 }
248}
249
250impl<'a, T> ops::DerefMut for MutexGuard<'a, T> {
251 fn deref_mut(&mut self) -> &mut Self::Target {
252 self.inner.deref_mut()
253 }
254}
255
256impl<T: fmt::Debug> fmt::Debug for Mutex<T> {
257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 self.inner.fmt(f)
259 }
260}
261
262/// An `RwLock` instrumented for deadlock prevention.
263///
264/// This is just a wrapper around a [`wgpu_sync::RwLock`], along with
265/// its rank in the `wgpu_core` lock ordering.
266///
267/// For details, see [the module documentation][self].
268pub struct RwLock<T> {
269 inner: wgpu_sync::RwLock<T>,
270 rank: LockRank,
271}
272
273/// A read guard produced by locking [`RwLock`] for reading.
274///
275/// This is just a wrapper around a [`wgpu_sync::RwLockReadGuard`], along with
276/// the state needed to track lock acquisition.
277///
278/// For details, see [the module documentation][self].
279pub struct RwLockReadGuard<'a, T> {
280 inner: wgpu_sync::RwLockReadGuard<'a, T>,
281 saved: LockStateGuard,
282}
283
284/// A write guard produced by locking [`RwLock`] for writing.
285///
286/// This is just a wrapper around a [`wgpu_sync::RwLockWriteGuard`], along
287/// with the state needed to track lock acquisition.
288///
289/// For details, see [the module documentation][self].
290pub struct RwLockWriteGuard<'a, T> {
291 inner: wgpu_sync::RwLockWriteGuard<'a, T>,
292 #[cfg_attr(not(miri), expect(unused))] // but `Drop` has important side effects
293 saved: LockStateGuard,
294}
295
296impl<T> RwLock<T> {
297 pub fn new(rank: LockRank, value: T) -> RwLock<T> {
298 RwLock {
299 inner: wgpu_sync::RwLock::new(value),
300 rank,
301 }
302 }
303
304 #[track_caller]
305 pub fn read(&self) -> RwLockReadGuard<'_, T> {
306 let saved = acquire(self.rank, Location::caller());
307 RwLockReadGuard {
308 inner: self.inner.read(),
309 saved: LockStateGuard(saved),
310 }
311 }
312
313 #[track_caller]
314 pub fn write(&self) -> RwLockWriteGuard<'_, T> {
315 let saved = acquire(self.rank, Location::caller());
316 RwLockWriteGuard {
317 inner: self.inner.write(),
318 saved: LockStateGuard(saved),
319 }
320 }
321
322 /// Force an read-unlock operation on this lock.
323 ///
324 /// Safety:
325 /// - A read lock must be held which is not held by a guard.
326 pub unsafe fn force_unlock_read(&self, data: RankData) {
327 release(data);
328 unsafe { self.inner.force_unlock_read() };
329 }
330}
331
332impl<'a, T> RwLockReadGuard<'a, T> {
333 // Forget the read guard, leaving the lock in a locked state with no guard.
334 //
335 // Equivalent to std::mem::forget, but preserves the information about the lock
336 // rank.
337 pub fn forget(this: Self) -> RankData {
338 // Skip `Drop` for both the actual lock guard (`this.inner`) and the
339 // rank-checking state guard (`this.saved`)
340 let saved = core::mem::ManuallyDrop::new(this.saved);
341 core::mem::forget(this.inner);
342
343 // Return the `RankData` so the caller can pass it to `force_unlock_read`.
344 saved.0
345 }
346}
347
348impl<T: fmt::Debug> fmt::Debug for RwLock<T> {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 self.inner.fmt(f)
351 }
352}
353
354impl<'a, T> ops::Deref for RwLockReadGuard<'a, T> {
355 type Target = T;
356
357 fn deref(&self) -> &Self::Target {
358 self.inner.deref()
359 }
360}
361
362impl<'a, T> ops::Deref for RwLockWriteGuard<'a, T> {
363 type Target = T;
364
365 fn deref(&self) -> &Self::Target {
366 self.inner.deref()
367 }
368}
369
370impl<'a, T> ops::DerefMut for RwLockWriteGuard<'a, T> {
371 fn deref_mut(&mut self) -> &mut Self::Target {
372 self.inner.deref_mut()
373 }
374}
375
376/// Locks can be acquired in the order indicated by their ranks.
377#[test]
378fn permitted() {
379 use super::rank;
380
381 let lock1 = Mutex::new(rank::PAWN, ());
382 let lock2 = Mutex::new(rank::ROOK, ());
383
384 let _guard1 = lock1.lock();
385 let _guard2 = lock2.lock();
386}
387
388/// Locks can only be acquired in the order indicated by their ranks.
389#[test]
390#[should_panic(expected = "Locking pawn after locking rook")]
391fn forbidden_unrelated() {
392 use super::rank;
393
394 let lock1 = Mutex::new(rank::ROOK, ());
395 let lock2 = Mutex::new(rank::PAWN, ());
396
397 let _guard1 = lock1.lock();
398 let _guard2 = lock2.lock();
399}
400
401/// Lock acquisitions can't skip ranks.
402///
403/// These two locks *could* be acquired in this order, but only if other locks
404/// are acquired in between them. Skipping ranks isn't allowed.
405#[test]
406#[should_panic(expected = "Locking knight after locking pawn")]
407fn forbidden_skip() {
408 use super::rank;
409
410 let lock1 = Mutex::new(rank::PAWN, ());
411 let lock2 = Mutex::new(rank::KNIGHT, ());
412
413 let _guard1 = lock1.lock();
414 let _guard2 = lock2.lock();
415}
416
417/// Locks can be acquired and released in a stack-like order.
418#[test]
419fn stack_like() {
420 use super::rank;
421
422 let lock1 = Mutex::new(rank::PAWN, ());
423 let lock2 = Mutex::new(rank::ROOK, ());
424 let lock3 = Mutex::new(rank::BISHOP, ());
425
426 let guard1 = lock1.lock();
427 let guard2 = lock2.lock();
428 drop(guard2);
429
430 let guard3 = lock3.lock();
431 drop(guard3);
432 drop(guard1);
433}
434
435/// Locks can only be acquired and released in a stack-like order.
436#[test]
437#[should_panic(expected = "Lock not released in stacking order")]
438fn non_stack_like() {
439 use super::rank;
440
441 let lock1 = Mutex::new(rank::PAWN, ());
442 let lock2 = Mutex::new(rank::ROOK, ());
443
444 let guard1 = lock1.lock();
445 let guard2 = lock2.lock();
446
447 // Avoid a double panic from dropping this while unwinding due to the panic
448 // we're testing for.
449 core::mem::forget(guard2);
450
451 drop(guard1);
452}