Skip to content

Commit a6a3804

Browse files
committed
[core] Add force_unlock_read methods to lock primitives
1 parent a248b8c commit a6a3804

File tree

5 files changed

+106
-8
lines changed

5 files changed

+106
-8
lines changed

wgpu-core/src/lock/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,4 @@ use observing as chosen;
5252
#[cfg(not(any(wgpu_validate_locks, feature = "observe_locks")))]
5353
use vanilla as chosen;
5454

55-
pub use chosen::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
55+
pub use chosen::{Mutex, MutexGuard, RankData, RwLock, RwLockReadGuard, RwLockWriteGuard};

wgpu-core/src/lock/observing.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ use std::{
3838
use super::rank::{LockRank, LockRankSet};
3939
use crate::FastHashSet;
4040

41+
pub type RankData = Option<HeldLock>;
42+
4143
/// A `Mutex` instrumented for lock acquisition order observation.
4244
///
4345
/// This is just a wrapper around a [`parking_lot::Mutex`], along with
@@ -160,6 +162,27 @@ impl<T> RwLock<T> {
160162
_state: LockStateGuard { saved },
161163
}
162164
}
165+
166+
/// Force an read-unlock operation on this lock.
167+
///
168+
/// Safety:
169+
/// - A read lock must be held which is not held by a guard.
170+
pub unsafe fn force_unlock_read(&self, data: RankData) {
171+
release(data);
172+
unsafe { self.inner.force_unlock_read() };
173+
}
174+
}
175+
176+
impl<'a, T> RwLockReadGuard<'a, T> {
177+
// Forget the read guard, leaving the lock in a locked state with no guard.
178+
//
179+
// Equivalent to std::mem::forget, but preserves the information about the lock
180+
// rank.
181+
pub fn forget(this: Self) -> RankData {
182+
core::mem::forget(this.inner);
183+
184+
this._state.saved
185+
}
163186
}
164187

