1use core::cell::UnsafeCell;
2use core::marker::PhantomData;
3use core::ops::{Deref, DerefMut};
4#[cfg(panic = "unwind")]
5use core::sync::atomic::{AtomicBool, Ordering};
6use std::sync::{LockResult, PoisonError};
7#[cfg(panic = "unwind")]
8use std::thread;
9
10struct Flag {
14 #[cfg(panic = "unwind")]
15 failed: AtomicBool,
16}
17
18impl Flag {
19 #[inline]
20 const fn new() -> Flag {
21 Flag {
22 #[cfg(panic = "unwind")]
23 failed: AtomicBool::new(false),
24 }
25 }
26
27 #[inline]
29 fn borrow(&self) -> LockResult<()> {
30 if self.get() {
31 Err(PoisonError::new(()))
32 } else {
33 Ok(())
34 }
35 }
36
37 #[inline]
39 fn guard(&self) -> LockResult<Guard> {
40 let ret = Guard {
41 #[cfg(panic = "unwind")]
42 panicking: thread::panicking(),
43 };
44 if self.get() {
45 Err(PoisonError::new(ret))
46 } else {
47 Ok(ret)
48 }
49 }
50
51 #[inline]
52 #[cfg(panic = "unwind")]
53 fn done(&self, guard: &Guard) {
54 if !guard.panicking && thread::panicking() {
55 self.failed.store(true, Ordering::Relaxed);
56 }
57 }
58
59 #[inline]
60 #[cfg(not(panic = "unwind"))]
61 fn done(&self, _guard: &Guard) {}
62
63 #[inline]
64 #[cfg(panic = "unwind")]
65 fn get(&self) -> bool {
66 self.failed.load(Ordering::Relaxed)
67 }
68
69 #[inline(always)]
70 #[cfg(not(panic = "unwind"))]
71 fn get(&self) -> bool {
72 false
73 }
74
75 #[inline]
76 fn clear(&self) {
77 #[cfg(panic = "unwind")]
78 self.failed.store(false, Ordering::Relaxed)
79 }
80}
81
82#[derive(Clone)]
83pub(crate) struct Guard {
84 #[cfg(panic = "unwind")]
85 panicking: bool,
86}
87
88pub struct PyMutex<T: ?Sized> {
120 pub(crate) mutex: UnsafeCell<crate::ffi::PyMutex>,
121 poison: Flag,
122 pub(crate) data: UnsafeCell<T>,
123}
124
125pub struct PyMutexGuard<'a, T: ?Sized> {
129 inner: &'a PyMutex<T>,
130 poison: Guard,
131 _phantom: PhantomData<*const ()>,
134}
135
136unsafe impl<T: ?Sized + Sync> Sync for PyMutexGuard<'_, T> {}
139
140unsafe impl<T: ?Sized + Send> Send for PyMutex<T> {}
145
146unsafe impl<T: ?Sized + Send> Sync for PyMutex<T> {}
163
164impl<T> PyMutex<T> {
165 pub fn lock(&self) -> LockResult<PyMutexGuard<'_, T>> {
167 unsafe { crate::ffi::PyMutex_Lock(UnsafeCell::raw_get(&self.mutex)) };
168 PyMutexGuard::new(self)
169 }
170
171 pub const fn new(value: T) -> Self {
173 Self {
174 mutex: UnsafeCell::new(crate::ffi::PyMutex::new()),
175 data: UnsafeCell::new(value),
176 poison: Flag::new(),
177 }
178 }
179
180 #[cfg(Py_3_14)]
186 pub fn is_locked(&self) -> bool {
187 let ret = unsafe { crate::ffi::PyMutex_IsLocked(UnsafeCell::raw_get(&self.mutex)) };
188 ret != 0
189 }
190
191 pub fn into_inner(self) -> LockResult<T>
199 where
200 T: Sized,
201 {
202 let data = self.data.into_inner();
203 map_result(self.poison.borrow(), |()| data)
204 }
205
206 pub fn clear_poison(&self) {
214 self.poison.clear();
215 }
216}
217
218#[cfg_attr(not(panic = "unwind"), allow(clippy::unnecessary_wraps))]
219fn map_result<T, U, F>(result: LockResult<T>, f: F) -> LockResult<U>
220where
221 F: FnOnce(T) -> U,
222{
223 match result {
224 Ok(t) => Ok(f(t)),
225 #[cfg(panic = "unwind")]
226 Err(e) => Err(PoisonError::new(f(e.into_inner()))),
227 #[cfg(not(panic = "unwind"))]
228 Err(_) => {
229 unreachable!();
230 }
231 }
232}
233
234impl<'mutex, T: ?Sized> PyMutexGuard<'mutex, T> {
235 fn new(lock: &'mutex PyMutex<T>) -> LockResult<PyMutexGuard<'mutex, T>> {
236 map_result(lock.poison.guard(), |guard| PyMutexGuard {
237 inner: lock,
238 poison: guard,
239 _phantom: PhantomData,
240 })
241 }
242}
243
244impl<'a, T: ?Sized> Drop for PyMutexGuard<'a, T> {
245 fn drop(&mut self) {
246 unsafe {
247 self.inner.poison.done(&self.poison);
248 crate::ffi::PyMutex_Unlock(UnsafeCell::raw_get(&self.inner.mutex))
249 };
250 }
251}
252
253impl<'a, T> Deref for PyMutexGuard<'a, T> {
254 type Target = T;
255
256 fn deref(&self) -> &T {
257 unsafe { &*self.inner.data.get() }
260 }
261}
262
263impl<'a, T> DerefMut for PyMutexGuard<'a, T> {
264 fn deref_mut(&mut self) -> &mut T {
265 unsafe { &mut *self.inner.data.get() }
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 #[cfg(not(target_arch = "wasm32"))]
274 use alloc::sync::Arc;
275 #[cfg(not(target_arch = "wasm32"))]
276 use core::sync::atomic::{AtomicBool, Ordering};
277 #[cfg(not(target_arch = "wasm32"))]
278 use std::sync::Barrier;
279
280 use super::*;
281 #[cfg(not(target_arch = "wasm32"))]
282 use crate::types::{PyAnyMethods, PyDict, PyDictMethods, PyNone};
283 #[cfg(not(target_arch = "wasm32"))]
284 use crate::Py;
285 #[cfg(not(target_arch = "wasm32"))]
286 use crate::Python;
287
288 #[cfg(not(target_arch = "wasm32"))]
289 #[test]
290 fn test_pymutex() {
291 let mutex = Python::attach(|py| -> PyMutex<Py<PyDict>> {
292 let d = PyDict::new(py);
293 PyMutex::new(d.unbind())
294 });
295 #[cfg_attr(not(Py_3_14), allow(unused_variables))]
296 let mutex = Python::attach(|py| {
297 let mutex = py.detach(|| -> PyMutex<Py<PyDict>> {
298 std::thread::spawn(|| {
299 let dict_guard = mutex.lock().unwrap();
300 Python::attach(|py| {
301 let dict = dict_guard.bind(py);
302 dict.set_item(PyNone::get(py), PyNone::get(py)).unwrap();
303 });
304 #[cfg(Py_3_14)]
305 assert!(mutex.is_locked());
306 drop(dict_guard);
307 #[cfg(Py_3_14)]
308 assert!(!mutex.is_locked());
309 mutex
310 })
311 .join()
312 .unwrap()
313 });
314
315 let dict_guard = mutex.lock().unwrap();
316 #[cfg(Py_3_14)]
317 assert!(mutex.is_locked());
318 let d = dict_guard.bind(py);
319
320 assert!(d
321 .get_item(PyNone::get(py))
322 .unwrap()
323 .unwrap()
324 .eq(PyNone::get(py))
325 .unwrap());
326 #[cfg(Py_3_14)]
327 assert!(mutex.is_locked());
328 drop(dict_guard);
329 #[cfg(Py_3_14)]
330 assert!(!mutex.is_locked());
331 mutex
332 });
333 #[cfg(Py_3_14)]
334 assert!(!mutex.is_locked());
335 }
336
337 #[cfg(not(target_arch = "wasm32"))]
338 #[test]
339 fn test_pymutex_blocks() {
340 let mutex = PyMutex::new(());
341 let first_thread_locked_once = AtomicBool::new(false);
342 let second_thread_locked_once = AtomicBool::new(false);
343 let finished = AtomicBool::new(false);
344 let barrier = Barrier::new(2);
345
346 std::thread::scope(|s| {
347 s.spawn(|| {
348 let guard = mutex.lock();
349 first_thread_locked_once.store(true, Ordering::SeqCst);
350 while !finished.load(Ordering::SeqCst) {
351 if second_thread_locked_once.load(Ordering::SeqCst) {
352 std::thread::sleep(core::time::Duration::from_millis(10));
357 barrier.wait();
359 finished.store(true, Ordering::SeqCst);
360 }
361 }
362 drop(guard);
363 });
364
365 s.spawn(|| {
366 while !first_thread_locked_once.load(Ordering::SeqCst) {
367 core::hint::spin_loop();
368 }
369 second_thread_locked_once.store(true, Ordering::SeqCst);
370 let guard = mutex.lock();
371 assert!(finished.load(Ordering::SeqCst));
372 drop(guard);
373 });
374
375 barrier.wait();
376 });
377 }
378
379 #[cfg(not(target_arch = "wasm32"))]
380 #[test]
381 fn test_recover_poison() {
382 let mutex = Python::attach(|py| -> PyMutex<Py<PyDict>> {
383 let d = PyDict::new(py);
384 d.set_item("hello", "world").unwrap();
385 PyMutex::new(d.unbind())
386 });
387
388 let lock = Arc::new(mutex);
389 let lock2 = Arc::clone(&lock);
390
391 let _ = thread::spawn(move || {
392 let _guard = lock2.lock().unwrap();
393
394 panic!();
396 })
397 .join();
398
399 let guard = match lock.lock() {
401 Ok(_) => {
402 unreachable!();
403 }
404 Err(poisoned) => poisoned.into_inner(),
405 };
406
407 Python::attach(|py| {
408 assert!(
409 (*guard)
410 .bind(py)
411 .get_item("hello")
412 .unwrap()
413 .unwrap()
414 .extract::<&str>()
415 .unwrap()
416 == "world"
417 );
418 });
419
420 let mutex = PyMutex::new(0);
422 assert_eq!(mutex.into_inner().unwrap(), 0);
423
424 let mutex = PyMutex::new(0);
425 let _ = std::thread::scope(|s| {
426 s.spawn(|| {
427 let _guard = mutex.lock().unwrap();
428
429 panic!();
431 })
432 .join()
433 });
434
435 match mutex.into_inner() {
436 Ok(_) => {
437 unreachable!()
438 }
439 Err(e) => {
440 assert!(e.into_inner() == 0)
441 }
442 }
443
444 let mutex = PyMutex::new(0);
446 let _ = std::thread::scope(|s| {
447 s.spawn(|| {
448 let _guard = mutex.lock().unwrap();
449
450 panic!();
452 })
453 .join()
454 });
455 mutex.clear_poison();
456 assert_eq!(*mutex.lock().unwrap(), 0);
457 }
458
459 #[test]
460 fn test_send_not_send() {
461 use crate::impl_::pyclass::{value_of, IsSend, IsSync};
462
463 assert!(!value_of!(IsSend, PyMutexGuard<'_, i32>));
464 assert!(value_of!(IsSync, PyMutexGuard<'_, i32>));
465
466 assert!(value_of!(IsSend, PyMutex<i32>));
467 assert!(value_of!(IsSync, PyMutex<i32>));
468 }
469}