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