wgpu_sync/rwlock.rs
1cfg_if::cfg_if! {
2 if #[cfg(feature = "std")] {
3 type RawRwLockInner = parking_lot::RawRwLock;
4 } else {
5 type RawRwLockInner = core::cell::Cell<isize>;
6
7 /// When a `no_std` locking primitive is under contention, the "correct" way to
8 /// handle it would be to spin until the lock is available. This is because
9 /// without `std` there is no standard way to yield/block the current thread.
10 /// However, since we only support `no_std` locks that aren't `Sync`, we know
11 /// that only one thread can access the lock at a time. Therefore, we know this
12 /// is actually a deadlock and will never resolve. We choose to panic in these
13 /// cases to highlight what is almost certainly an internal bug.
14 fn deadlock() -> ! {
15 panic!("a locking primitive in wgpu is currently deadlocked");
16 }
17
18 #[repr(isize)]
19 enum BorrowCount {
20 LockedExclusive = -1,
21 Unlocked = 0,
22 SingleLockedShared = 1,
23 }
24 }
25}
26
27/// Raw implementation for a [`lock_api::RwLock`].
28///
29/// This will delegate to [`parking_lot`] if the `std` feature is enabled (which
30/// it is by default). Otherwise, it will provide a `!Sync` implementation
31/// similar to [`RefCell`].
32///
33/// [`parking_lot`]: https://docs.rs/parking_lot/
34/// [`RefCell`]: core::cell::RefCell
35pub struct RawRwLock(RawRwLockInner);
36
37impl RawRwLock {
38 /// Constructs a new [`RawRwLock`].
39 pub const fn new() -> Self {
40 Self({
41 cfg_if::cfg_if! {
42 if #[cfg(feature = "std")] {
43 lock_api::RawRwLock::INIT
44 } else {
45 RawRwLockInner::new(BorrowCount::Unlocked as _)
46 }
47 }
48 })
49 }
50}
51
52impl Default for RawRwLock {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl core::fmt::Debug for RawRwLock {
59 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
60 f.debug_tuple("RawRwLock").finish_non_exhaustive()
61 }
62}
63
64// SAFETY:
65//
66// # With `std`
67//
68// This implementation directly delegates to an existing implementation of
69// `RawRwLock`, and is therefore safe.
70//
71// # Without `std`
72//
73// This implementation tracks the number of borrows using an `isize`, where `-1`
74// indicates a single _exclusive_ lock, and a positive number represents that many
75// shared locks.
76unsafe impl lock_api::RawRwLock for RawRwLock {
77 type GuardMarker = lock_api::GuardNoSend;
78
79 const INIT: RawRwLock = RawRwLock::new();
80
81 #[inline]
82 fn lock_exclusive(&self) {
83 cfg_if::cfg_if! {
84 if #[cfg(feature = "std")] {
85 lock_api::RawRwLock::lock_exclusive(&self.0)
86 } else {
87 if !self.try_lock_exclusive() {
88 // Since this "lock" is `!Sync`, any failing attempt to lock it
89 // must be from the same thread, which means a deadlock.
90 deadlock()
91 }
92 }
93 }
94 }
95
96 #[inline]
97 fn try_lock_exclusive(&self) -> bool {
98 cfg_if::cfg_if! {
99 if #[cfg(feature = "std")] {
100 lock_api::RawRwLock::try_lock_exclusive(&self.0)
101 } else {
102 if self.0.get() != BorrowCount::Unlocked as _ {
103 false
104 } else {
105 self.0.set(BorrowCount::LockedExclusive as _);
106 true
107 }
108 }
109 }
110 }
111
112 #[inline]
113 unsafe fn unlock_exclusive(&self) {
114 cfg_if::cfg_if! {
115 if #[cfg(feature = "std")] {
116 // SAFETY: directly delegating to an accepted implementation
117 unsafe { lock_api::RawRwLock::unlock_exclusive(&self.0) }
118 } else {
119 self.0.set(BorrowCount::Unlocked as _);
120 }
121 }
122 }
123
124 #[inline]
125 fn lock_shared(&self) {
126 cfg_if::cfg_if! {
127 if #[cfg(feature = "std")] {
128 lock_api::RawRwLock::lock_shared(&self.0)
129 } else {
130 if !self.try_lock_shared() {
131 // Since this "lock" is `!Sync`, any failing attempt to lock it
132 // must be from the same thread, which means a deadlock.
133 deadlock()
134 }
135 }
136 }
137 }
138
139 #[inline]
140 fn try_lock_shared(&self) -> bool {
141 cfg_if::cfg_if! {
142 if #[cfg(feature = "std")] {
143 lock_api::RawRwLock::try_lock_shared(&self.0)
144 } else {
145 if self.0.get() == BorrowCount::LockedExclusive as _ {
146 false
147 } else {
148 match self.0.get().checked_add(1) {
149 Some(value) => {
150 self.0.set(value);
151 true
152 }
153 None => {
154 // Instead of panicking, we can simply fail to lock,
155 // preventing the count for overflowing.
156 false
157 }
158 }
159 }
160 }
161 }
162 }
163
164 #[inline]
165 unsafe fn unlock_shared(&self) {
166 cfg_if::cfg_if! {
167 if #[cfg(feature = "std")] {
168 // SAFETY: directly delegating to an accepted implementation
169 unsafe { lock_api::RawRwLock::unlock_shared(&self.0) }
170 } else {
171 match self.0.get().checked_sub(1) {
172 Some(value) => {
173 // It is a safety condition of `RawRwLock::unlock_shared` that the caller
174 // has already determined the lock is held in the shared state (`> 0`).
175 debug_assert!(!value.is_negative(), "caller violated safety condition");
176 self.0.set(value);
177 }
178 None => {
179 unreachable!("lock state should never underflow");
180 }
181 }
182 }
183 }
184 }
185
186 #[inline]
187 fn is_locked(&self) -> bool {
188 cfg_if::cfg_if! {
189 if #[cfg(feature = "std")] {
190 lock_api::RawRwLock::is_locked(&self.0)
191 } else {
192 self.0.get() != BorrowCount::Unlocked as _
193 }
194 }
195 }
196
197 #[inline]
198 fn is_locked_exclusive(&self) -> bool {
199 cfg_if::cfg_if! {
200 if #[cfg(feature = "std")] {
201 lock_api::RawRwLock::is_locked_exclusive(&self.0)
202 } else {
203 self.0.get() == BorrowCount::LockedExclusive as _
204 }
205 }
206 }
207}
208
209// SAFETY:
210//
211// # With `std`
212//
213// This implementation directly delegates to an existing implementation of
214// `RawRwLockDowngrade`, and is therefore safe.
215//
216// # Without `std`
217//
218// It's a safety condition on the caller of `downgrade` that they already have an
219// exclusive lock, so it is sufficient to set the count to `1` to change the state
220// of the reader-writer lock to shared with one reader.
221unsafe impl lock_api::RawRwLockDowngrade for RawRwLock {
222 unsafe fn downgrade(&self) {
223 cfg_if::cfg_if! {
224 if #[cfg(feature = "std")] {
225 // SAFETY: directly delegating to an accepted implementation
226 unsafe { lock_api::RawRwLockDowngrade::downgrade(&self.0) }
227 } else {
228 self.0.set(BorrowCount::SingleLockedShared as _);
229 }
230 }
231 }
232}