pyo3/sync.rs
1//! Synchronization mechanisms based on the Python GIL.
2//!
3//! With the acceptance of [PEP 703] (aka a "freethreaded Python") for Python 3.13, these
4//! are likely to undergo significant developments in the future.
5//!
6//! [PEP 703]: https://peps.python.org/pep-703/
7use crate::{
8 ffi,
9 sealed::Sealed,
10 types::{any::PyAnyMethods, PyAny, PyString},
11 Bound, Py, PyResult, PyTypeCheck, Python,
12};
13use std::{
14 cell::UnsafeCell,
15 marker::PhantomData,
16 mem::MaybeUninit,
17 sync::{Once, OnceState},
18};
19
20#[cfg(not(Py_GIL_DISABLED))]
21use crate::PyVisit;
22
23/// Value with concurrent access protected by the GIL.
24///
25/// This is a synchronization primitive based on Python's global interpreter lock (GIL).
26/// It ensures that only one thread at a time can access the inner value via shared references.
27/// It can be combined with interior mutability to obtain mutable references.
28///
29/// This type is not defined for extensions built against the free-threaded CPython ABI.
30///
31/// # Example
32///
33/// Combining `GILProtected` with `RefCell` enables mutable access to static data:
34///
35/// ```
36/// # use pyo3::prelude::*;
37/// use pyo3::sync::GILProtected;
38/// use std::cell::RefCell;
39///
40/// static NUMBERS: GILProtected<RefCell<Vec<i32>>> = GILProtected::new(RefCell::new(Vec::new()));
41///
42/// Python::with_gil(|py| {
43/// NUMBERS.get(py).borrow_mut().push(42);
44/// });
45/// ```
46#[cfg(not(Py_GIL_DISABLED))]
47pub struct GILProtected<T> {
48 value: T,
49}
50
51#[cfg(not(Py_GIL_DISABLED))]
52impl<T> GILProtected<T> {
53 /// Place the given value under the protection of the GIL.
54 pub const fn new(value: T) -> Self {
55 Self { value }
56 }
57
58 /// Gain access to the inner value by giving proof of having acquired the GIL.
59 pub fn get<'py>(&'py self, _py: Python<'py>) -> &'py T {
60 &self.value
61 }
62
63 /// Gain access to the inner value by giving proof that garbage collection is happening.
64 pub fn traverse<'py>(&'py self, _visit: PyVisit<'py>) -> &'py T {
65 &self.value
66 }
67}
68
69#[cfg(not(Py_GIL_DISABLED))]
70unsafe impl<T> Sync for GILProtected<T> where T: Send {}
71
72/// A write-once primitive similar to [`std::sync::OnceLock<T>`].
73///
74/// Unlike `OnceLock<T>` which blocks threads to achieve thread safety, `GilOnceCell<T>`
75/// allows calls to [`get_or_init`][GILOnceCell::get_or_init] and
76/// [`get_or_try_init`][GILOnceCell::get_or_try_init] to race to create an initialized value.
77/// (It is still guaranteed that only one thread will ever write to the cell.)
78///
79/// On Python versions that run with the Global Interpreter Lock (GIL), this helps to avoid
80/// deadlocks between initialization and the GIL. For an example of such a deadlock, see
81#[doc = concat!(
82 "[the FAQ section](https://pyo3.rs/v",
83 env!("CARGO_PKG_VERSION"),
84 "/faq.html#im-experiencing-deadlocks-using-pyo3-with-stdsynconcelock-stdsynclazylock-lazy_static-and-once_cell)"
85)]
86/// of the guide.
87///
88/// Note that because the GIL blocks concurrent execution, in practice the means that
89/// [`get_or_init`][GILOnceCell::get_or_init] and
90/// [`get_or_try_init`][GILOnceCell::get_or_try_init] may race if the initialization
91/// function leads to the GIL being released and a thread context switch. This can
92/// happen when importing or calling any Python code, as long as it releases the
93/// GIL at some point. On free-threaded Python without any GIL, the race is
94/// more likely since there is no GIL to prevent races. In the future, PyO3 may change
95/// the semantics of GILOnceCell to behave more like the GIL build in the future.
96///
97/// # Re-entrant initialization
98///
99/// [`get_or_init`][GILOnceCell::get_or_init] and
100/// [`get_or_try_init`][GILOnceCell::get_or_try_init] do not protect against infinite recursion
101/// from reentrant initialization.
102///
103/// # Examples
104///
105/// The following example shows how to use `GILOnceCell` to share a reference to a Python list
106/// between threads:
107///
108/// ```
109/// use pyo3::sync::GILOnceCell;
110/// use pyo3::prelude::*;
111/// use pyo3::types::PyList;
112///
113/// static LIST_CELL: GILOnceCell<Py<PyList>> = GILOnceCell::new();
114///
115/// pub fn get_shared_list(py: Python<'_>) -> &Bound<'_, PyList> {
116/// LIST_CELL
117/// .get_or_init(py, || PyList::empty(py).unbind())
118/// .bind(py)
119/// }
120/// # Python::with_gil(|py| assert_eq!(get_shared_list(py).len(), 0));
121/// ```
122pub struct GILOnceCell<T> {
123 once: Once,
124 data: UnsafeCell<MaybeUninit<T>>,
125
126 /// (Copied from std::sync::OnceLock)
127 ///
128 /// `PhantomData` to make sure dropck understands we're dropping T in our Drop impl.
129 ///
130 /// ```compile_error,E0597
131 /// use pyo3::Python;
132 /// use pyo3::sync::GILOnceCell;
133 ///
134 /// struct A<'a>(#[allow(dead_code)] &'a str);
135 ///
136 /// impl<'a> Drop for A<'a> {
137 /// fn drop(&mut self) {}
138 /// }
139 ///
140 /// let cell = GILOnceCell::new();
141 /// {
142 /// let s = String::new();
143 /// let _ = Python::with_gil(|py| cell.set(py,A(&s)));
144 /// }
145 /// ```
146 _marker: PhantomData<T>,
147}
148
149impl<T> Default for GILOnceCell<T> {
150 fn default() -> Self {
151 Self::new()
152 }
153}
154
155// T: Send is needed for Sync because the thread which drops the GILOnceCell can be different
156// to the thread which fills it. (e.g. think scoped thread which fills the cell and then exits,
157// leaving the cell to be dropped by the main thread).
158unsafe impl<T: Send + Sync> Sync for GILOnceCell<T> {}
159unsafe impl<T: Send> Send for GILOnceCell<T> {}
160
161impl<T> GILOnceCell<T> {
162 /// Create a `GILOnceCell` which does not yet contain a value.
163 pub const fn new() -> Self {
164 Self {
165 once: Once::new(),
166 data: UnsafeCell::new(MaybeUninit::uninit()),
167 _marker: PhantomData,
168 }
169 }
170
171 /// Get a reference to the contained value, or `None` if the cell has not yet been written.
172 #[inline]
173 pub fn get(&self, _py: Python<'_>) -> Option<&T> {
174 if self.once.is_completed() {
175 // SAFETY: the cell has been written.
176 Some(unsafe { (*self.data.get()).assume_init_ref() })
177 } else {
178 None
179 }
180 }
181
182 /// Get a reference to the contained value, initializing it if needed using the provided
183 /// closure.
184 ///
185 /// See the type-level documentation for detail on re-entrancy and concurrent initialization.
186 #[inline]
187 pub fn get_or_init<F>(&self, py: Python<'_>, f: F) -> &T
188 where
189 F: FnOnce() -> T,
190 {
191 if let Some(value) = self.get(py) {
192 return value;
193 }
194
195 // .unwrap() will never panic because the result is always Ok
196 self.init(py, || Ok::<T, std::convert::Infallible>(f()))
197 .unwrap()
198 }
199
200 /// Like `get_or_init`, but accepts a fallible initialization function. If it fails, the cell
201 /// is left uninitialized.
202 ///
203 /// See the type-level documentation for detail on re-entrancy and concurrent initialization.
204 #[inline]
205 pub fn get_or_try_init<F, E>(&self, py: Python<'_>, f: F) -> Result<&T, E>
206 where
207 F: FnOnce() -> Result<T, E>,
208 {
209 if let Some(value) = self.get(py) {
210 return Ok(value);
211 }
212
213 self.init(py, f)
214 }
215
216 #[cold]
217 fn init<F, E>(&self, py: Python<'_>, f: F) -> Result<&T, E>
218 where
219 F: FnOnce() -> Result<T, E>,
220 {
221 // Note that f() could temporarily release the GIL, so it's possible that another thread
222 // writes to this GILOnceCell before f() finishes. That's fine; we'll just have to discard
223 // the value computed here and accept a bit of wasted computation.
224
225 // TODO: on the freethreaded build, consider wrapping this pair of operations in a
226 // critical section (requires a critical section API which can use a PyMutex without
227 // an object.)
228 let value = f()?;
229 let _ = self.set(py, value);
230
231 Ok(self.get(py).unwrap())
232 }
233
234 /// Get the contents of the cell mutably. This is only possible if the reference to the cell is
235 /// unique.
236 pub fn get_mut(&mut self) -> Option<&mut T> {
237 if self.once.is_completed() {
238 // SAFETY: the cell has been written.
239 Some(unsafe { (*self.data.get()).assume_init_mut() })
240 } else {
241 None
242 }
243 }
244
245 /// Set the value in the cell.
246 ///
247 /// If the cell has already been written, `Err(value)` will be returned containing the new
248 /// value which was not written.
249 pub fn set(&self, _py: Python<'_>, value: T) -> Result<(), T> {
250 let mut value = Some(value);
251 // NB this can block, but since this is only writing a single value and
252 // does not call arbitrary python code, we don't need to worry about
253 // deadlocks with the GIL.
254 self.once.call_once_force(|_| {
255 // SAFETY: no other threads can be writing this value, because we are
256 // inside the `call_once_force` closure.
257 unsafe {
258 // `.take().unwrap()` will never panic
259 (*self.data.get()).write(value.take().unwrap());
260 }
261 });
262
263 match value {
264 // Some other thread wrote to the cell first
265 Some(value) => Err(value),
266 None => Ok(()),
267 }
268 }
269
270 /// Takes the value out of the cell, moving it back to an uninitialized state.
271 ///
272 /// Has no effect and returns None if the cell has not yet been written.
273 pub fn take(&mut self) -> Option<T> {
274 if self.once.is_completed() {
275 // Reset the cell to its default state so that it won't try to
276 // drop the value again.
277 self.once = Once::new();
278 // SAFETY: the cell has been written. `self.once` has been reset,
279 // so when `self` is dropped the value won't be read again.
280 Some(unsafe { self.data.get_mut().assume_init_read() })
281 } else {
282 None
283 }
284 }
285
286 /// Consumes the cell, returning the wrapped value.
287 ///
288 /// Returns None if the cell has not yet been written.
289 pub fn into_inner(mut self) -> Option<T> {
290 self.take()
291 }
292}
293
294impl<T> GILOnceCell<Py<T>> {
295 /// Creates a new cell that contains a new Python reference to the same contained object.
296 ///
297 /// Returns an uninitialized cell if `self` has not yet been initialized.
298 pub fn clone_ref(&self, py: Python<'_>) -> Self {
299 let cloned = Self {
300 once: Once::new(),
301 data: UnsafeCell::new(MaybeUninit::uninit()),
302 _marker: PhantomData,
303 };
304 if let Some(value) = self.get(py) {
305 let _ = cloned.set(py, value.clone_ref(py));
306 }
307 cloned
308 }
309}
310
311impl<T> GILOnceCell<Py<T>>
312where
313 T: PyTypeCheck,
314{
315 /// Get a reference to the contained Python type, initializing the cell if needed.
316 ///
317 /// This is a shorthand method for `get_or_init` which imports the type from Python on init.
318 ///
319 /// # Example: Using `GILOnceCell` to store a class in a static variable.
320 ///
321 /// `GILOnceCell` can be used to avoid importing a class multiple times:
322 /// ```
323 /// # use pyo3::prelude::*;
324 /// # use pyo3::sync::GILOnceCell;
325 /// # use pyo3::types::{PyDict, PyType};
326 /// # use pyo3::intern;
327 /// #
328 /// #[pyfunction]
329 /// fn create_ordered_dict<'py>(py: Python<'py>, dict: Bound<'py, PyDict>) -> PyResult<Bound<'py, PyAny>> {
330 /// // Even if this function is called multiple times,
331 /// // the `OrderedDict` class will be imported only once.
332 /// static ORDERED_DICT: GILOnceCell<Py<PyType>> = GILOnceCell::new();
333 /// ORDERED_DICT
334 /// .import(py, "collections", "OrderedDict")?
335 /// .call1((dict,))
336 /// }
337 ///
338 /// # Python::with_gil(|py| {
339 /// # let dict = PyDict::new(py);
340 /// # dict.set_item(intern!(py, "foo"), 42).unwrap();
341 /// # let fun = wrap_pyfunction!(create_ordered_dict, py).unwrap();
342 /// # let ordered_dict = fun.call1((&dict,)).unwrap();
343 /// # assert!(dict.eq(ordered_dict).unwrap());
344 /// # });
345 /// ```
346 pub fn import<'py>(
347 &self,
348 py: Python<'py>,
349 module_name: &str,
350 attr_name: &str,
351 ) -> PyResult<&Bound<'py, T>> {
352 self.get_or_try_init(py, || {
353 let type_object = py
354 .import(module_name)?
355 .getattr(attr_name)?
356 .downcast_into()?;
357 Ok(type_object.unbind())
358 })
359 .map(|ty| ty.bind(py))
360 }
361}
362
363impl<T> Drop for GILOnceCell<T> {
364 fn drop(&mut self) {
365 if self.once.is_completed() {
366 // SAFETY: the cell has been written.
367 unsafe { MaybeUninit::assume_init_drop(self.data.get_mut()) }
368 }
369 }
370}
371
372/// Interns `text` as a Python string and stores a reference to it in static storage.
373///
374/// A reference to the same Python string is returned on each invocation.
375///
376/// # Example: Using `intern!` to avoid needlessly recreating the same Python string
377///
378/// ```
379/// use pyo3::intern;
380/// # use pyo3::{prelude::*, types::PyDict};
381///
382/// #[pyfunction]
383/// fn create_dict(py: Python<'_>) -> PyResult<Bound<'_, PyDict>> {
384/// let dict = PyDict::new(py);
385/// // 👇 A new `PyString` is created
386/// // for every call of this function.
387/// dict.set_item("foo", 42)?;
388/// Ok(dict)
389/// }
390///
391/// #[pyfunction]
392/// fn create_dict_faster(py: Python<'_>) -> PyResult<Bound<'_, PyDict>> {
393/// let dict = PyDict::new(py);
394/// // 👇 A `PyString` is created once and reused
395/// // for the lifetime of the program.
396/// dict.set_item(intern!(py, "foo"), 42)?;
397/// Ok(dict)
398/// }
399/// #
400/// # Python::with_gil(|py| {
401/// # let fun_slow = wrap_pyfunction!(create_dict, py).unwrap();
402/// # let dict = fun_slow.call0().unwrap();
403/// # assert!(dict.contains("foo").unwrap());
404/// # let fun = wrap_pyfunction!(create_dict_faster, py).unwrap();
405/// # let dict = fun.call0().unwrap();
406/// # assert!(dict.contains("foo").unwrap());
407/// # });
408/// ```
409#[macro_export]
410macro_rules! intern {
411 ($py: expr, $text: expr) => {{
412 static INTERNED: $crate::sync::Interned = $crate::sync::Interned::new($text);
413 INTERNED.get($py)
414 }};
415}
416
417/// Implementation detail for `intern!` macro.
418#[doc(hidden)]
419pub struct Interned(&'static str, GILOnceCell<Py<PyString>>);
420
421impl Interned {
422 /// Creates an empty holder for an interned `str`.
423 pub const fn new(value: &'static str) -> Self {
424 Interned(value, GILOnceCell::new())
425 }
426
427 /// Gets or creates the interned `str` value.
428 #[inline]
429 pub fn get<'py>(&self, py: Python<'py>) -> &Bound<'py, PyString> {
430 self.1
431 .get_or_init(py, || PyString::intern(py, self.0).into())
432 .bind(py)
433 }
434}
435
436/// Executes a closure with a Python critical section held on an object.
437///
438/// Acquires the per-object lock for the object `op` that is held
439/// until the closure `f` is finished.
440///
441/// This is structurally equivalent to the use of the paired
442/// Py_BEGIN_CRITICAL_SECTION and Py_END_CRITICAL_SECTION C-API macros.
443///
444/// A no-op on GIL-enabled builds, where the critical section API is exposed as
445/// a no-op by the Python C API.
446///
447/// Provides weaker locking guarantees than traditional locks, but can in some
448/// cases be used to provide guarantees similar to the GIL without the risk of
449/// deadlocks associated with traditional locks.
450///
451/// Many CPython C API functions do not acquire the per-object lock on objects
452/// passed to Python. You should not expect critical sections applied to
453/// built-in types to prevent concurrent modification. This API is most useful
454/// for user-defined types with full control over how the internal state for the
455/// type is managed.
456#[cfg_attr(not(Py_GIL_DISABLED), allow(unused_variables))]
457pub fn with_critical_section<F, R>(object: &Bound<'_, PyAny>, f: F) -> R
458where
459 F: FnOnce() -> R,
460{
461 #[cfg(Py_GIL_DISABLED)]
462 {
463 struct Guard(crate::ffi::PyCriticalSection);
464
465 impl Drop for Guard {
466 fn drop(&mut self) {
467 unsafe {
468 crate::ffi::PyCriticalSection_End(&mut self.0);
469 }
470 }
471 }
472
473 let mut guard = Guard(unsafe { std::mem::zeroed() });
474 unsafe { crate::ffi::PyCriticalSection_Begin(&mut guard.0, object.as_ptr()) };
475 f()
476 }
477 #[cfg(not(Py_GIL_DISABLED))]
478 {
479 f()
480 }
481}
482
483#[cfg(rustc_has_once_lock)]
484mod once_lock_ext_sealed {
485 pub trait Sealed {}
486 impl<T> Sealed for std::sync::OnceLock<T> {}
487}
488
489/// Helper trait for `Once` to help avoid deadlocking when using a `Once` when attached to a
490/// Python thread.
491pub trait OnceExt: Sealed {
492 /// Similar to [`call_once`][Once::call_once], but releases the Python GIL temporarily
493 /// if blocking on another thread currently calling this `Once`.
494 fn call_once_py_attached(&self, py: Python<'_>, f: impl FnOnce());
495
496 /// Similar to [`call_once_force`][Once::call_once_force], but releases the Python GIL
497 /// temporarily if blocking on another thread currently calling this `Once`.
498 fn call_once_force_py_attached(&self, py: Python<'_>, f: impl FnOnce(&OnceState));
499}
500
501// Extension trait for [`std::sync::OnceLock`] which helps avoid deadlocks between the Python
502/// interpreter and initialization with the `OnceLock`.
503#[cfg(rustc_has_once_lock)]
504pub trait OnceLockExt<T>: once_lock_ext_sealed::Sealed {
505 /// Initializes this `OnceLock` with the given closure if it has not been initialized yet.
506 ///
507 /// If this function would block, this function detaches from the Python interpreter and
508 /// reattaches before calling `f`. This avoids deadlocks between the Python interpreter and
509 /// the `OnceLock` in cases where `f` can call arbitrary Python code, as calling arbitrary
510 /// Python code can lead to `f` itself blocking on the Python interpreter.
511 ///
512 /// By detaching from the Python interpreter before blocking, this ensures that if `f` blocks
513 /// then the Python interpreter cannot be blocked by `f` itself.
514 fn get_or_init_py_attached<F>(&self, py: Python<'_>, f: F) -> &T
515 where
516 F: FnOnce() -> T;
517}
518
519struct Guard(*mut crate::ffi::PyThreadState);
520
521impl Drop for Guard {
522 fn drop(&mut self) {
523 unsafe { ffi::PyEval_RestoreThread(self.0) };
524 }
525}
526
527impl OnceExt for Once {
528 fn call_once_py_attached(&self, py: Python<'_>, f: impl FnOnce()) {
529 if self.is_completed() {
530 return;
531 }
532
533 init_once_py_attached(self, py, f)
534 }
535
536 fn call_once_force_py_attached(&self, py: Python<'_>, f: impl FnOnce(&OnceState)) {
537 if self.is_completed() {
538 return;
539 }
540
541 init_once_force_py_attached(self, py, f);
542 }
543}
544
545#[cfg(rustc_has_once_lock)]
546impl<T> OnceLockExt<T> for std::sync::OnceLock<T> {
547 fn get_or_init_py_attached<F>(&self, py: Python<'_>, f: F) -> &T
548 where
549 F: FnOnce() -> T,
550 {
551 // this trait is guarded by a rustc version config
552 // so clippy's MSRV check is wrong
553 #[allow(clippy::incompatible_msrv)]
554 // Use self.get() first to create a fast path when initialized
555 self.get()
556 .unwrap_or_else(|| init_once_lock_py_attached(self, py, f))
557 }
558}
559
560#[cold]
561fn init_once_py_attached<F, T>(once: &Once, _py: Python<'_>, f: F)
562where
563 F: FnOnce() -> T,
564{
565 // Safety: we are currently attached to the GIL, and we expect to block. We will save
566 // the current thread state and restore it as soon as we are done blocking.
567 let ts_guard = Guard(unsafe { ffi::PyEval_SaveThread() });
568
569 once.call_once(move || {
570 drop(ts_guard);
571 f();
572 });
573}
574
575#[cold]
576fn init_once_force_py_attached<F, T>(once: &Once, _py: Python<'_>, f: F)
577where
578 F: FnOnce(&OnceState) -> T,
579{
580 // Safety: we are currently attached to the GIL, and we expect to block. We will save
581 // the current thread state and restore it as soon as we are done blocking.
582 let ts_guard = Guard(unsafe { ffi::PyEval_SaveThread() });
583
584 once.call_once_force(move |state| {
585 drop(ts_guard);
586 f(state);
587 });
588}
589
590#[cfg(rustc_has_once_lock)]
591#[cold]
592fn init_once_lock_py_attached<'a, F, T>(
593 lock: &'a std::sync::OnceLock<T>,
594 _py: Python<'_>,
595 f: F,
596) -> &'a T
597where
598 F: FnOnce() -> T,
599{
600 // SAFETY: we are currently attached to a Python thread
601 let ts_guard = Guard(unsafe { ffi::PyEval_SaveThread() });
602
603 // this trait is guarded by a rustc version config
604 // so clippy's MSRV check is wrong
605 #[allow(clippy::incompatible_msrv)]
606 // By having detached here, we guarantee that `.get_or_init` cannot deadlock with
607 // the Python interpreter
608 let value = lock.get_or_init(move || {
609 drop(ts_guard);
610 f()
611 });
612
613 value
614}
615
616#[cfg(test)]
617mod tests {
618 use super::*;
619
620 use crate::types::{PyDict, PyDictMethods};
621
622 #[test]
623 fn test_intern() {
624 Python::with_gil(|py| {
625 let foo1 = "foo";
626 let foo2 = intern!(py, "foo");
627 let foo3 = intern!(py, stringify!(foo));
628
629 let dict = PyDict::new(py);
630 dict.set_item(foo1, 42_usize).unwrap();
631 assert!(dict.contains(foo2).unwrap());
632 assert_eq!(
633 dict.get_item(foo3)
634 .unwrap()
635 .unwrap()
636 .extract::<usize>()
637 .unwrap(),
638 42
639 );
640 });
641 }
642
643 #[test]
644 fn test_once_cell() {
645 Python::with_gil(|py| {
646 let mut cell = GILOnceCell::new();
647
648 assert!(cell.get(py).is_none());
649
650 assert_eq!(cell.get_or_try_init(py, || Err(5)), Err(5));
651 assert!(cell.get(py).is_none());
652
653 assert_eq!(cell.get_or_try_init(py, || Ok::<_, ()>(2)), Ok(&2));
654 assert_eq!(cell.get(py), Some(&2));
655
656 assert_eq!(cell.get_or_try_init(py, || Err(5)), Ok(&2));
657
658 assert_eq!(cell.take(), Some(2));
659 assert_eq!(cell.into_inner(), None);
660
661 let cell_py = GILOnceCell::new();
662 assert!(cell_py.clone_ref(py).get(py).is_none());
663 cell_py.get_or_init(py, || py.None());
664 assert!(cell_py.clone_ref(py).get(py).unwrap().is_none(py));
665 })
666 }
667
668 #[test]
669 fn test_once_cell_drop() {
670 #[derive(Debug)]
671 struct RecordDrop<'a>(&'a mut bool);
672
673 impl Drop for RecordDrop<'_> {
674 fn drop(&mut self) {
675 *self.0 = true;
676 }
677 }
678
679 Python::with_gil(|py| {
680 let mut dropped = false;
681 let cell = GILOnceCell::new();
682 cell.set(py, RecordDrop(&mut dropped)).unwrap();
683 let drop_container = cell.get(py).unwrap();
684
685 assert!(!*drop_container.0);
686 drop(cell);
687 assert!(dropped);
688 });
689 }
690
691 #[cfg(feature = "macros")]
692 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
693 #[test]
694 fn test_critical_section() {
695 use std::sync::{
696 atomic::{AtomicBool, Ordering},
697 Barrier,
698 };
699
700 let barrier = Barrier::new(2);
701
702 #[crate::pyclass(crate = "crate")]
703 struct BoolWrapper(AtomicBool);
704
705 let bool_wrapper = Python::with_gil(|py| -> Py<BoolWrapper> {
706 Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap()
707 });
708
709 std::thread::scope(|s| {
710 s.spawn(|| {
711 Python::with_gil(|py| {
712 let b = bool_wrapper.bind(py);
713 with_critical_section(b, || {
714 barrier.wait();
715 std::thread::sleep(std::time::Duration::from_millis(10));
716 b.borrow().0.store(true, Ordering::Release);
717 })
718 });
719 });
720 s.spawn(|| {
721 barrier.wait();
722 Python::with_gil(|py| {
723 let b = bool_wrapper.bind(py);
724 // this blocks until the other thread's critical section finishes
725 with_critical_section(b, || {
726 assert!(b.borrow().0.load(Ordering::Acquire));
727 });
728 });
729 });
730 });
731 }
732
733 #[test]
734 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
735 fn test_once_ext() {
736 // adapted from the example in the docs for Once::try_once_force
737 let init = Once::new();
738 std::thread::scope(|s| {
739 // poison the once
740 let handle = s.spawn(|| {
741 Python::with_gil(|py| {
742 init.call_once_py_attached(py, || panic!());
743 })
744 });
745 assert!(handle.join().is_err());
746
747 // poisoning propagates
748 let handle = s.spawn(|| {
749 Python::with_gil(|py| {
750 init.call_once_py_attached(py, || {});
751 });
752 });
753
754 assert!(handle.join().is_err());
755
756 // call_once_force will still run and reset the poisoned state
757 Python::with_gil(|py| {
758 init.call_once_force_py_attached(py, |state| {
759 assert!(state.is_poisoned());
760 });
761
762 // once any success happens, we stop propagating the poison
763 init.call_once_py_attached(py, || {});
764 });
765 });
766 }
767
768 #[cfg(rustc_has_once_lock)]
769 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
770 #[test]
771 fn test_once_lock_ext() {
772 let cell = std::sync::OnceLock::new();
773 std::thread::scope(|s| {
774 assert!(cell.get().is_none());
775
776 s.spawn(|| {
777 Python::with_gil(|py| {
778 assert_eq!(*cell.get_or_init_py_attached(py, || 12345), 12345);
779 });
780 });
781 });
782 assert_eq!(cell.get(), Some(&12345));
783 }
784}