pyo3/sync/nonpoison.rs
1//! Contains a non-poisoning variant of [`PyMutex`].
2
3use core::cell::UnsafeCell;
4use core::marker::PhantomData;
5use core::ops::{Deref, DerefMut};
6
7/// Wrapper for [`PyMutex`](https://docs.python.org/3/c-api/init.html#c.PyMutex), exposing an RAII guard interface.
8///
9/// Compared with `std::sync::Mutex` or `parking_lot::Mutex`, this is a very
10/// stripped-down locking primitive that only supports blocking lock and unlock
11/// operations and does not support `try_lock` or APIs that depend on
12/// `try_lock`. For this reason, it is not possible to avoid the possibility of
13/// possibly blocking when calling `lock` and extreme care must be taken to avoid
14/// introducing a deadlock.
15///
16/// This type is most useful when arbitrary Python code might execute while the
17/// lock is held. On the GIL-enabled build, PyMutex will release the GIL if the
18/// thread is blocked on acquiring the lock. On the free-threaded build, threads
19/// blocked on acquiring a PyMutex will not prevent the garbage collector from
20/// running.
21pub struct PyMutex<T: ?Sized> {
22 pub(crate) mutex: UnsafeCell<crate::ffi::PyMutex>,
23 pub(crate) data: UnsafeCell<T>,
24}
25
26/// RAII guard to handle releasing a PyMutex lock.
27///
28/// The lock is released when `PyMutexGuard` is dropped.
29pub struct PyMutexGuard<'a, T: ?Sized> {
30 inner: &'a PyMutex<T>,
31 // this is equivalent to impl !Send, which we can't do
32 // because negative trait bounds aren't supported yet
33 _phantom: PhantomData<*const ()>,
34}
35
36/// SAFETY: `T` must be `Sync` for a [`PyMutexGuard<T>`] to be `Sync`
37/// because it is possible to get a `&T` from `&MutexGuard` (via `Deref`).
38unsafe impl<T: ?Sized + Sync> Sync for PyMutexGuard<'_, T> {}
39
40/// SAFETY: `T` must be `Send` for a [`PyMutex`] to be `Send` because it is possible to acquire
41/// the owned `T` from the `PyMutex` via [`into_inner`].
42///
43/// [`into_inner`]: PyMutex::into_inner
44unsafe impl<T: ?Sized + Send> Send for PyMutex<T> {}
45
46/// SAFETY: `T` must be `Send` for [`PyMutex`] to be `Sync`.
47/// This ensures that the protected data can be accessed safely from multiple threads
48/// without causing data races or other unsafe behavior.
49///
50/// [`PyMutex<T>`] provides mutable access to `T` to one thread at a time. However, it's essential
51/// for `T` to be `Send` because it's not safe for non-`Send` structures to be accessed in
52/// this manner. For instance, consider [`Rc`], a non-atomic reference counted smart pointer,
53/// which is not `Send`. With `Rc`, we can have multiple copies pointing to the same heap
54/// allocation with a non-atomic reference count. If we were to use `Mutex<Rc<_>>`, it would
55/// only protect one instance of `Rc` from shared access, leaving other copies vulnerable
56/// to potential data races.
57///
58/// Also note that it is not necessary for `T` to be `Sync` as `&T` is only made available
59/// to one thread at a time if `T` is not `Sync`.
60///
61/// [`Rc`]: alloc::rc::Rc
62unsafe impl<T: ?Sized + Send> Sync for PyMutex<T> {}
63
64impl<T> PyMutex<T> {
65 /// Acquire the mutex, blocking the current thread until it is able to do so.
66 pub fn lock(&self) -> PyMutexGuard<'_, T> {
67 // SAFETY: valid pointer to mutex passed to `PyMutex_Lock`
68 // and the mutex is not moved while locked
69 unsafe { crate::ffi::PyMutex_Lock(self.mutex.get()) };
70 PyMutexGuard::new(self)
71 }
72
73 /// Create a new mutex in an unlocked state ready for use.
74 pub const fn new(value: T) -> Self {
75 Self {
76 mutex: UnsafeCell::new(crate::ffi::PyMutex::new()),
77 data: UnsafeCell::new(value),
78 }
79 }
80
81 /// Check if the mutex is locked.
82 ///
83 /// Note that this is only useful for debugging or test purposes and should
84 /// not be used to make concurrency control decisions, as the lock state may
85 /// change immediately after the check.
86 #[cfg(Py_3_14)]
87 pub fn is_locked(&self) -> bool {
88 // SAFETY: valid pointer to mutex passed to `PyMutex_IsLocked`
89 let ret = unsafe { crate::ffi::PyMutex_IsLocked(self.mutex.get()) };
90 ret != 0
91 }
92
93 /// Consumes this mutex, returning the underlying data.
94 pub fn into_inner(self) -> T
95 where
96 T: Sized,
97 {
98 self.data.into_inner()
99 }
100}
101
102impl<'mutex, T: ?Sized> PyMutexGuard<'mutex, T> {
103 fn new(lock: &'mutex PyMutex<T>) -> PyMutexGuard<'mutex, T> {
104 PyMutexGuard {
105 inner: lock,
106 _phantom: PhantomData,
107 }
108 }
109}
110
111impl<'a, T: ?Sized> Drop for PyMutexGuard<'a, T> {
112 fn drop(&mut self) {
113 // SAFETY: valid pointer to mutex passed to `PyMutex_Unlock`
114 unsafe { crate::ffi::PyMutex_Unlock(self.inner.mutex.get()) };
115 }
116}
117
118impl<'a, T> Deref for PyMutexGuard<'a, T> {
119 type Target = T;
120
121 fn deref(&self) -> &T {
122 // safety: cannot be null pointer because PyMutex::new always
123 // creates a valid PyMutex pointer
124 unsafe { &*self.inner.data.get() }
125 }
126}
127
128impl<'a, T> DerefMut for PyMutexGuard<'a, T> {
129 fn deref_mut(&mut self) -> &mut T {
130 // safety: cannot be null pointer because PyMutex::new always
131 // creates a valid PyMutex pointer
132 unsafe { &mut *self.inner.data.get() }
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 extern crate std;
139
140 #[cfg(not(target_arch = "wasm32"))]
141 use core::sync::atomic::{AtomicBool, Ordering};
142 #[cfg(not(target_arch = "wasm32"))]
143 use std::sync::Barrier;
144
145 use super::*;
146 #[cfg(not(target_arch = "wasm32"))]
147 use crate::Py;
148 #[cfg(not(target_arch = "wasm32"))]
149 use crate::Python;
150 #[cfg(not(target_arch = "wasm32"))]
151 use crate::types::{PyAnyMethods, PyDict, PyDictMethods, PyNone};
152
153 #[cfg(not(target_arch = "wasm32"))]
154 #[test]
155 fn test_pymutex() {
156 let mutex = Python::attach(|py| -> PyMutex<Py<PyDict>> {
157 let d = PyDict::new(py);
158 PyMutex::new(d.unbind())
159 });
160 #[cfg_attr(not(Py_3_14), allow(unused_variables))]
161 let mutex = Python::attach(|py| {
162 let mutex = py.detach(|| -> PyMutex<Py<PyDict>> {
163 std::thread::spawn(|| {
164 let dict_guard = mutex.lock();
165 Python::attach(|py| {
166 let dict = dict_guard.bind(py);
167 dict.set_item(PyNone::get(py), PyNone::get(py)).unwrap();
168 });
169 #[cfg(Py_3_14)]
170 assert!(mutex.is_locked());
171 drop(dict_guard);
172 #[cfg(Py_3_14)]
173 assert!(!mutex.is_locked());
174 mutex
175 })
176 .join()
177 .unwrap()
178 });
179
180 let dict_guard = mutex.lock();
181 #[cfg(Py_3_14)]
182 assert!(mutex.is_locked());
183 let d = dict_guard.bind(py);
184
185 assert!(
186 d.get_item(PyNone::get(py))
187 .unwrap()
188 .unwrap()
189 .eq(PyNone::get(py))
190 .unwrap()
191 );
192 #[cfg(Py_3_14)]
193 assert!(mutex.is_locked());
194 drop(dict_guard);
195 #[cfg(Py_3_14)]
196 assert!(!mutex.is_locked());
197 mutex
198 });
199 #[cfg(Py_3_14)]
200 assert!(!mutex.is_locked());
201 }
202
203 #[cfg(not(target_arch = "wasm32"))]
204 #[test]
205 fn test_pymutex_blocks() {
206 let mutex = PyMutex::new(());
207 let first_thread_locked_once = AtomicBool::new(false);
208 let second_thread_locked_once = AtomicBool::new(false);
209 let finished = AtomicBool::new(false);
210 let barrier = Barrier::new(2);
211
212 std::thread::scope(|s| {
213 s.spawn(|| {
214 let guard = mutex.lock();
215 first_thread_locked_once.store(true, Ordering::SeqCst);
216 while !finished.load(Ordering::SeqCst) {
217 if second_thread_locked_once.load(Ordering::SeqCst) {
218 // Wait a little to guard against the unlikely event that
219 // the other thread isn't blocked on acquiring the mutex yet.
220 // If PyMutex had a try_lock implementation this would be
221 // unnecessary
222 std::thread::sleep(core::time::Duration::from_millis(10));
223 // block (and hold the mutex) until the receiver actually receives something
224 barrier.wait();
225 finished.store(true, Ordering::SeqCst);
226 }
227 }
228 drop(guard);
229 });
230
231 s.spawn(|| {
232 while !first_thread_locked_once.load(Ordering::SeqCst) {
233 core::hint::spin_loop();
234 }
235 second_thread_locked_once.store(true, Ordering::SeqCst);
236 let guard = mutex.lock();
237 assert!(finished.load(Ordering::SeqCst));
238 drop(guard);
239 });
240
241 barrier.wait();
242 });
243 }
244
245 #[test]
246 fn test_send_not_send() {
247 use crate::impl_::pyclass::{IsSend, IsSync, value_of};
248
249 assert!(!value_of!(IsSend, PyMutexGuard<'_, i32>));
250 assert!(value_of!(IsSync, PyMutexGuard<'_, i32>));
251
252 assert!(value_of!(IsSend, PyMutex<i32>));
253 assert!(value_of!(IsSync, PyMutex<i32>));
254 }
255}