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