165188
impl<'a, T> RwLockWriteGuard<'a, T> {
@@ -316,7 +339,7 @@ enum ThreadState {
316339

317340
/// Information about a currently held lock.
318341
#[derive(Debug, Copy, Clone)]
319-
struct HeldLock {
342+
pub struct HeldLock {
320343
/// The lock's rank.
321344
rank: LockRank,
322345

wgpu-core/src/lock/ranked.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ use core::{cell::Cell, fmt, ops, panic::Location};
5959

6060
use super::rank::LockRank;
6161

62+
pub use LockState as RankData;
63+
6264
/// A `Mutex` instrumented for deadlock prevention.
6365
///
6466
/// This is just a wrapper around a [`parking_lot::Mutex`], along with
@@ -87,7 +89,7 @@ std::thread_local! {
8789

8890
/// Per-thread state for the deadlock checker.
8991
#[derive(Debug, Copy, Clone)]
90-
struct LockState {
92+
pub struct LockState {
9193
/// The last lock we acquired, and where.
9294
last_acquired: Option<(LockRank, &'static Location<'static>)>,
9395

@@ -270,6 +272,27 @@ impl<T> RwLock<T> {
270272
saved: LockStateGuard(saved),
271273
}
272274
}
275+
276+
/// Force an read-unlock operation on this lock.
277+
///
278+
/// Safety:
279+
/// - A read lock must be held which is not held by a guard.
280+
pub unsafe fn force_unlock_read(&self, data: RankData) {
281+
release(data);
282+
unsafe { self.inner.force_unlock_read() };
283+
}
284+
}
285+
286+
impl<'a, T> RwLockReadGuard<'a, T> {
287+
// Forget the read guard, leaving the lock in a locked state with no guard.
288+
//
289+
// Equivalent to std::mem::forget, but preserves the information about the lock
290+
// rank.
291+
pub fn forget(this: Self) -> RankData {
292+
core::mem::forget(this.inner);
293+
294+
this.saved.0
295+
}
273296
}
274297

275298
impl<'a, T> RwLockWriteGuard<'a, T> {

wgpu-core/src/lock/vanilla.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
66
use core::{fmt, ops};
77

8+
use crate::lock::rank::LockRank;
9+
10+
pub struct RankData;
11+
812
/// A plain wrapper around [`parking_lot::Mutex`].
913
///
1014
/// This is just like [`parking_lot::Mutex`], except that our [`new`]
@@ -23,7 +27,7 @@ pub struct Mutex<T>(parking_lot::Mutex<T>);
2327
pub struct MutexGuard<'a, T>(parking_lot::MutexGuard<'a, T>);
2428

2529
impl<T> Mutex<T> {
26-
pub fn new(_rank: super::rank::LockRank, value: T) -> Mutex<T> {
30+
pub fn new(_rank: LockRank, value: T) -> Mutex<T> {
2731
Mutex(parking_lot::Mutex::new(value))
2832
}
2933

@@ -79,7 +83,7 @@ pub struct RwLockReadGuard<'a, T>(parking_lot::RwLockReadGuard<'a, T>);
7983
pub struct RwLockWriteGuard<'a, T>(parking_lot::RwLockWriteGuard<'a, T>);
8084

8185
impl<T> RwLock<T> {
82-
pub fn new(_rank: super::rank::LockRank, value: T) -> RwLock<T> {
86+
pub fn new(_rank: LockRank, value: T) -> RwLock<T> {
8387
RwLock(parking_lot::RwLock::new(value))
8488
}
8589

@@ -90,6 +94,26 @@ impl<T> RwLock<T> {
9094
pub fn write(&self) -> RwLockWriteGuard<T> {
9195
RwLockWriteGuard(self.0.write())
9296
}
97+
98+
/// Force an read-unlock operation on this lock.
99+
///
100+
/// Safety:
101+
/// - A read lock must be held which is not held by a guard.
102+
pub unsafe fn force_unlock_read(&self, _data: RankData) {
103+
unsafe { self.0.force_unlock_read() };
104+
}
105+
}
106+
107+
impl<'a, T> RwLockReadGuard<'a, T> {
108+
// Forget the read guard, leaving the lock in a locked state with no guard.
109+
//
110+
// Equivalent to std::mem::forget, but preserves the information about the lock
111+
// rank.
112+
pub fn forget(this: Self) -> RankData {
113+
core::mem::forget(this.0);
114+
115+
RankData
116+
}
93117
}
94118

95119
impl<'a, T> RwLockWriteGuard<'a, T> {

wgpu-core/src/snatch.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use core::{cell::UnsafeCell, fmt};
1+
use core::{cell::UnsafeCell, fmt, mem::ManuallyDrop};
22

3-
use crate::lock::{rank, RwLock, RwLockReadGuard, RwLockWriteGuard};
3+
use crate::lock::{rank, RankData, RwLock, RwLockReadGuard, RwLockWriteGuard};
44

55
/// A guard that provides read access to snatchable data.
6-
pub struct SnatchGuard<'a>(#[expect(dead_code)] RwLockReadGuard<'a, ()>);
6+
pub struct SnatchGuard<'a>(RwLockReadGuard<'a, ()>);
77
/// A guard that allows snatching the snatchable data.
88
pub struct ExclusiveSnatchGuard<'a>(#[expect(dead_code)] RwLockWriteGuard<'a, ()>);
99

@@ -158,6 +158,34 @@ impl SnatchLock {
158158
LockTrace::enter("write");
159159
ExclusiveSnatchGuard(self.lock.write())
160160
}
161+
162+
#[track_caller]
163+
#[expect(unused)]
164+
pub unsafe fn force_unlock_read(&self, data: RankData) {
165+
// This is unsafe because it can cause deadlocks if the lock is held.
166+
// It should only be used in very specific cases, like when a resource
167+
// needs to be snatched in a panic handler.
168+
LockTrace::exit();
169+
unsafe { self.lock.force_unlock_read(data) };
170+
}
171+
}
172+
173+
impl SnatchGuard<'_> {
174+
/// Forget the guard, leaving the lock in a locked state with no guard.
175+
///
176+
/// This is equivalent to `std::mem::forget`, but preserves the information about the lock
177+
/// rank.
178+
pub fn forget(this: Self) -> RankData {
179+
// Cancel the drop implementation of the current guard.
180+
let manually_drop = ManuallyDrop::new(this);
181+
182+
// As we are unable to destructure out of this guard due to the drop implementation,
183+
// so we manually read the inner value.
184+
// SAFETY: This is safe because we never access the original guard again.
185+
let inner_guard = unsafe { core::ptr::read(&manually_drop.0) };
186+
187+
RwLockReadGuard::forget(inner_guard)
188+
}
161189
}
162190

163191
impl Drop for SnatchGuard<'_> {

0 commit comments

Comments
 (0)