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::with_gil(|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::with_gil(|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::with_gil(|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::with_gil(|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::with_gil(|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#[cfg(rustc_has_once_lock)]
484mod once_lock_ext_sealed {
485    pub trait Sealed {}
486    impl<T> Sealed for std::sync::OnceLock<T> {}
487}
488
489/// Helper trait for `Once` to help avoid deadlocking when using a `Once` when attached to a
490/// Python thread.
491pub trait OnceExt: Sealed {
492    /// Similar to [`call_once`][Once::call_once], but releases the Python GIL temporarily
493    /// if blocking on another thread currently calling this `Once`.
494    fn call_once_py_attached(&self, py: Python<'_>, f: impl FnOnce());
495
496    /// Similar to [`call_once_force`][Once::call_once_force], but releases the Python GIL
497    /// temporarily if blocking on another thread currently calling this `Once`.
498    fn call_once_force_py_attached(&self, py: Python<'_>, f: impl FnOnce(&OnceState));
499}
500
501/// Extension trait for [`std::sync::OnceLock`] which helps avoid deadlocks between the Python
502/// interpreter and initialization with the `OnceLock`.
503#[cfg(rustc_has_once_lock)]
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    /// Lock this `Mutex` in a manner that cannot deadlock with the Python interpreter.
523    ///
524    /// Before attempting to lock the mutex, this function detaches from the
525    /// Python runtime. When the lock is acquired, it re-attaches to the Python
526    /// runtime before returning the `LockResult`. This avoids deadlocks between
527    /// the GIL and other global synchronization events triggered by the Python
528    /// interpreter.
529    fn lock_py_attached(
530        &self,
531        py: Python<'_>,
532    ) -> std::sync::LockResult<std::sync::MutexGuard<'_, T>>;
533}
534
535impl OnceExt for Once {
536    fn call_once_py_attached(&self, py: Python<'_>, f: impl FnOnce()) {
537        if self.is_completed() {
538            return;
539        }
540
541        init_once_py_attached(self, py, f)
542    }
543
544    fn call_once_force_py_attached(&self, py: Python<'_>, f: impl FnOnce(&OnceState)) {
545        if self.is_completed() {
546            return;
547        }
548
549        init_once_force_py_attached(self, py, f);
550    }
551}
552
553#[cfg(rustc_has_once_lock)]
554impl<T> OnceLockExt<T> for std::sync::OnceLock<T> {
555    fn get_or_init_py_attached<F>(&self, py: Python<'_>, f: F) -> &T
556    where
557        F: FnOnce() -> T,
558    {
559        // this trait is guarded by a rustc version config
560        // so clippy's MSRV check is wrong
561        #[allow(clippy::incompatible_msrv)]
562        // Use self.get() first to create a fast path when initialized
563        self.get()
564            .unwrap_or_else(|| init_once_lock_py_attached(self, py, f))
565    }
566}
567
568impl<T> MutexExt<T> for std::sync::Mutex<T> {
569    fn lock_py_attached(
570        &self,
571        _py: Python<'_>,
572    ) -> std::sync::LockResult<std::sync::MutexGuard<'_, T>> {
573        // If try_lock is successful or returns a poisoned mutex, return them so
574        // the caller can deal with them. Otherwise we need to use blocking
575        // lock, which requires detaching from the Python runtime to avoid
576        // possible deadlocks.
577        match self.try_lock() {
578            Ok(inner) => return Ok(inner),
579            Err(std::sync::TryLockError::Poisoned(inner)) => {
580                return std::sync::LockResult::Err(inner)
581            }
582            Err(std::sync::TryLockError::WouldBlock) => {}
583        }
584        // SAFETY: detach from the runtime right before a possibly blocking call
585        // then reattach when the blocking call completes and before calling
586        // into the C API.
587        let ts_guard = unsafe { SuspendGIL::new() };
588        let res = self.lock();
589        drop(ts_guard);
590        res
591    }
592}
593
594#[cold]
595fn init_once_py_attached<F, T>(once: &Once, _py: Python<'_>, f: F)
596where
597    F: FnOnce() -> T,
598{
599    // SAFETY: detach from the runtime right before a possibly blocking call
600    // then reattach when the blocking call completes and before calling
601    // into the C API.
602    let ts_guard = unsafe { SuspendGIL::new() };
603
604    once.call_once(move || {
605        drop(ts_guard);
606        f();
607    });
608}
609
610#[cold]
611fn init_once_force_py_attached<F, T>(once: &Once, _py: Python<'_>, f: F)
612where
613    F: FnOnce(&OnceState) -> T,
614{
615    // SAFETY: detach from the runtime right before a possibly blocking call
616    // then reattach when the blocking call completes and before calling
617    // into the C API.
618    let ts_guard = unsafe { SuspendGIL::new() };
619
620    once.call_once_force(move |state| {
621        drop(ts_guard);
622        f(state);
623    });
624}
625
626#[cfg(rustc_has_once_lock)]
627#[cold]
628fn init_once_lock_py_attached<'a, F, T>(
629    lock: &'a std::sync::OnceLock<T>,
630    _py: Python<'_>,
631    f: F,
632) -> &'a T
633where
634    F: FnOnce() -> T,
635{
636    // SAFETY: detach from the runtime right before a possibly blocking call
637    // then reattach when the blocking call completes and before calling
638    // into the C API.
639    let ts_guard = unsafe { SuspendGIL::new() };
640
641    // this trait is guarded by a rustc version config
642    // so clippy's MSRV check is wrong
643    #[allow(clippy::incompatible_msrv)]
644    // By having detached here, we guarantee that `.get_or_init` cannot deadlock with
645    // the Python interpreter
646    let value = lock.get_or_init(move || {
647        drop(ts_guard);
648        f()
649    });
650
651    value
652}
653
654#[cfg(test)]
655mod tests {
656    use super::*;
657
658    use crate::types::{PyDict, PyDictMethods};
659    #[cfg(not(target_arch = "wasm32"))]
660    use std::sync::Mutex;
661    #[cfg(not(target_arch = "wasm32"))]
662    #[cfg(feature = "macros")]
663    use std::sync::{
664        atomic::{AtomicBool, Ordering},
665        Barrier,
666    };
667
668    #[cfg(not(target_arch = "wasm32"))]
669    #[cfg(feature = "macros")]
670    #[crate::pyclass(crate = "crate")]
671    struct BoolWrapper(AtomicBool);
672
673    #[test]
674    fn test_intern() {
675        Python::with_gil(|py| {
676            let foo1 = "foo";
677            let foo2 = intern!(py, "foo");
678            let foo3 = intern!(py, stringify!(foo));
679
680            let dict = PyDict::new(py);
681            dict.set_item(foo1, 42_usize).unwrap();
682            assert!(dict.contains(foo2).unwrap());
683            assert_eq!(
684                dict.get_item(foo3)
685                    .unwrap()
686                    .unwrap()
687                    .extract::<usize>()
688                    .unwrap(),
689                42
690            );
691        });
692    }
693
694    #[test]
695    fn test_once_cell() {
696        Python::with_gil(|py| {
697            let mut cell = GILOnceCell::new();
698
699            assert!(cell.get(py).is_none());
700
701            assert_eq!(cell.get_or_try_init(py, || Err(5)), Err(5));
702            assert!(cell.get(py).is_none());
703
704            assert_eq!(cell.get_or_try_init(py, || Ok::<_, ()>(2)), Ok(&2));
705            assert_eq!(cell.get(py), Some(&2));
706
707            assert_eq!(cell.get_or_try_init(py, || Err(5)), Ok(&2));
708
709            assert_eq!(cell.take(), Some(2));
710            assert_eq!(cell.into_inner(), None);
711
712            let cell_py = GILOnceCell::new();
713            assert!(cell_py.clone_ref(py).get(py).is_none());
714            cell_py.get_or_init(py, || py.None());
715            assert!(cell_py.clone_ref(py).get(py).unwrap().is_none(py));
716        })
717    }
718
719    #[test]
720    fn test_once_cell_drop() {
721        #[derive(Debug)]
722        struct RecordDrop<'a>(&'a mut bool);
723
724        impl Drop for RecordDrop<'_> {
725            fn drop(&mut self) {
726                *self.0 = true;
727            }
728        }
729
730        Python::with_gil(|py| {
731            let mut dropped = false;
732            let cell = GILOnceCell::new();
733            cell.set(py, RecordDrop(&mut dropped)).unwrap();
734            let drop_container = cell.get(py).unwrap();
735
736            assert!(!*drop_container.0);
737            drop(cell);
738            assert!(dropped);
739        });
740    }
741
742    #[cfg(feature = "macros")]
743    #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
744    #[test]
745    fn test_critical_section() {
746        let barrier = Barrier::new(2);
747
748        let bool_wrapper = Python::with_gil(|py| -> Py<BoolWrapper> {
749            Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap()
750        });
751
752        std::thread::scope(|s| {
753            s.spawn(|| {
754                Python::with_gil(|py| {
755                    let b = bool_wrapper.bind(py);
756                    with_critical_section(b, || {
757                        barrier.wait();
758                        std::thread::sleep(std::time::Duration::from_millis(10));
759                        b.borrow().0.store(true, Ordering::Release);
760                    })
761                });
762            });
763            s.spawn(|| {
764                barrier.wait();
765                Python::with_gil(|py| {
766                    let b = bool_wrapper.bind(py);
767                    // this blocks until the other thread's critical section finishes
768                    with_critical_section(b, || {
769                        assert!(b.borrow().0.load(Ordering::Acquire));
770                    });
771                });
772            });
773        });
774    }
775
776    #[test]
777    #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
778    fn test_once_ext() {
779        // adapted from the example in the docs for Once::try_once_force
780        let init = Once::new();
781        std::thread::scope(|s| {
782            // poison the once
783            let handle = s.spawn(|| {
784                Python::with_gil(|py| {
785                    init.call_once_py_attached(py, || panic!());
786                })
787            });
788            assert!(handle.join().is_err());
789
790            // poisoning propagates
791            let handle = s.spawn(|| {
792                Python::with_gil(|py| {
793                    init.call_once_py_attached(py, || {});
794                });
795            });
796
797            assert!(handle.join().is_err());
798
799            // call_once_force will still run and reset the poisoned state
800            Python::with_gil(|py| {
801                init.call_once_force_py_attached(py, |state| {
802                    assert!(state.is_poisoned());
803                });
804
805                // once any success happens, we stop propagating the poison
806                init.call_once_py_attached(py, || {});
807            });
808        });
809    }
810
811    #[cfg(rustc_has_once_lock)]
812    #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
813    #[test]
814    fn test_once_lock_ext() {
815        let cell = std::sync::OnceLock::new();
816        std::thread::scope(|s| {
817            assert!(cell.get().is_none());
818
819            s.spawn(|| {
820                Python::with_gil(|py| {
821                    assert_eq!(*cell.get_or_init_py_attached(py, || 12345), 12345);
822                });
823            });
824        });
825        assert_eq!(cell.get(), Some(&12345));
826    }
827
828    #[cfg(feature = "macros")]
829    #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
830    #[test]
831    fn test_mutex_ext() {
832        let barrier = Barrier::new(2);
833
834        let mutex = Python::with_gil(|py| -> Mutex<Py<BoolWrapper>> {
835            Mutex::new(Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap())
836        });
837
838        std::thread::scope(|s| {
839            s.spawn(|| {
840                Python::with_gil(|py| {
841                    let b = mutex.lock_py_attached(py).unwrap();
842                    barrier.wait();
843                    // sleep to ensure the other thread actually blocks
844                    std::thread::sleep(std::time::Duration::from_millis(10));
845                    (*b).bind(py).borrow().0.store(true, Ordering::Release);
846                    drop(b);
847                });
848            });
849            s.spawn(|| {
850                barrier.wait();
851                Python::with_gil(|py| {
852                    // blocks until the other thread releases the lock
853                    let b = mutex.lock_py_attached(py).unwrap();
854                    assert!((*b).bind(py).borrow().0.load(Ordering::Acquire));
855                });
856            });
857        });
858    }
859
860    #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
861    #[test]
862    fn test_mutex_ext_poison() {
863        let mutex = Mutex::new(42);
864
865        std::thread::scope(|s| {
866            let lock_result = s.spawn(|| {
867                Python::with_gil(|py| {
868                    let _unused = mutex.lock_py_attached(py);
869                    panic!();
870                });
871            });
872            assert!(lock_result.join().is_err());
873            assert!(mutex.is_poisoned());
874        });
875        let guard = Python::with_gil(|py| {
876            // recover from the poisoning
877            match mutex.lock_py_attached(py) {
878                Ok(guard) => guard,
879                Err(poisoned) => poisoned.into_inner(),
880            }
881        });
882        assert!(*guard == 42);
883    }
884}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here