pyo3/sync.rs
1//! Synchronization mechanisms based on the Python GIL.
2//!
3//! With the acceptance of [PEP 703] (aka a "freethreaded Python") for Python 3.13, these
4//! are likely to undergo significant developments in the future.
5//!
6//! [PEP 703]: https://peps.python.org/pep-703/
7use crate::{
8 gil::SuspendGIL,
9 sealed::Sealed,
10 types::{any::PyAnyMethods, PyAny, PyString},
11 Bound, Py, PyResult, PyTypeCheck, Python,
12};
13use std::{
14 cell::UnsafeCell,
15 marker::PhantomData,
16 mem::MaybeUninit,
17 sync::{Once, OnceState},
18};
19
20#[cfg(not(Py_GIL_DISABLED))]
21use crate::PyVisit;
22
23/// Value with concurrent access protected by the GIL.
24///
25/// This is a synchronization primitive based on Python's global interpreter lock (GIL).
26/// It ensures that only one thread at a time can access the inner value via shared references.
27/// It can be combined with interior mutability to obtain mutable references.
28///
29/// This type is not defined for extensions built against the free-threaded CPython ABI.
30///
31/// # Example
32///
33/// Combining `GILProtected` with `RefCell` enables mutable access to static data:
34///
35/// ```
36/// # use pyo3::prelude::*;
37/// use pyo3::sync::GILProtected;
38/// use std::cell::RefCell;
39///
40/// static NUMBERS: GILProtected<RefCell<Vec<i32>>> = GILProtected::new(RefCell::new(Vec::new()));
41///
42/// Python::attach(|py| {
43/// NUMBERS.get(py).borrow_mut().push(42);
44/// });
45/// ```
46#[cfg(not(Py_GIL_DISABLED))]
47pub struct GILProtected<T> {
48 value: T,
49}
50
51#[cfg(not(Py_GIL_DISABLED))]
52impl<T> GILProtected<T> {
53 /// Place the given value under the protection of the GIL.
54 pub const fn new(value: T) -> Self {
55 Self { value }
56 }
57
58 /// Gain access to the inner value by giving proof of having acquired the GIL.
59 pub fn get<'py>(&'py self, _py: Python<'py>) -> &'py T {
60 &self.value
61 }
62
63 /// Gain access to the inner value by giving proof that garbage collection is happening.
64 pub fn traverse<'py>(&'py self, _visit: PyVisit<'py>) -> &'py T {
65 &self.value
66 }
67}
68
69#[cfg(not(Py_GIL_DISABLED))]
70unsafe impl<T> Sync for GILProtected<T> where T: Send {}
71
72/// A write-once primitive similar to [`std::sync::OnceLock<T>`].
73///
74/// Unlike `OnceLock<T>` which blocks threads to achieve thread safety, `GilOnceCell<T>`
75/// allows calls to [`get_or_init`][GILOnceCell::get_or_init] and
76/// [`get_or_try_init`][GILOnceCell::get_or_try_init] to race to create an initialized value.
77/// (It is still guaranteed that only one thread will ever write to the cell.)
78///
79/// On Python versions that run with the Global Interpreter Lock (GIL), this helps to avoid
80/// deadlocks between initialization and the GIL. For an example of such a deadlock, see
81#[doc = concat!(
82 "[the FAQ section](https://pyo3.rs/v",
83 env!("CARGO_PKG_VERSION"),
84 "/faq.html#im-experiencing-deadlocks-using-pyo3-with-stdsynconcelock-stdsynclazylock-lazy_static-and-once_cell)"
85)]
86/// of the guide.
87///
88/// Note that because the GIL blocks concurrent execution, in practice the means that
89/// [`get_or_init`][GILOnceCell::get_or_init] and
90/// [`get_or_try_init`][GILOnceCell::get_or_try_init] may race if the initialization
91/// function leads to the GIL being released and a thread context switch. This can
92/// happen when importing or calling any Python code, as long as it releases the
93/// GIL at some point. On free-threaded Python without any GIL, the race is
94/// more likely since there is no GIL to prevent races. In the future, PyO3 may change
95/// the semantics of GILOnceCell to behave more like the GIL build in the future.
96///
97/// # Re-entrant initialization
98///
99/// [`get_or_init`][GILOnceCell::get_or_init] and
100/// [`get_or_try_init`][GILOnceCell::get_or_try_init] do not protect against infinite recursion
101/// from reentrant initialization.
102///
103/// # Examples
104///
105/// The following example shows how to use `GILOnceCell` to share a reference to a Python list
106/// between threads:
107///
108/// ```
109/// use pyo3::sync::GILOnceCell;
110/// use pyo3::prelude::*;
111/// use pyo3::types::PyList;
112///
113/// static LIST_CELL: GILOnceCell<Py<PyList>> = GILOnceCell::new();
114///
115/// pub fn get_shared_list(py: Python<'_>) -> &Bound<'_, PyList> {
116/// LIST_CELL
117/// .get_or_init(py, || PyList::empty(py).unbind())
118/// .bind(py)
119/// }
120/// # Python::attach(|py| assert_eq!(get_shared_list(py).len(), 0));
121/// ```
122pub struct GILOnceCell<T> {
123 once: Once,
124 data: UnsafeCell<MaybeUninit<T>>,
125
126 /// (Copied from std::sync::OnceLock)
127 ///
128 /// `PhantomData` to make sure dropck understands we're dropping T in our Drop impl.
129 ///
130 /// ```compile_error,E0597
131 /// use pyo3::Python;
132 /// use pyo3::sync::GILOnceCell;
133 ///
134 /// struct A<'a>(#[allow(dead_code)] &'a str);
135 ///
136 /// impl<'a> Drop for A<'a> {
137 /// fn drop(&mut self) {}
138 /// }
139 ///
140 /// let cell = GILOnceCell::new();
141 /// {
142 /// let s = String::new();
143 /// let _ = Python::attach(|py| cell.set(py,A(&s)));
144 /// }
145 /// ```
146 _marker: PhantomData<T>,
147}
148
149impl<T> Default for GILOnceCell<T> {
150 fn default() -> Self {
151 Self::new()
152 }
153}
154
155// T: Send is needed for Sync because the thread which drops the GILOnceCell can be different
156// to the thread which fills it. (e.g. think scoped thread which fills the cell and then exits,
157// leaving the cell to be dropped by the main thread).
158unsafe impl<T: Send + Sync> Sync for GILOnceCell<T> {}
159unsafe impl<T: Send> Send for GILOnceCell<T> {}
160
161impl<T> GILOnceCell<T> {
162 /// Create a `GILOnceCell` which does not yet contain a value.
163 pub const fn new() -> Self {
164 Self {
165 once: Once::new(),
166 data: UnsafeCell::new(MaybeUninit::uninit()),
167 _marker: PhantomData,
168 }
169 }
170
171 /// Get a reference to the contained value, or `None` if the cell has not yet been written.
172 #[inline]
173 pub fn get(&self, _py: Python<'_>) -> Option<&T> {
174 if self.once.is_completed() {
175 // SAFETY: the cell has been written.
176 Some(unsafe { (*self.data.get()).assume_init_ref() })
177 } else {
178 None
179 }
180 }
181
182 /// Get a reference to the contained value, initializing it if needed using the provided
183 /// closure.
184 ///
185 /// See the type-level documentation for detail on re-entrancy and concurrent initialization.
186 #[inline]
187 pub fn get_or_init<F>(&self, py: Python<'_>, f: F) -> &T
188 where
189 F: FnOnce() -> T,
190 {
191 if let Some(value) = self.get(py) {
192 return value;
193 }
194
195 // .unwrap() will never panic because the result is always Ok
196 self.init(py, || Ok::<T, std::convert::Infallible>(f()))
197 .unwrap()
198 }
199
200 /// Like `get_or_init`, but accepts a fallible initialization function. If it fails, the cell
201 /// is left uninitialized.
202 ///
203 /// See the type-level documentation for detail on re-entrancy and concurrent initialization.
204 #[inline]
205 pub fn get_or_try_init<F, E>(&self, py: Python<'_>, f: F) -> Result<&T, E>
206 where
207 F: FnOnce() -> Result<T, E>,
208 {
209 if let Some(value) = self.get(py) {
210 return Ok(value);
211 }
212
213 self.init(py, f)
214 }
215
216 #[cold]
217 fn init<F, E>(&self, py: Python<'_>, f: F) -> Result<&T, E>
218 where
219 F: FnOnce() -> Result<T, E>,
220 {
221 // Note that f() could temporarily release the GIL, so it's possible that another thread
222 // writes to this GILOnceCell before f() finishes. That's fine; we'll just have to discard
223 // the value computed here and accept a bit of wasted computation.
224
225 // TODO: on the freethreaded build, consider wrapping this pair of operations in a
226 // critical section (requires a critical section API which can use a PyMutex without
227 // an object.)
228 let value = f()?;
229 let _ = self.set(py, value);
230
231 Ok(self.get(py).unwrap())
232 }
233
234 /// Get the contents of the cell mutably. This is only possible if the reference to the cell is
235 /// unique.
236 pub fn get_mut(&mut self) -> Option<&mut T> {
237 if self.once.is_completed() {
238 // SAFETY: the cell has been written.
239 Some(unsafe { (*self.data.get()).assume_init_mut() })
240 } else {
241 None
242 }
243 }
244
245 /// Set the value in the cell.
246 ///
247 /// If the cell has already been written, `Err(value)` will be returned containing the new
248 /// value which was not written.
249 pub fn set(&self, _py: Python<'_>, value: T) -> Result<(), T> {
250 let mut value = Some(value);
251 // NB this can block, but since this is only writing a single value and
252 // does not call arbitrary python code, we don't need to worry about
253 // deadlocks with the GIL.
254 self.once.call_once_force(|_| {
255 // SAFETY: no other threads can be writing this value, because we are
256 // inside the `call_once_force` closure.
257 unsafe {
258 // `.take().unwrap()` will never panic
259 (*self.data.get()).write(value.take().unwrap());
260 }
261 });
262
263 match value {
264 // Some other thread wrote to the cell first
265 Some(value) => Err(value),
266 None => Ok(()),
267 }
268 }
269
270 /// Takes the value out of the cell, moving it back to an uninitialized state.
271 ///
272 /// Has no effect and returns None if the cell has not yet been written.
273 pub fn take(&mut self) -> Option<T> {
274 if self.once.is_completed() {
275 // Reset the cell to its default state so that it won't try to
276 // drop the value again.
277 self.once = Once::new();
278 // SAFETY: the cell has been written. `self.once` has been reset,
279 // so when `self` is dropped the value won't be read again.
280 Some(unsafe { self.data.get_mut().assume_init_read() })
281 } else {
282 None
283 }
284 }
285
286 /// Consumes the cell, returning the wrapped value.
287 ///
288 /// Returns None if the cell has not yet been written.
289 pub fn into_inner(mut self) -> Option<T> {
290 self.take()
291 }
292}
293
294impl<T> GILOnceCell<Py<T>> {
295 /// Creates a new cell that contains a new Python reference to the same contained object.
296 ///
297 /// Returns an uninitialized cell if `self` has not yet been initialized.
298 pub fn clone_ref(&self, py: Python<'_>) -> Self {
299 let cloned = Self {
300 once: Once::new(),
301 data: UnsafeCell::new(MaybeUninit::uninit()),
302 _marker: PhantomData,
303 };
304 if let Some(value) = self.get(py) {
305 let _ = cloned.set(py, value.clone_ref(py));
306 }
307 cloned
308 }
309}
310
311impl<T> GILOnceCell<Py<T>>
312where
313 T: PyTypeCheck,
314{
315 /// Get a reference to the contained Python type, initializing the cell if needed.
316 ///
317 /// This is a shorthand method for `get_or_init` which imports the type from Python on init.
318 ///
319 /// # Example: Using `GILOnceCell` to store a class in a static variable.
320 ///
321 /// `GILOnceCell` can be used to avoid importing a class multiple times:
322 /// ```
323 /// # use pyo3::prelude::*;
324 /// # use pyo3::sync::GILOnceCell;
325 /// # use pyo3::types::{PyDict, PyType};
326 /// # use pyo3::intern;
327 /// #
328 /// #[pyfunction]
329 /// fn create_ordered_dict<'py>(py: Python<'py>, dict: Bound<'py, PyDict>) -> PyResult<Bound<'py, PyAny>> {
330 /// // Even if this function is called multiple times,
331 /// // the `OrderedDict` class will be imported only once.
332 /// static ORDERED_DICT: GILOnceCell<Py<PyType>> = GILOnceCell::new();
333 /// ORDERED_DICT
334 /// .import(py, "collections", "OrderedDict")?
335 /// .call1((dict,))
336 /// }
337 ///
338 /// # Python::attach(|py| {
339 /// # let dict = PyDict::new(py);
340 /// # dict.set_item(intern!(py, "foo"), 42).unwrap();
341 /// # let fun = wrap_pyfunction!(create_ordered_dict, py).unwrap();
342 /// # let ordered_dict = fun.call1((&dict,)).unwrap();
343 /// # assert!(dict.eq(ordered_dict).unwrap());
344 /// # });
345 /// ```
346 pub fn import<'py>(
347 &self,
348 py: Python<'py>,
349 module_name: &str,
350 attr_name: &str,
351 ) -> PyResult<&Bound<'py, T>> {
352 self.get_or_try_init(py, || {
353 let type_object = py
354 .import(module_name)?
355 .getattr(attr_name)?
356 .downcast_into()?;
357 Ok(type_object.unbind())
358 })
359 .map(|ty| ty.bind(py))
360 }
361}
362
363impl<T> Drop for GILOnceCell<T> {
364 fn drop(&mut self) {
365 if self.once.is_completed() {
366 // SAFETY: the cell has been written.
367 unsafe { MaybeUninit::assume_init_drop(self.data.get_mut()) }
368 }
369 }
370}
371
372/// Interns `text` as a Python string and stores a reference to it in static storage.
373///
374/// A reference to the same Python string is returned on each invocation.
375///
376/// # Example: Using `intern!` to avoid needlessly recreating the same Python string
377///
378/// ```
379/// use pyo3::intern;
380/// # use pyo3::{prelude::*, types::PyDict};
381///
382/// #[pyfunction]
383/// fn create_dict(py: Python<'_>) -> PyResult<Bound<'_, PyDict>> {
384/// let dict = PyDict::new(py);
385/// // 👇 A new `PyString` is created
386/// // for every call of this function.
387/// dict.set_item("foo", 42)?;
388/// Ok(dict)
389/// }
390///
391/// #[pyfunction]
392/// fn create_dict_faster(py: Python<'_>) -> PyResult<Bound<'_, PyDict>> {
393/// let dict = PyDict::new(py);
394/// // 👇 A `PyString` is created once and reused
395/// // for the lifetime of the program.
396/// dict.set_item(intern!(py, "foo"), 42)?;
397/// Ok(dict)
398/// }
399/// #
400/// # Python::attach(|py| {
401/// # let fun_slow = wrap_pyfunction!(create_dict, py).unwrap();
402/// # let dict = fun_slow.call0().unwrap();
403/// # assert!(dict.contains("foo").unwrap());
404/// # let fun = wrap_pyfunction!(create_dict_faster, py).unwrap();
405/// # let dict = fun.call0().unwrap();
406/// # assert!(dict.contains("foo").unwrap());
407/// # });
408/// ```
409#[macro_export]
410macro_rules! intern {
411 ($py: expr, $text: expr) => {{
412 static INTERNED: $crate::sync::Interned = $crate::sync::Interned::new($text);
413 INTERNED.get($py)
414 }};
415}
416
417/// Implementation detail for `intern!` macro.
418#[doc(hidden)]
419pub struct Interned(&'static str, GILOnceCell<Py<PyString>>);
420
421impl Interned {
422 /// Creates an empty holder for an interned `str`.
423 pub const fn new(value: &'static str) -> Self {
424 Interned(value, GILOnceCell::new())
425 }
426
427 /// Gets or creates the interned `str` value.
428 #[inline]
429 pub fn get<'py>(&self, py: Python<'py>) -> &Bound<'py, PyString> {
430 self.1
431 .get_or_init(py, || PyString::intern(py, self.0).into())
432 .bind(py)
433 }
434}
435
436/// Executes a closure with a Python critical section held on an object.
437///
438/// Acquires the per-object lock for the object `op` that is held
439/// until the closure `f` is finished.
440///
441/// This is structurally equivalent to the use of the paired
442/// Py_BEGIN_CRITICAL_SECTION and Py_END_CRITICAL_SECTION C-API macros.
443///
444/// A no-op on GIL-enabled builds, where the critical section API is exposed as
445/// a no-op by the Python C API.
446///
447/// Provides weaker locking guarantees than traditional locks, but can in some
448/// cases be used to provide guarantees similar to the GIL without the risk of
449/// deadlocks associated with traditional locks.
450///
451/// Many CPython C API functions do not acquire the per-object lock on objects
452/// passed to Python. You should not expect critical sections applied to
453/// built-in types to prevent concurrent modification. This API is most useful
454/// for user-defined types with full control over how the internal state for the
455/// type is managed.
456#[cfg_attr(not(Py_GIL_DISABLED), allow(unused_variables))]
457pub fn with_critical_section<F, R>(object: &Bound<'_, PyAny>, f: F) -> R
458where
459 F: FnOnce() -> R,
460{
461 #[cfg(Py_GIL_DISABLED)]
462 {
463 struct Guard(crate::ffi::PyCriticalSection);
464
465 impl Drop for Guard {
466 fn drop(&mut self) {
467 unsafe {
468 crate::ffi::PyCriticalSection_End(&mut self.0);
469 }
470 }
471 }
472
473 let mut guard = Guard(unsafe { std::mem::zeroed() });
474 unsafe { crate::ffi::PyCriticalSection_Begin(&mut guard.0, object.as_ptr()) };
475 f()
476 }
477 #[cfg(not(Py_GIL_DISABLED))]
478 {
479 f()
480 }
481}
482
483/// Executes a closure with a Python critical section held on two objects.
484///
485/// Acquires the per-object lock for the objects `a` and `b` that are held
486/// until the closure `f` is finished.
487///
488/// This is structurally equivalent to the use of the paired
489/// Py_BEGIN_CRITICAL_SECTION2 and Py_END_CRITICAL_SECTION2 C-API macros.
490///
491/// A no-op on GIL-enabled builds, where the critical section API is exposed as
492/// a no-op by the Python C API.
493///
494/// Provides weaker locking guarantees than traditional locks, but can in some
495/// cases be used to provide guarantees similar to the GIL without the risk of
496/// deadlocks associated with traditional locks.
497///
498/// Many CPython C API functions do not acquire the per-object lock on objects
499/// passed to Python. You should not expect critical sections applied to
500/// built-in types to prevent concurrent modification. This API is most useful
501/// for user-defined types with full control over how the internal state for the
502/// type is managed.
503#[cfg_attr(not(Py_GIL_DISABLED), allow(unused_variables))]
504pub fn with_critical_section2<F, R>(a: &Bound<'_, PyAny>, b: &Bound<'_, PyAny>, f: F) -> R
505where
506 F: FnOnce() -> R,
507{
508 #[cfg(Py_GIL_DISABLED)]
509 {
510 struct Guard(crate::ffi::PyCriticalSection2);
511
512 impl Drop for Guard {
513 fn drop(&mut self) {
514 unsafe {
515 crate::ffi::PyCriticalSection2_End(&mut self.0);
516 }
517 }
518 }
519
520 let mut guard = Guard(unsafe { std::mem::zeroed() });
521 unsafe { crate::ffi::PyCriticalSection2_Begin(&mut guard.0, a.as_ptr(), b.as_ptr()) };
522 f()
523 }
524 #[cfg(not(Py_GIL_DISABLED))]
525 {
526 f()
527 }
528}
529
530mod once_lock_ext_sealed {
531 pub trait Sealed {}
532 impl<T> Sealed for std::sync::OnceLock<T> {}
533}
534
535/// Helper trait for `Once` to help avoid deadlocking when using a `Once` when attached to a
536/// Python thread.
537pub trait OnceExt: Sealed {
538 ///The state of `Once`
539 type OnceState;
540
541 /// Similar to [`call_once`][Once::call_once], but releases the Python GIL temporarily
542 /// if blocking on another thread currently calling this `Once`.
543 fn call_once_py_attached(&self, py: Python<'_>, f: impl FnOnce());
544
545 /// Similar to [`call_once_force`][Once::call_once_force], but releases the Python GIL
546 /// temporarily if blocking on another thread currently calling this `Once`.
547 fn call_once_force_py_attached(&self, py: Python<'_>, f: impl FnOnce(&Self::OnceState));
548}
549
550/// Extension trait for [`std::sync::OnceLock`] which helps avoid deadlocks between the Python
551/// interpreter and initialization with the `OnceLock`.
552pub trait OnceLockExt<T>: once_lock_ext_sealed::Sealed {
553 /// Initializes this `OnceLock` with the given closure if it has not been initialized yet.
554 ///
555 /// If this function would block, this function detaches from the Python interpreter and
556 /// reattaches before calling `f`. This avoids deadlocks between the Python interpreter and
557 /// the `OnceLock` in cases where `f` can call arbitrary Python code, as calling arbitrary
558 /// Python code can lead to `f` itself blocking on the Python interpreter.
559 ///
560 /// By detaching from the Python interpreter before blocking, this ensures that if `f` blocks
561 /// then the Python interpreter cannot be blocked by `f` itself.
562 fn get_or_init_py_attached<F>(&self, py: Python<'_>, f: F) -> &T
563 where
564 F: FnOnce() -> T;
565}
566
567/// Extension trait for [`std::sync::Mutex`] which helps avoid deadlocks between
568/// the Python interpreter and acquiring the `Mutex`.
569pub trait MutexExt<T>: Sealed {
570 /// The result type returned by the `lock_py_attached` method.
571 type LockResult<'a>
572 where
573 Self: 'a;
574
575 /// Lock this `Mutex` in a manner that cannot deadlock with the Python interpreter.
576 ///
577 /// Before attempting to lock the mutex, this function detaches from the
578 /// Python runtime. When the lock is acquired, it re-attaches to the Python
579 /// runtime before returning the `LockResult`. This avoids deadlocks between
580 /// the GIL and other global synchronization events triggered by the Python
581 /// interpreter.
582 fn lock_py_attached(&self, py: Python<'_>) -> Self::LockResult<'_>;
583}
584
585impl OnceExt for Once {
586 type OnceState = OnceState;
587
588 fn call_once_py_attached(&self, py: Python<'_>, f: impl FnOnce()) {
589 if self.is_completed() {
590 return;
591 }
592
593 init_once_py_attached(self, py, f)
594 }
595
596 fn call_once_force_py_attached(&self, py: Python<'_>, f: impl FnOnce(&OnceState)) {
597 if self.is_completed() {
598 return;
599 }
600
601 init_once_force_py_attached(self, py, f);
602 }
603}
604
605#[cfg(feature = "parking_lot")]
606impl OnceExt for parking_lot::Once {
607 type OnceState = parking_lot::OnceState;
608
609 fn call_once_py_attached(&self, _py: Python<'_>, f: impl FnOnce()) {
610 if self.state().done() {
611 return;
612 }
613
614 let ts_guard = unsafe { SuspendGIL::new() };
615
616 self.call_once(move || {
617 drop(ts_guard);
618 f();
619 });
620 }
621
622 fn call_once_force_py_attached(
623 &self,
624 _py: Python<'_>,
625 f: impl FnOnce(&parking_lot::OnceState),
626 ) {
627 if self.state().done() {
628 return;
629 }
630
631 let ts_guard = unsafe { SuspendGIL::new() };
632
633 self.call_once_force(move |state| {
634 drop(ts_guard);
635 f(&state);
636 });
637 }
638}
639
640impl<T> OnceLockExt<T> for std::sync::OnceLock<T> {
641 fn get_or_init_py_attached<F>(&self, py: Python<'_>, f: F) -> &T
642 where
643 F: FnOnce() -> T,
644 {
645 // Use self.get() first to create a fast path when initialized
646 self.get()
647 .unwrap_or_else(|| init_once_lock_py_attached(self, py, f))
648 }
649}
650
651impl<T> MutexExt<T> for std::sync::Mutex<T> {
652 type LockResult<'a>
653 = std::sync::LockResult<std::sync::MutexGuard<'a, T>>
654 where
655 Self: 'a;
656
657 fn lock_py_attached(
658 &self,
659 _py: Python<'_>,
660 ) -> std::sync::LockResult<std::sync::MutexGuard<'_, T>> {
661 // If try_lock is successful or returns a poisoned mutex, return them so
662 // the caller can deal with them. Otherwise we need to use blocking
663 // lock, which requires detaching from the Python runtime to avoid
664 // possible deadlocks.
665 match self.try_lock() {
666 Ok(inner) => return Ok(inner),
667 Err(std::sync::TryLockError::Poisoned(inner)) => {
668 return std::sync::LockResult::Err(inner)
669 }
670 Err(std::sync::TryLockError::WouldBlock) => {}
671 }
672 // SAFETY: detach from the runtime right before a possibly blocking call
673 // then reattach when the blocking call completes and before calling
674 // into the C API.
675 let ts_guard = unsafe { SuspendGIL::new() };
676 let res = self.lock();
677 drop(ts_guard);
678 res
679 }
680}
681
682#[cfg(feature = "lock_api")]
683impl<R: lock_api::RawMutex, T> MutexExt<T> for lock_api::Mutex<R, T> {
684 type LockResult<'a>
685 = lock_api::MutexGuard<'a, R, T>
686 where
687 Self: 'a;
688
689 fn lock_py_attached(&self, _py: Python<'_>) -> lock_api::MutexGuard<'_, R, T> {
690 if let Some(guard) = self.try_lock() {
691 return guard;
692 }
693
694 let ts_guard = unsafe { SuspendGIL::new() };
695 let res = self.lock();
696 drop(ts_guard);
697 res
698 }
699}
700
701#[cfg(feature = "arc_lock")]
702impl<R, T> MutexExt<T> for std::sync::Arc<lock_api::Mutex<R, T>>
703where
704 R: lock_api::RawMutex,
705{
706 type LockResult<'a>
707 = lock_api::ArcMutexGuard<R, T>
708 where
709 Self: 'a;
710
711 fn lock_py_attached(&self, _py: Python<'_>) -> lock_api::ArcMutexGuard<R, T> {
712 if let Some(guard) = self.try_lock_arc() {
713 return guard;
714 }
715
716 let ts_guard = unsafe { SuspendGIL::new() };
717 let res = self.lock_arc();
718 drop(ts_guard);
719 res
720 }
721}
722
723#[cold]
724fn init_once_py_attached<F, T>(once: &Once, _py: Python<'_>, f: F)
725where
726 F: FnOnce() -> T,
727{
728 // SAFETY: detach from the runtime right before a possibly blocking call
729 // then reattach when the blocking call completes and before calling
730 // into the C API.
731 let ts_guard = unsafe { SuspendGIL::new() };
732
733 once.call_once(move || {
734 drop(ts_guard);
735 f();
736 });
737}
738
739#[cold]
740fn init_once_force_py_attached<F, T>(once: &Once, _py: Python<'_>, f: F)
741where
742 F: FnOnce(&OnceState) -> T,
743{
744 // SAFETY: detach from the runtime right before a possibly blocking call
745 // then reattach when the blocking call completes and before calling
746 // into the C API.
747 let ts_guard = unsafe { SuspendGIL::new() };
748
749 once.call_once_force(move |state| {
750 drop(ts_guard);
751 f(state);
752 });
753}
754
755#[cold]
756fn init_once_lock_py_attached<'a, F, T>(
757 lock: &'a std::sync::OnceLock<T>,
758 _py: Python<'_>,
759 f: F,
760) -> &'a T
761where
762 F: FnOnce() -> T,
763{
764 // SAFETY: detach from the runtime right before a possibly blocking call
765 // then reattach when the blocking call completes and before calling
766 // into the C API.
767 let ts_guard = unsafe { SuspendGIL::new() };
768
769 // this trait is guarded by a rustc version config
770 // so clippy's MSRV check is wrong
771 #[allow(clippy::incompatible_msrv)]
772 // By having detached here, we guarantee that `.get_or_init` cannot deadlock with
773 // the Python interpreter
774 let value = lock.get_or_init(move || {
775 drop(ts_guard);
776 f()
777 });
778
779 value
780}
781
782#[cfg(test)]
783mod tests {
784 use super::*;
785
786 use crate::types::{PyDict, PyDictMethods};
787 #[cfg(not(target_arch = "wasm32"))]
788 use std::sync::Mutex;
789 #[cfg(not(target_arch = "wasm32"))]
790 #[cfg(feature = "macros")]
791 use std::sync::{
792 atomic::{AtomicBool, Ordering},
793 Barrier,
794 };
795
796 #[cfg(not(target_arch = "wasm32"))]
797 #[cfg(feature = "macros")]
798 #[crate::pyclass(crate = "crate")]
799 struct BoolWrapper(AtomicBool);
800
801 #[cfg(not(target_arch = "wasm32"))]
802 #[cfg(feature = "macros")]
803 #[crate::pyclass(crate = "crate")]
804 struct VecWrapper(Vec<isize>);
805
806 #[test]
807 fn test_intern() {
808 Python::attach(|py| {
809 let foo1 = "foo";
810 let foo2 = intern!(py, "foo");
811 let foo3 = intern!(py, stringify!(foo));
812
813 let dict = PyDict::new(py);
814 dict.set_item(foo1, 42_usize).unwrap();
815 assert!(dict.contains(foo2).unwrap());
816 assert_eq!(
817 dict.get_item(foo3)
818 .unwrap()
819 .unwrap()
820 .extract::<usize>()
821 .unwrap(),
822 42
823 );
824 });
825 }
826
827 #[test]
828 fn test_once_cell() {
829 Python::attach(|py| {
830 let mut cell = GILOnceCell::new();
831
832 assert!(cell.get(py).is_none());
833
834 assert_eq!(cell.get_or_try_init(py, || Err(5)), Err(5));
835 assert!(cell.get(py).is_none());
836
837 assert_eq!(cell.get_or_try_init(py, || Ok::<_, ()>(2)), Ok(&2));
838 assert_eq!(cell.get(py), Some(&2));
839
840 assert_eq!(cell.get_or_try_init(py, || Err(5)), Ok(&2));
841
842 assert_eq!(cell.take(), Some(2));
843 assert_eq!(cell.into_inner(), None);
844
845 let cell_py = GILOnceCell::new();
846 assert!(cell_py.clone_ref(py).get(py).is_none());
847 cell_py.get_or_init(py, || py.None());
848 assert!(cell_py.clone_ref(py).get(py).unwrap().is_none(py));
849 })
850 }
851
852 #[test]
853 fn test_once_cell_drop() {
854 #[derive(Debug)]
855 struct RecordDrop<'a>(&'a mut bool);
856
857 impl Drop for RecordDrop<'_> {
858 fn drop(&mut self) {
859 *self.0 = true;
860 }
861 }
862
863 Python::attach(|py| {
864 let mut dropped = false;
865 let cell = GILOnceCell::new();
866 cell.set(py, RecordDrop(&mut dropped)).unwrap();
867 let drop_container = cell.get(py).unwrap();
868
869 assert!(!*drop_container.0);
870 drop(cell);
871 assert!(dropped);
872 });
873 }
874
875 #[cfg(feature = "macros")]
876 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
877 #[test]
878 fn test_critical_section() {
879 let barrier = Barrier::new(2);
880
881 let bool_wrapper = Python::attach(|py| -> Py<BoolWrapper> {
882 Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap()
883 });
884
885 std::thread::scope(|s| {
886 s.spawn(|| {
887 Python::attach(|py| {
888 let b = bool_wrapper.bind(py);
889 with_critical_section(b, || {
890 barrier.wait();
891 std::thread::sleep(std::time::Duration::from_millis(10));
892 b.borrow().0.store(true, Ordering::Release);
893 })
894 });
895 });
896 s.spawn(|| {
897 barrier.wait();
898 Python::attach(|py| {
899 let b = bool_wrapper.bind(py);
900 // this blocks until the other thread's critical section finishes
901 with_critical_section(b, || {
902 assert!(b.borrow().0.load(Ordering::Acquire));
903 });
904 });
905 });
906 });
907 }
908
909 #[cfg(feature = "macros")]
910 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
911 #[test]
912 fn test_critical_section2() {
913 let barrier = Barrier::new(3);
914
915 let (bool_wrapper1, bool_wrapper2) = Python::attach(|py| {
916 (
917 Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap(),
918 Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap(),
919 )
920 });
921
922 std::thread::scope(|s| {
923 s.spawn(|| {
924 Python::attach(|py| {
925 let b1 = bool_wrapper1.bind(py);
926 let b2 = bool_wrapper2.bind(py);
927 with_critical_section2(b1, b2, || {
928 barrier.wait();
929 std::thread::sleep(std::time::Duration::from_millis(10));
930 b1.borrow().0.store(true, Ordering::Release);
931 b2.borrow().0.store(true, Ordering::Release);
932 })
933 });
934 });
935 s.spawn(|| {
936 barrier.wait();
937 Python::attach(|py| {
938 let b1 = bool_wrapper1.bind(py);
939 // this blocks until the other thread's critical section finishes
940 with_critical_section(b1, || {
941 assert!(b1.borrow().0.load(Ordering::Acquire));
942 });
943 });
944 });
945 s.spawn(|| {
946 barrier.wait();
947 Python::attach(|py| {
948 let b2 = bool_wrapper2.bind(py);
949 // this blocks until the other thread's critical section finishes
950 with_critical_section(b2, || {
951 assert!(b2.borrow().0.load(Ordering::Acquire));
952 });
953 });
954 });
955 });
956 }
957
958 #[cfg(feature = "macros")]
959 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
960 #[test]
961 fn test_critical_section2_same_object_no_deadlock() {
962 let barrier = Barrier::new(2);
963
964 let bool_wrapper = Python::attach(|py| -> Py<BoolWrapper> {
965 Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap()
966 });
967
968 std::thread::scope(|s| {
969 s.spawn(|| {
970 Python::attach(|py| {
971 let b = bool_wrapper.bind(py);
972 with_critical_section2(b, b, || {
973 barrier.wait();
974 std::thread::sleep(std::time::Duration::from_millis(10));
975 b.borrow().0.store(true, Ordering::Release);
976 })
977 });
978 });
979 s.spawn(|| {
980 barrier.wait();
981 Python::attach(|py| {
982 let b = bool_wrapper.bind(py);
983 // this blocks until the other thread's critical section finishes
984 with_critical_section(b, || {
985 assert!(b.borrow().0.load(Ordering::Acquire));
986 });
987 });
988 });
989 });
990 }
991
992 #[cfg(feature = "macros")]
993 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
994 #[test]
995 fn test_critical_section2_two_containers() {
996 let (vec1, vec2) = Python::attach(|py| {
997 (
998 Py::new(py, VecWrapper(vec![1, 2, 3])).unwrap(),
999 Py::new(py, VecWrapper(vec![4, 5])).unwrap(),
1000 )
1001 });
1002
1003 std::thread::scope(|s| {
1004 s.spawn(|| {
1005 Python::attach(|py| {
1006 let v1 = vec1.bind(py);
1007 let v2 = vec2.bind(py);
1008 with_critical_section2(v1, v2, || {
1009 // v2.extend(v1)
1010 v2.borrow_mut().0.extend(v1.borrow().0.iter());
1011 })
1012 });
1013 });
1014 s.spawn(|| {
1015 Python::attach(|py| {
1016 let v1 = vec1.bind(py);
1017 let v2 = vec2.bind(py);
1018 with_critical_section2(v1, v2, || {
1019 // v1.extend(v2)
1020 v1.borrow_mut().0.extend(v2.borrow().0.iter());
1021 })
1022 });
1023 });
1024 });
1025
1026 Python::attach(|py| {
1027 let v1 = vec1.bind(py);
1028 let v2 = vec2.bind(py);
1029 // execution order is not guaranteed, so we need to check both
1030 // NB: extend should be atomic, items must not be interleaved
1031 // v1.extend(v2)
1032 // v2.extend(v1)
1033 let expected1_vec1 = vec![1, 2, 3, 4, 5];
1034 let expected1_vec2 = vec![4, 5, 1, 2, 3, 4, 5];
1035 // v2.extend(v1)
1036 // v1.extend(v2)
1037 let expected2_vec1 = vec![1, 2, 3, 4, 5, 1, 2, 3];
1038 let expected2_vec2 = vec![4, 5, 1, 2, 3];
1039
1040 assert!(
1041 (v1.borrow().0.eq(&expected1_vec1) && v2.borrow().0.eq(&expected1_vec2))
1042 || (v1.borrow().0.eq(&expected2_vec1) && v2.borrow().0.eq(&expected2_vec2))
1043 );
1044 });
1045 }
1046
1047 #[test]
1048 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
1049 fn test_once_ext() {
1050 macro_rules! test_once {
1051 ($once:expr, $is_poisoned:expr) => {{
1052 // adapted from the example in the docs for Once::try_once_force
1053 let init = $once;
1054 std::thread::scope(|s| {
1055 // poison the once
1056 let handle = s.spawn(|| {
1057 Python::attach(|py| {
1058 init.call_once_py_attached(py, || panic!());
1059 })
1060 });
1061 assert!(handle.join().is_err());
1062
1063 // poisoning propagates
1064 let handle = s.spawn(|| {
1065 Python::attach(|py| {
1066 init.call_once_py_attached(py, || {});
1067 });
1068 });
1069
1070 assert!(handle.join().is_err());
1071
1072 // call_once_force will still run and reset the poisoned state
1073 Python::attach(|py| {
1074 init.call_once_force_py_attached(py, |state| {
1075 assert!($is_poisoned(state.clone()));
1076 });
1077
1078 // once any success happens, we stop propagating the poison
1079 init.call_once_py_attached(py, || {});
1080 });
1081
1082 // calling call_once_force should return immediately without calling the closure
1083 Python::attach(|py| init.call_once_force_py_attached(py, |_| panic!()));
1084 });
1085 }};
1086 }
1087
1088 test_once!(Once::new(), OnceState::is_poisoned);
1089 #[cfg(feature = "parking_lot")]
1090 test_once!(parking_lot::Once::new(), parking_lot::OnceState::poisoned);
1091 }
1092
1093 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
1094 #[test]
1095 fn test_once_lock_ext() {
1096 let cell = std::sync::OnceLock::new();
1097 std::thread::scope(|s| {
1098 assert!(cell.get().is_none());
1099
1100 s.spawn(|| {
1101 Python::attach(|py| {
1102 assert_eq!(*cell.get_or_init_py_attached(py, || 12345), 12345);
1103 });
1104 });
1105 });
1106 assert_eq!(cell.get(), Some(&12345));
1107 }
1108
1109 #[cfg(feature = "macros")]
1110 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
1111 #[test]
1112 fn test_mutex_ext() {
1113 let barrier = Barrier::new(2);
1114
1115 let mutex = Python::attach(|py| -> Mutex<Py<BoolWrapper>> {
1116 Mutex::new(Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap())
1117 });
1118
1119 std::thread::scope(|s| {
1120 s.spawn(|| {
1121 Python::attach(|py| {
1122 let b = mutex.lock_py_attached(py).unwrap();
1123 barrier.wait();
1124 // sleep to ensure the other thread actually blocks
1125 std::thread::sleep(std::time::Duration::from_millis(10));
1126 (*b).bind(py).borrow().0.store(true, Ordering::Release);
1127 drop(b);
1128 });
1129 });
1130 s.spawn(|| {
1131 barrier.wait();
1132 Python::attach(|py| {
1133 // blocks until the other thread releases the lock
1134 let b = mutex.lock_py_attached(py).unwrap();
1135 assert!((*b).bind(py).borrow().0.load(Ordering::Acquire));
1136 });
1137 });
1138 });
1139 }
1140
1141 #[cfg(feature = "macros")]
1142 #[cfg(all(
1143 any(feature = "parking_lot", feature = "lock_api"),
1144 not(target_arch = "wasm32") // We are building wasm Python with pthreads disabled
1145 ))]
1146 #[test]
1147 fn test_parking_lot_mutex_ext() {
1148 macro_rules! test_mutex {
1149 ($guard:ty ,$mutex:stmt) => {{
1150 let barrier = Barrier::new(2);
1151
1152 let mutex = Python::attach({ $mutex });
1153
1154 std::thread::scope(|s| {
1155 s.spawn(|| {
1156 Python::attach(|py| {
1157 let b: $guard = mutex.lock_py_attached(py);
1158 barrier.wait();
1159 // sleep to ensure the other thread actually blocks
1160 std::thread::sleep(std::time::Duration::from_millis(10));
1161 (*b).bind(py).borrow().0.store(true, Ordering::Release);
1162 drop(b);
1163 });
1164 });
1165 s.spawn(|| {
1166 barrier.wait();
1167 Python::attach(|py| {
1168 // blocks until the other thread releases the lock
1169 let b: $guard = mutex.lock_py_attached(py);
1170 assert!((*b).bind(py).borrow().0.load(Ordering::Acquire));
1171 });
1172 });
1173 });
1174 }};
1175 }
1176
1177 test_mutex!(parking_lot::MutexGuard<'_, _>, |py| {
1178 parking_lot::Mutex::new(Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap())
1179 });
1180
1181 #[cfg(feature = "arc_lock")]
1182 test_mutex!(parking_lot::ArcMutexGuard<_, _>, |py| {
1183 let mutex =
1184 parking_lot::Mutex::new(Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap());
1185 std::sync::Arc::new(mutex)
1186 });
1187 }
1188
1189 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
1190 #[test]
1191 fn test_mutex_ext_poison() {
1192 let mutex = Mutex::new(42);
1193
1194 std::thread::scope(|s| {
1195 let lock_result = s.spawn(|| {
1196 Python::attach(|py| {
1197 let _unused = mutex.lock_py_attached(py);
1198 panic!();
1199 });
1200 });
1201 assert!(lock_result.join().is_err());
1202 assert!(mutex.is_poisoned());
1203 });
1204 let guard = Python::attach(|py| {
1205 // recover from the poisoning
1206 match mutex.lock_py_attached(py) {
1207 Ok(guard) => guard,
1208 Err(poisoned) => poisoned.into_inner(),
1209 }
1210 });
1211 assert!(*guard == 42);
1212 }
1213}