wgpu_sync/mutex.rs
1cfg_if::cfg_if! {
2 if #[cfg(feature = "std")] {
3 type RawMutexInner = parking_lot::RawMutex;
4 } else {
5 type RawMutexInner = core::cell::Cell<bool>;
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}
19
20/// Raw implementation for a [`lock_api::Mutex`].
21///
22/// This will delegate to [`parking_lot`] if the `std` feature is enabled (which
23/// it is by default). Otherwise, it will provide a `!Sync` implementation
24/// similar to [`RefCell`].
25///
26/// [`parking_lot`]: https://docs.rs/parking_lot/
27/// [`RefCell`]: core::cell::RefCell
28pub struct RawMutex(RawMutexInner);
29
30impl RawMutex {
31 /// Constructs a new [`RawMutex`].
32 pub const fn new() -> Self {
33 Self({
34 cfg_if::cfg_if! {
35 if #[cfg(feature = "std")] {
36 lock_api::RawMutex::INIT
37 } else {
38 RawMutexInner::new(false)
39 }
40 }
41 })
42 }
43}
44
45impl Default for RawMutex {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51impl core::fmt::Debug for RawMutex {
52 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
53 f.debug_tuple("RawMutex").finish_non_exhaustive()
54 }
55}
56
57// SAFETY:
58//
59// # With `std`
60//
61// This implementation directly delegates to an existing implementation of
62// `RawMutex`, and is therefore safe.
63//
64// # Without `std`
65//
66// This implementation tracks the state of the mutex in a boolean, where `false`
67// indicates it is unlocked, and `true` indicates it is locked. `is_locked`
68// directly returns this state, and only `try_lock` and `unlock` are able to
69// modify it. Both methods ensure the state of the lock is sound.
70unsafe impl lock_api::RawMutex for RawMutex {
71 type GuardMarker = lock_api::GuardNoSend;
72
73 const INIT: RawMutex = RawMutex::new();
74
75 #[inline]
76 fn lock(&self) {
77 cfg_if::cfg_if! {
78 if #[cfg(feature = "std")] {
79 lock_api::RawMutex::lock(&self.0)
80 } else {
81 if !self.try_lock() {
82 // Since this "mutex" is `!Sync`, any attempt to lock it twice
83 // must be from the same thread, which means a deadlock.
84 deadlock()
85 }
86 }
87 }
88 }
89
90 #[inline]
91 fn try_lock(&self) -> bool {
92 cfg_if::cfg_if! {
93 if #[cfg(feature = "std")] {
94 lock_api::RawMutex::try_lock(&self.0)
95 } else {
96 !self.0.replace(true)
97 }
98 }
99 }
100
101 #[inline]
102 unsafe fn unlock(&self) {
103 cfg_if::cfg_if! {
104 if #[cfg(feature = "std")] {
105 // SAFETY: directly delegating to an accepted implementation
106 unsafe { lock_api::RawMutex::unlock(&self.0) }
107 } else {
108 self.0.set(false);
109 }
110 }
111 }
112
113 #[inline]
114 fn is_locked(&self) -> bool {
115 cfg_if::cfg_if! {
116 if #[cfg(feature = "std")] {
117 lock_api::RawMutex::is_locked(&self.0)
118 } else {
119 self.0.get()
120 }
121 }
122 }
123}
124
125// SAFETY:
126//
127// # With `std`
128//
129// This implementation directly delegates to an existing implementation of
130// `RawMutexTimed`, and is therefore safe.
131#[cfg(feature = "std")]
132unsafe impl lock_api::RawMutexTimed for RawMutex {
133 type Duration = core::time::Duration;
134 type Instant = <RawMutexInner as lock_api::RawMutexTimed>::Instant;
135
136 fn try_lock_for(&self, timeout: Self::Duration) -> bool {
137 lock_api::RawMutexTimed::try_lock_for(&self.0, timeout)
138 }
139
140 fn try_lock_until(&self, timeout: Self::Instant) -> bool {
141 lock_api::RawMutexTimed::try_lock_until(&self.0, timeout)
142 }
143}