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}