pyo3/
sync.rs

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