Skip to main content

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