Skip to main content

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