1use core::{cell::UnsafeCell, fmt, mem::ManuallyDrop};
2
3use crate::lock::{rank, RankData, RwLock, RwLockReadGuard, RwLockWriteGuard};
4
5pub struct SnatchGuard<'a>(RwLockReadGuard<'a, ()>);
7pub struct ExclusiveSnatchGuard<'a>(#[expect(dead_code)] RwLockWriteGuard<'a, ()>);
9
10pub struct SnatchableInner<T> {
17 value: UnsafeCell<T>,
18}
19
20pub type Snatchable<T> = SnatchableInner<Option<T>>;
21
22impl<T> Snatchable<T> {
23 pub fn new(val: T) -> Self {
24 SnatchableInner {
25 value: UnsafeCell::new(Some(val)),
26 }
27 }
28
29 #[allow(dead_code)]
30 pub fn empty() -> Self {
31 SnatchableInner {
32 value: UnsafeCell::new(None),
33 }
34 }
35
36 pub fn get<'a>(&'a self, _guard: &'a SnatchGuard) -> Option<&'a T> {
38 unsafe { (*self.value.get()).as_ref() }
39 }
40
41 pub fn snatch(&self, _guard: &mut ExclusiveSnatchGuard) -> Option<T> {
43 unsafe { (*self.value.get()).take() }
44 }
45
46 pub fn take(&mut self) -> Option<T> {
51 self.value.get_mut().take()
52 }
53}
54
55impl<T> fmt::Debug for SnatchableInner<T> {
58 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
59 write!(f, "<snatchable>")
60 }
61}
62
63unsafe impl<T> Sync for SnatchableInner<T> {}
64
65use trace::LockTrace;
66#[cfg(all(debug_assertions, feature = "std"))]
67mod trace {
68 use core::{cell::Cell, fmt, panic::Location};
69 use std::{backtrace::Backtrace, thread};
70
71 pub(super) struct LockTrace {
72 purpose: &'static str,
73 caller: &'static Location<'static>,
74 backtrace: Backtrace,
75 }
76
77 impl fmt::Display for LockTrace {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 write!(
80 f,
81 "a {} lock at {}\n{}",
82 self.purpose, self.caller, self.backtrace
83 )
84 }
85 }
86
87 impl LockTrace {
88 #[track_caller]
89 pub(super) fn enter(purpose: &'static str) {
90 let new = LockTrace {
91 purpose,
92 caller: Location::caller(),
93 backtrace: Backtrace::capture(),
94 };
95
96 let _ = SNATCH_LOCK_TRACE.try_with(|trace| {
99 if let Some(prev) = trace.take() {
100 let current = thread::current();
101 let name = current.name().unwrap_or("<unnamed>");
102 panic!(
103 "thread '{name}' attempted to acquire a snatch lock recursively.\n\
104 - Currently trying to acquire {new}\n\
105 - Previously acquired {prev}",
106 );
107 } else {
108 trace.set(Some(new));
109 }
110 });
111 }
112
113 pub(super) fn exit() {
114 let _ = SNATCH_LOCK_TRACE.try_with(|trace| trace.take());
115 }
116 }
117
118 std::thread_local! {
119 static SNATCH_LOCK_TRACE: Cell<Option<LockTrace>> = const { Cell::new(None) };
120 }
121}
122#[cfg(not(all(debug_assertions, feature = "std")))]
123mod trace {
124 pub(super) struct LockTrace {
125 _private: (),
126 }
127
128 impl LockTrace {
129 pub(super) fn enter(_purpose: &'static str) {}
130 pub(super) fn exit() {}
131 }
132}
133
134pub struct SnatchLock {
136 lock: RwLock<()>,
137}
138
139impl SnatchLock {
140 pub unsafe fn new(rank: rank::LockRank) -> Self {
145 SnatchLock {
146 lock: RwLock::new(rank, ()),
147 }
148 }
149
150 #[track_caller]
152 pub fn read(&self) -> SnatchGuard<'_> {
153 LockTrace::enter("read");
154 SnatchGuard(self.lock.read())
155 }
156
157 #[track_caller]
163 pub fn write(&self) -> ExclusiveSnatchGuard<'_> {
164 LockTrace::enter("write");
165 ExclusiveSnatchGuard(self.lock.write())
166 }
167
168 #[track_caller]
169 pub unsafe fn force_unlock_read(&self, data: RankData) {
170 LockTrace::exit();
174 unsafe { self.lock.force_unlock_read(data) };
175 }
176}
177
178impl SnatchGuard<'_> {
179 pub fn forget(this: Self) -> RankData {
184 let manually_drop = ManuallyDrop::new(this);
186
187 let inner_guard = unsafe { core::ptr::read(&manually_drop.0) };
191
192 RwLockReadGuard::forget(inner_guard)
193 }
194}
195
196impl Drop for SnatchGuard<'_> {
197 fn drop(&mut self) {
198 LockTrace::exit();
199 }
200}
201
202impl Drop for ExclusiveSnatchGuard<'_> {
203 fn drop(&mut self) {
204 LockTrace::exit();
205 }
206}
207
208#[cfg(all(test, feature = "std"))]
209mod tests {
210 use super::SnatchLock;
211 use crate::lock::rank;
212 use core::cell::Cell;
213
214 struct LockOnDrop(SnatchLock);
216 impl Drop for LockOnDrop {
217 fn drop(&mut self) {
218 drop(self.0.read());
219 }
220 }
221
222 std::thread_local! {
223 static HOLDER: Cell<Option<LockOnDrop>> = const { Cell::new(None) };
224 }
225
226 #[test]
227 fn read_during_tls_destruction() {
228 std::thread::spawn(|| {
229 HOLDER.set(Some(LockOnDrop(unsafe {
231 SnatchLock::new(rank::DEVICE_SNATCHABLE_LOCK)
232 })));
233
234 let lock = unsafe { SnatchLock::new(rank::DEVICE_SNATCHABLE_LOCK) };
237 drop(lock.read());
238 })
239 .join()
240 .unwrap();
241 }
243}