pyo3/
gil.rs

1//! Interaction with Python's global interpreter lock
2
3#[cfg(feature = "gil-refs")]
4use crate::impl_::not_send::{NotSend, NOT_SEND};
5#[cfg(pyo3_disable_reference_pool)]
6use crate::impl_::panic::PanicTrap;
7use crate::{ffi, Python};
8#[cfg(not(pyo3_disable_reference_pool))]
9use once_cell::sync::Lazy;
10use std::cell::Cell;
11#[cfg(all(feature = "gil-refs", debug_assertions))]
12use std::cell::RefCell;
13#[cfg(all(feature = "gil-refs", not(debug_assertions)))]
14use std::cell::UnsafeCell;
15use std::{mem, ptr::NonNull, sync};
16
17static START: sync::Once = sync::Once::new();
18
19std::thread_local! {
20    /// This is an internal counter in pyo3 monitoring whether this thread has the GIL.
21    ///
22    /// It will be incremented whenever a GILGuard or GILPool is created, and decremented whenever
23    /// they are dropped.
24    ///
25    /// As a result, if this thread has the GIL, GIL_COUNT is greater than zero.
26    ///
27    /// Additionally, we sometimes need to prevent safe access to the GIL,
28    /// e.g. when implementing `__traverse__`, which is represented by a negative value.
29    static GIL_COUNT: Cell<isize> = const { Cell::new(0) };
30
31    /// Temporarily hold objects that will be released when the GILPool drops.
32    #[cfg(all(feature = "gil-refs", debug_assertions))]
33    static OWNED_OBJECTS: RefCell<PyObjVec> = const { RefCell::new(Vec::new()) };
34    #[cfg(all(feature = "gil-refs", not(debug_assertions)))]
35    static OWNED_OBJECTS: UnsafeCell<PyObjVec> = const { UnsafeCell::new(Vec::new()) };
36}
37
38const GIL_LOCKED_DURING_TRAVERSE: isize = -1;
39
40/// Checks whether the GIL is acquired.
41///
42/// Note: This uses pyo3's internal count rather than PyGILState_Check for two reasons:
43///  1) for performance
44///  2) PyGILState_Check always returns 1 if the sub-interpreter APIs have ever been called,
45///     which could lead to incorrect conclusions that the GIL is held.
46#[inline(always)]
47fn gil_is_acquired() -> bool {
48    GIL_COUNT.try_with(|c| c.get() > 0).unwrap_or(false)
49}
50
51/// Prepares the use of Python in a free-threaded context.
52///
53/// If the Python interpreter is not already initialized, this function will initialize it with
54/// signal handling disabled (Python will not raise the `KeyboardInterrupt` exception). Python
55/// signal handling depends on the notion of a 'main thread', which must be the thread that
56/// initializes the Python interpreter.
57///
58/// If the Python interpreter is already initialized, this function has no effect.
59///
60/// This function is unavailable under PyPy because PyPy cannot be embedded in Rust (or any other
61/// software). Support for this is tracked on the
62/// [PyPy issue tracker](https://github.com/pypy/pypy/issues/3836).
63///
64/// # Examples
65/// ```rust
66/// use pyo3::prelude::*;
67///
68/// # fn main() -> PyResult<()> {
69/// pyo3::prepare_freethreaded_python();
70/// Python::with_gil(|py| py.run_bound("print('Hello World')", None, None))
71/// # }
72/// ```
73#[cfg(not(any(PyPy, GraalPy)))]
74pub fn prepare_freethreaded_python() {
75    // Protect against race conditions when Python is not yet initialized and multiple threads
76    // concurrently call 'prepare_freethreaded_python()'. Note that we do not protect against
77    // concurrent initialization of the Python runtime by other users of the Python C API.
78    START.call_once_force(|_| unsafe {
79        // Use call_once_force because if initialization panics, it's okay to try again.
80        if ffi::Py_IsInitialized() == 0 {
81            ffi::Py_InitializeEx(0);
82
83            // Release the GIL.
84            ffi::PyEval_SaveThread();
85        }
86    });
87}
88
89/// Executes the provided closure with an embedded Python interpreter.
90///
91/// This function initializes the Python interpreter, executes the provided closure, and then
92/// finalizes the Python interpreter.
93///
94/// After execution all Python resources are cleaned up, and no further Python APIs can be called.
95/// Because many Python modules implemented in C do not support multiple Python interpreters in a
96/// single process, it is not safe to call this function more than once. (Many such modules will not
97/// initialize correctly on the second run.)
98///
99/// # Panics
100/// - If the Python interpreter is already initialized before calling this function.
101///
102/// # Safety
103/// - This function should only ever be called once per process (usually as part of the `main`
104///   function). It is also not thread-safe.
105/// - No Python APIs can be used after this function has finished executing.
106/// - The return value of the closure must not contain any Python value, _including_ `PyResult`.
107///
108/// # Examples
109///
110/// ```rust
111/// unsafe {
112///     pyo3::with_embedded_python_interpreter(|py| {
113///         if let Err(e) = py.run_bound("print('Hello World')", None, None) {
114///             // We must make sure to not return a `PyErr`!
115///             e.print(py);
116///         }
117///     });
118/// }
119/// ```
120#[cfg(not(any(PyPy, GraalPy)))]
121pub unsafe fn with_embedded_python_interpreter<F, R>(f: F) -> R
122where
123    F: for<'p> FnOnce(Python<'p>) -> R,
124{
125    assert_eq!(
126        ffi::Py_IsInitialized(),
127        0,
128        "called `with_embedded_python_interpreter` but a Python interpreter is already running."
129    );
130
131    ffi::Py_InitializeEx(0);
132
133    let result = {
134        let guard = GILGuard::assume();
135        let py = guard.python();
136        // Import the threading module - this ensures that it will associate this thread as the "main"
137        // thread, which is important to avoid an `AssertionError` at finalization.
138        py.import_bound("threading").unwrap();
139
140        // Execute the closure.
141        f(py)
142    };
143
144    // Finalize the Python interpreter.
145    ffi::Py_Finalize();
146
147    result
148}
149
150/// RAII type that represents the Global Interpreter Lock acquisition.
151pub(crate) enum GILGuard {
152    /// Indicates the GIL was already held with this GILGuard was acquired.
153    Assumed,
154    /// Indicates that we actually acquired the GIL when this GILGuard was acquired
155    Ensured {
156        gstate: ffi::PyGILState_STATE,
157        #[cfg(feature = "gil-refs")]
158        #[allow(deprecated)]
159        pool: mem::ManuallyDrop<GILPool>,
160    },
161}
162
163impl GILGuard {
164    /// PyO3 internal API for acquiring the GIL. The public API is Python::with_gil.
165    ///
166    /// If the GIL was already acquired via PyO3, this returns
167    /// `GILGuard::Assumed`. Otherwise, the GIL will be acquired and
168    /// `GILGuard::Ensured` will be returned.
169    pub(crate) fn acquire() -> Self {
170        if gil_is_acquired() {
171            // SAFETY: We just checked that the GIL is already acquired.
172            return unsafe { Self::assume() };
173        }
174
175        // Maybe auto-initialize the GIL:
176        //  - If auto-initialize feature set and supported, try to initialize the interpreter.
177        //  - If the auto-initialize feature is set but unsupported, emit hard errors only when the
178        //    extension-module feature is not activated - extension modules don't care about
179        //    auto-initialize so this avoids breaking existing builds.
180        //  - Otherwise, just check the GIL is initialized.
181        cfg_if::cfg_if! {
182            if #[cfg(all(feature = "auto-initialize", not(any(PyPy, GraalPy))))] {
183                prepare_freethreaded_python();
184            } else {
185                // This is a "hack" to make running `cargo test` for PyO3 convenient (i.e. no need
186                // to specify `--features auto-initialize` manually. Tests within the crate itself
187                // all depend on the auto-initialize feature for conciseness but Cargo does not
188                // provide a mechanism to specify required features for tests.
189                #[cfg(not(any(PyPy, GraalPy)))]
190                if option_env!("CARGO_PRIMARY_PACKAGE").is_some() {
191                    prepare_freethreaded_python();
192                }
193
194                START.call_once_force(|_| unsafe {
195                    // Use call_once_force because if there is a panic because the interpreter is
196                    // not initialized, it's fine for the user to initialize the interpreter and
197                    // retry.
198                    assert_ne!(
199                        ffi::Py_IsInitialized(),
200                        0,
201                        "The Python interpreter is not initialized and the `auto-initialize` \
202                         feature is not enabled.\n\n\
203                         Consider calling `pyo3::prepare_freethreaded_python()` before attempting \
204                         to use Python APIs."
205                    );
206                });
207            }
208        }
209
210        // SAFETY: We have ensured the Python interpreter is initialized.
211        unsafe { Self::acquire_unchecked() }
212    }
213
214    /// Acquires the `GILGuard` without performing any state checking.
215    ///
216    /// This can be called in "unsafe" contexts where the normal interpreter state
217    /// checking performed by `GILGuard::acquire` may fail. This includes calling
218    /// as part of multi-phase interpreter initialization.
219    pub(crate) unsafe fn acquire_unchecked() -> Self {
220        if gil_is_acquired() {
221            return Self::assume();
222        }
223
224        let gstate = ffi::PyGILState_Ensure(); // acquire GIL
225        increment_gil_count();
226
227        #[cfg(feature = "gil-refs")]
228        #[allow(deprecated)]
229        let pool = mem::ManuallyDrop::new(GILPool::new());
230
231        #[cfg(not(pyo3_disable_reference_pool))]
232        if let Some(pool) = Lazy::get(&POOL) {
233            pool.update_counts(Python::assume_gil_acquired());
234        }
235        GILGuard::Ensured {
236            gstate,
237            #[cfg(feature = "gil-refs")]
238            pool,
239        }
240    }
241
242    /// Acquires the `GILGuard` while assuming that the GIL is already held.
243    pub(crate) unsafe fn assume() -> Self {
244        increment_gil_count();
245        let guard = GILGuard::Assumed;
246        #[cfg(not(pyo3_disable_reference_pool))]
247        if let Some(pool) = Lazy::get(&POOL) {
248            pool.update_counts(guard.python());
249        }
250        guard
251    }
252
253    /// Gets the Python token associated with this [`GILGuard`].
254    #[inline]
255    pub fn python(&self) -> Python<'_> {
256        unsafe { Python::assume_gil_acquired() }
257    }
258}
259
260/// The Drop implementation for `GILGuard` will release the GIL.
261impl Drop for GILGuard {
262    fn drop(&mut self) {
263        match self {
264            GILGuard::Assumed => {}
265            GILGuard::Ensured {
266                gstate,
267                #[cfg(feature = "gil-refs")]
268                pool,
269            } => unsafe {
270                // Drop the objects in the pool before attempting to release the thread state
271                #[cfg(feature = "gil-refs")]
272                mem::ManuallyDrop::drop(pool);
273                ffi::PyGILState_Release(*gstate);
274            },
275        }
276        decrement_gil_count();
277    }
278}
279
280// Vector of PyObject
281type PyObjVec = Vec<NonNull<ffi::PyObject>>;
282
283#[cfg(not(pyo3_disable_reference_pool))]
284/// Thread-safe storage for objects which were dec_ref while the GIL was not held.
285struct ReferencePool {
286    pending_decrefs: sync::Mutex<PyObjVec>,
287}
288
289#[cfg(not(pyo3_disable_reference_pool))]
290impl ReferencePool {
291    const fn new() -> Self {
292        Self {
293            pending_decrefs: sync::Mutex::new(Vec::new()),
294        }
295    }
296
297    fn register_decref(&self, obj: NonNull<ffi::PyObject>) {
298        self.pending_decrefs.lock().unwrap().push(obj);
299    }
300
301    fn update_counts(&self, _py: Python<'_>) {
302        let mut pending_decrefs = self.pending_decrefs.lock().unwrap();
303        if pending_decrefs.is_empty() {
304            return;
305        }
306
307        let decrefs = mem::take(&mut *pending_decrefs);
308        drop(pending_decrefs);
309
310        for ptr in decrefs {
311            unsafe { ffi::Py_DECREF(ptr.as_ptr()) };
312        }
313    }
314}
315
316#[cfg(not(pyo3_disable_reference_pool))]
317unsafe impl Send for ReferencePool {}
318
319#[cfg(not(pyo3_disable_reference_pool))]
320unsafe impl Sync for ReferencePool {}
321
322#[cfg(not(pyo3_disable_reference_pool))]
323static POOL: Lazy<ReferencePool> = Lazy::new(ReferencePool::new);
324
325/// A guard which can be used to temporarily release the GIL and restore on `Drop`.
326pub(crate) struct SuspendGIL {
327    count: isize,
328    tstate: *mut ffi::PyThreadState,
329}
330
331impl SuspendGIL {
332    pub(crate) unsafe fn new() -> Self {
333        let count = GIL_COUNT.with(|c| c.replace(0));
334        let tstate = ffi::PyEval_SaveThread();
335
336        Self { count, tstate }
337    }
338}
339
340impl Drop for SuspendGIL {
341    fn drop(&mut self) {
342        GIL_COUNT.with(|c| c.set(self.count));
343        unsafe {
344            ffi::PyEval_RestoreThread(self.tstate);
345
346            // Update counts of PyObjects / Py that were cloned or dropped while the GIL was released.
347            #[cfg(not(pyo3_disable_reference_pool))]
348            if let Some(pool) = Lazy::get(&POOL) {
349                pool.update_counts(Python::assume_gil_acquired());
350            }
351        }
352    }
353}
354
355/// Used to lock safe access to the GIL
356pub(crate) struct LockGIL {
357    count: isize,
358}
359
360impl LockGIL {
361    /// Lock access to the GIL while an implementation of `__traverse__` is running
362    pub fn during_traverse() -> Self {
363        Self::new(GIL_LOCKED_DURING_TRAVERSE)
364    }
365
366    fn new(reason: isize) -> Self {
367        let count = GIL_COUNT.with(|c| c.replace(reason));
368
369        Self { count }
370    }
371
372    #[cold]
373    fn bail(current: isize) {
374        match current {
375            GIL_LOCKED_DURING_TRAVERSE => panic!(
376                "Access to the GIL is prohibited while a __traverse__ implmentation is running."
377            ),
378            _ => panic!("Access to the GIL is currently prohibited."),
379        }
380    }
381}
382
383impl Drop for LockGIL {
384    fn drop(&mut self) {
385        GIL_COUNT.with(|c| c.set(self.count));
386    }
387}
388
389/// A RAII pool which PyO3 uses to store owned Python references.
390///
391/// See the [Memory Management] chapter of the guide for more information about how PyO3 uses
392/// [`GILPool`] to manage memory.
393
394///
395/// [Memory Management]: https://pyo3.rs/main/memory.html#gil-bound-memory
396#[cfg(feature = "gil-refs")]
397#[deprecated(
398    since = "0.21.0",
399    note = "`GILPool` has no function if PyO3's deprecated GIL Refs API is not used"
400)]
401pub struct GILPool {
402    /// Initial length of owned objects and anys.
403    /// `Option` is used since TSL can be broken when `new` is called from `atexit`.
404    start: Option<usize>,
405    _not_send: NotSend,
406}
407
408#[cfg(feature = "gil-refs")]
409#[allow(deprecated)]
410impl GILPool {
411    /// Creates a new [`GILPool`]. This function should only ever be called with the GIL held.
412    ///
413    /// It is recommended not to use this API directly, but instead to use `Python::new_pool`, as
414    /// that guarantees the GIL is held.
415    ///
416    /// # Safety
417    ///
418    /// As well as requiring the GIL, see the safety notes on `Python::new_pool`.
419    #[inline]
420    pub unsafe fn new() -> GILPool {
421        // Update counts of PyObjects / Py that have been cloned or dropped since last acquisition
422        #[cfg(not(pyo3_disable_reference_pool))]
423        if let Some(pool) = Lazy::get(&POOL) {
424            pool.update_counts(Python::assume_gil_acquired());
425        }
426        GILPool {
427            start: OWNED_OBJECTS
428                .try_with(|owned_objects| {
429                    #[cfg(debug_assertions)]
430                    let len = owned_objects.borrow().len();
431                    #[cfg(not(debug_assertions))]
432                    // SAFETY: This is not re-entrant.
433                    let len = unsafe { (*owned_objects.get()).len() };
434                    len
435                })
436                .ok(),
437            _not_send: NOT_SEND,
438        }
439    }
440
441    /// Gets the Python token associated with this [`GILPool`].
442    #[inline]
443    pub fn python(&self) -> Python<'_> {
444        unsafe { Python::assume_gil_acquired() }
445    }
446}
447
448#[cfg(feature = "gil-refs")]
449#[allow(deprecated)]
450impl Drop for GILPool {
451    fn drop(&mut self) {
452        if let Some(start) = self.start {
453            let owned_objects = OWNED_OBJECTS.with(|owned_objects| {
454                #[cfg(debug_assertions)]
455                let mut owned_objects = owned_objects.borrow_mut();
456                #[cfg(not(debug_assertions))]
457                // SAFETY: `OWNED_OBJECTS` is released before calling Py_DECREF,
458                // or Py_DECREF may call `GILPool::drop` recursively, resulting in invalid borrowing.
459                let owned_objects = unsafe { &mut *owned_objects.get() };
460                if start < owned_objects.len() {
461                    owned_objects.split_off(start)
462                } else {
463                    Vec::new()
464                }
465            });
466            for obj in owned_objects {
467                unsafe {
468                    ffi::Py_DECREF(obj.as_ptr());
469                }
470            }
471        }
472    }
473}
474
475/// Increments the reference count of a Python object if the GIL is held. If
476/// the GIL is not held, this function will panic.
477///
478/// # Safety
479/// The object must be an owned Python reference.
480#[cfg(feature = "py-clone")]
481#[track_caller]
482pub unsafe fn register_incref(obj: NonNull<ffi::PyObject>) {
483    if gil_is_acquired() {
484        ffi::Py_INCREF(obj.as_ptr())
485    } else {
486        panic!("Cannot clone pointer into Python heap without the GIL being held.");
487    }
488}
489
490/// Registers a Python object pointer inside the release pool, to have its reference count decreased
491/// the next time the GIL is acquired in pyo3.
492///
493/// If the GIL is held, the reference count will be decreased immediately instead of being queued
494/// for later.
495///
496/// # Safety
497/// The object must be an owned Python reference.
498#[track_caller]
499pub unsafe fn register_decref(obj: NonNull<ffi::PyObject>) {
500    if gil_is_acquired() {
501        ffi::Py_DECREF(obj.as_ptr())
502    } else {
503        #[cfg(not(pyo3_disable_reference_pool))]
504        POOL.register_decref(obj);
505        #[cfg(all(
506            pyo3_disable_reference_pool,
507            not(pyo3_leak_on_drop_without_reference_pool)
508        ))]
509        {
510            let _trap = PanicTrap::new("Aborting the process to avoid panic-from-drop.");
511            panic!("Cannot drop pointer into Python heap without the GIL being held.");
512        }
513    }
514}
515
516/// Registers an owned object inside the GILPool, to be released when the GILPool drops.
517///
518/// # Safety
519/// The object must be an owned Python reference.
520#[cfg(feature = "gil-refs")]
521pub unsafe fn register_owned(_py: Python<'_>, obj: NonNull<ffi::PyObject>) {
522    debug_assert!(gil_is_acquired());
523    // Ignores the error in case this function called from `atexit`.
524    let _ = OWNED_OBJECTS.try_with(|owned_objects| {
525        #[cfg(debug_assertions)]
526        owned_objects.borrow_mut().push(obj);
527        #[cfg(not(debug_assertions))]
528        // SAFETY: This is not re-entrant.
529        unsafe {
530            (*owned_objects.get()).push(obj);
531        }
532    });
533}
534
535/// Increments pyo3's internal GIL count - to be called whenever GILPool or GILGuard is created.
536#[inline(always)]
537fn increment_gil_count() {
538    // Ignores the error in case this function called from `atexit`.
539    let _ = GIL_COUNT.try_with(|c| {
540        let current = c.get();
541        if current < 0 {
542            LockGIL::bail(current);
543        }
544        c.set(current + 1);
545    });
546}
547
548/// Decrements pyo3's internal GIL count - to be called whenever GILPool or GILGuard is dropped.
549#[inline(always)]
550fn decrement_gil_count() {
551    // Ignores the error in case this function called from `atexit`.
552    let _ = GIL_COUNT.try_with(|c| {
553        let current = c.get();
554        debug_assert!(
555            current > 0,
556            "Negative GIL count detected. Please report this error to the PyO3 repo as a bug."
557        );
558        c.set(current - 1);
559    });
560}
561
562#[cfg(test)]
563mod tests {
564    use super::GIL_COUNT;
565    #[cfg(feature = "gil-refs")]
566    #[allow(deprecated)]
567    use super::OWNED_OBJECTS;
568    #[cfg(not(pyo3_disable_reference_pool))]
569    use super::{gil_is_acquired, POOL};
570    #[cfg(feature = "gil-refs")]
571    use crate::{ffi, gil};
572    use crate::{gil::GILGuard, types::any::PyAnyMethods};
573    use crate::{PyObject, Python};
574    use std::ptr::NonNull;
575
576    fn get_object(py: Python<'_>) -> PyObject {
577        py.eval_bound("object()", None, None).unwrap().unbind()
578    }
579
580    #[cfg(feature = "gil-refs")]
581    fn owned_object_count() -> usize {
582        #[cfg(debug_assertions)]
583        let len = OWNED_OBJECTS.with(|owned_objects| owned_objects.borrow().len());
584        #[cfg(not(debug_assertions))]
585        let len = OWNED_OBJECTS.with(|owned_objects| unsafe { (*owned_objects.get()).len() });
586        len
587    }
588
589    #[cfg(not(pyo3_disable_reference_pool))]
590    fn pool_dec_refs_does_not_contain(obj: &PyObject) -> bool {
591        !POOL
592            .pending_decrefs
593            .lock()
594            .unwrap()
595            .contains(&unsafe { NonNull::new_unchecked(obj.as_ptr()) })
596    }
597
598    #[cfg(not(pyo3_disable_reference_pool))]
599    fn pool_dec_refs_contains(obj: &PyObject) -> bool {
600        POOL.pending_decrefs
601            .lock()
602            .unwrap()
603            .contains(&unsafe { NonNull::new_unchecked(obj.as_ptr()) })
604    }
605
606    #[test]
607    #[cfg(feature = "gil-refs")]
608    #[allow(deprecated)]
609    fn test_owned() {
610        Python::with_gil(|py| {
611            let obj = get_object(py);
612            let obj_ptr = obj.as_ptr();
613            // Ensure that obj does not get freed
614            let _ref = obj.clone_ref(py);
615
616            unsafe {
617                {
618                    let pool = py.new_pool();
619                    gil::register_owned(pool.python(), NonNull::new_unchecked(obj.into_ptr()));
620
621                    assert_eq!(owned_object_count(), 1);
622                    assert_eq!(ffi::Py_REFCNT(obj_ptr), 2);
623                }
624                {
625                    let _pool = py.new_pool();
626                    assert_eq!(owned_object_count(), 0);
627                    assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
628                }
629            }
630        })
631    }
632
633    #[test]
634    #[cfg(feature = "gil-refs")]
635    #[allow(deprecated)]
636    fn test_owned_nested() {
637        Python::with_gil(|py| {
638            let obj = get_object(py);
639            // Ensure that obj does not get freed
640            let _ref = obj.clone_ref(py);
641            let obj_ptr = obj.as_ptr();
642
643            unsafe {
644                {
645                    let _pool = py.new_pool();
646                    assert_eq!(owned_object_count(), 0);
647
648                    gil::register_owned(py, NonNull::new_unchecked(obj.into_ptr()));
649
650                    assert_eq!(owned_object_count(), 1);
651                    assert_eq!(ffi::Py_REFCNT(obj_ptr), 2);
652                    {
653                        let _pool = py.new_pool();
654                        let obj = get_object(py);
655                        gil::register_owned(py, NonNull::new_unchecked(obj.into_ptr()));
656                        assert_eq!(owned_object_count(), 2);
657                    }
658                    assert_eq!(owned_object_count(), 1);
659                }
660                {
661                    assert_eq!(owned_object_count(), 0);
662                    assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
663                }
664            }
665        });
666    }
667
668    #[test]
669    fn test_pyobject_drop_with_gil_decreases_refcnt() {
670        Python::with_gil(|py| {
671            let obj = get_object(py);
672
673            // Create a reference to drop with the GIL.
674            let reference = obj.clone_ref(py);
675
676            assert_eq!(obj.get_refcnt(py), 2);
677            #[cfg(not(pyo3_disable_reference_pool))]
678            assert!(pool_dec_refs_does_not_contain(&obj));
679
680            // With the GIL held, reference count will be decreased immediately.
681            drop(reference);
682
683            assert_eq!(obj.get_refcnt(py), 1);
684            #[cfg(not(pyo3_disable_reference_pool))]
685            assert!(pool_dec_refs_does_not_contain(&obj));
686        });
687    }
688
689    #[test]
690    #[cfg(all(not(pyo3_disable_reference_pool), not(target_arch = "wasm32")))] // We are building wasm Python with pthreads disabled
691    fn test_pyobject_drop_without_gil_doesnt_decrease_refcnt() {
692        let obj = Python::with_gil(|py| {
693            let obj = get_object(py);
694            // Create a reference to drop without the GIL.
695            let reference = obj.clone_ref(py);
696
697            assert_eq!(obj.get_refcnt(py), 2);
698            assert!(pool_dec_refs_does_not_contain(&obj));
699
700            // Drop reference in a separate thread which doesn't have the GIL.
701            std::thread::spawn(move || drop(reference)).join().unwrap();
702
703            // The reference count should not have changed (the GIL has always
704            // been held by this thread), it is remembered to release later.
705            assert_eq!(obj.get_refcnt(py), 2);
706            assert!(pool_dec_refs_contains(&obj));
707            obj
708        });
709
710        // Next time the GIL is acquired, the reference is released
711        Python::with_gil(|py| {
712            assert_eq!(obj.get_refcnt(py), 1);
713            assert!(pool_dec_refs_does_not_contain(&obj));
714        });
715    }
716
717    #[test]
718    #[allow(deprecated)]
719    fn test_gil_counts() {
720        // Check with_gil and GILGuard both increase counts correctly
721        let get_gil_count = || GIL_COUNT.with(|c| c.get());
722
723        assert_eq!(get_gil_count(), 0);
724        Python::with_gil(|_| {
725            assert_eq!(get_gil_count(), 1);
726
727            let pool = unsafe { GILGuard::assume() };
728            assert_eq!(get_gil_count(), 2);
729
730            let pool2 = unsafe { GILGuard::assume() };
731            assert_eq!(get_gil_count(), 3);
732
733            drop(pool);
734            assert_eq!(get_gil_count(), 2);
735
736            Python::with_gil(|_| {
737                // nested with_gil updates gil count
738                assert_eq!(get_gil_count(), 3);
739            });
740            assert_eq!(get_gil_count(), 2);
741
742            drop(pool2);
743            assert_eq!(get_gil_count(), 1);
744        });
745        assert_eq!(get_gil_count(), 0);
746    }
747
748    #[test]
749    fn test_allow_threads() {
750        assert!(!gil_is_acquired());
751
752        Python::with_gil(|py| {
753            assert!(gil_is_acquired());
754
755            py.allow_threads(move || {
756                assert!(!gil_is_acquired());
757
758                Python::with_gil(|_| assert!(gil_is_acquired()));
759
760                assert!(!gil_is_acquired());
761            });
762
763            assert!(gil_is_acquired());
764        });
765
766        assert!(!gil_is_acquired());
767    }
768
769    #[cfg(feature = "py-clone")]
770    #[test]
771    #[should_panic]
772    fn test_allow_threads_updates_refcounts() {
773        Python::with_gil(|py| {
774            // Make a simple object with 1 reference
775            let obj = get_object(py);
776            assert!(obj.get_refcnt(py) == 1);
777            // Clone the object without the GIL which should panic
778            py.allow_threads(|| obj.clone());
779        });
780    }
781
782    #[test]
783    fn dropping_gil_does_not_invalidate_references() {
784        // Acquiring GIL for the second time should be safe - see #864
785        Python::with_gil(|py| {
786            let obj = Python::with_gil(|_| py.eval_bound("object()", None, None).unwrap());
787
788            // After gil2 drops, obj should still have a reference count of one
789            assert_eq!(obj.get_refcnt(), 1);
790        })
791    }
792
793    #[cfg(feature = "py-clone")]
794    #[test]
795    fn test_clone_with_gil() {
796        Python::with_gil(|py| {
797            let obj = get_object(py);
798            let count = obj.get_refcnt(py);
799
800            // Cloning with the GIL should increase reference count immediately
801            #[allow(clippy::redundant_clone)]
802            let c = obj.clone();
803            assert_eq!(count + 1, c.get_refcnt(py));
804        })
805    }
806
807    #[test]
808    #[cfg(not(pyo3_disable_reference_pool))]
809    fn test_update_counts_does_not_deadlock() {
810        // update_counts can run arbitrary Python code during Py_DECREF.
811        // if the locking is implemented incorrectly, it will deadlock.
812
813        use crate::ffi;
814        use crate::gil::GILGuard;
815
816        Python::with_gil(|py| {
817            let obj = get_object(py);
818
819            unsafe extern "C" fn capsule_drop(capsule: *mut ffi::PyObject) {
820                // This line will implicitly call update_counts
821                // -> and so cause deadlock if update_counts is not handling recursion correctly.
822                let pool = GILGuard::assume();
823
824                // Rebuild obj so that it can be dropped
825                PyObject::from_owned_ptr(
826                    pool.python(),
827                    ffi::PyCapsule_GetPointer(capsule, std::ptr::null()) as _,
828                );
829            }
830
831            let ptr = obj.into_ptr();
832
833            let capsule =
834                unsafe { ffi::PyCapsule_New(ptr as _, std::ptr::null(), Some(capsule_drop)) };
835
836            POOL.register_decref(NonNull::new(capsule).unwrap());
837
838            // Updating the counts will call decref on the capsule, which calls capsule_drop
839            POOL.update_counts(py);
840        })
841    }
842
843    #[test]
844    #[cfg(not(pyo3_disable_reference_pool))]
845    fn test_gil_guard_update_counts() {
846        use crate::gil::GILGuard;
847
848        Python::with_gil(|py| {
849            let obj = get_object(py);
850
851            // For GILGuard::acquire
852
853            POOL.register_decref(NonNull::new(obj.clone_ref(py).into_ptr()).unwrap());
854            assert!(pool_dec_refs_contains(&obj));
855            let _guard = GILGuard::acquire();
856            assert!(pool_dec_refs_does_not_contain(&obj));
857
858            // For GILGuard::assume
859
860            POOL.register_decref(NonNull::new(obj.clone_ref(py).into_ptr()).unwrap());
861            assert!(pool_dec_refs_contains(&obj));
862            let _guard2 = unsafe { GILGuard::assume() };
863            assert!(pool_dec_refs_does_not_contain(&obj));
864        })
865    }
866}