#[cfg(feature = "gil-refs")]
use crate::impl_::not_send::{NotSend, NOT_SEND};
#[cfg(pyo3_disable_reference_pool)]
use crate::impl_::panic::PanicTrap;
use crate::{ffi, Python};
#[cfg(not(pyo3_disable_reference_pool))]
use once_cell::sync::Lazy;
use std::cell::Cell;
#[cfg(all(feature = "gil-refs", debug_assertions))]
use std::cell::RefCell;
#[cfg(all(feature = "gil-refs", not(debug_assertions)))]
use std::cell::UnsafeCell;
use std::{mem, ptr::NonNull, sync};
static START: sync::Once = sync::Once::new();
std::thread_local! {
static GIL_COUNT: Cell<isize> = const { Cell::new(0) };
#[cfg(all(feature = "gil-refs", debug_assertions))]
static OWNED_OBJECTS: RefCell<PyObjVec> = const { RefCell::new(Vec::new()) };
#[cfg(all(feature = "gil-refs", not(debug_assertions)))]
static OWNED_OBJECTS: UnsafeCell<PyObjVec> = const { UnsafeCell::new(Vec::new()) };
}
const GIL_LOCKED_DURING_TRAVERSE: isize = -1;
#[inline(always)]
fn gil_is_acquired() -> bool {
GIL_COUNT.try_with(|c| c.get() > 0).unwrap_or(false)
}
#[cfg(not(any(PyPy, GraalPy)))]
pub fn prepare_freethreaded_python() {
START.call_once_force(|_| unsafe {
if ffi::Py_IsInitialized() == 0 {
ffi::Py_InitializeEx(0);
ffi::PyEval_SaveThread();
}
});
}
#[cfg(not(any(PyPy, GraalPy)))]
pub unsafe fn with_embedded_python_interpreter<F, R>(f: F) -> R
where
F: for<'p> FnOnce(Python<'p>) -> R,
{
assert_eq!(
ffi::Py_IsInitialized(),
0,
"called `with_embedded_python_interpreter` but a Python interpreter is already running."
);
ffi::Py_InitializeEx(0);
let result = {
let guard = GILGuard::assume();
let py = guard.python();
py.import_bound("threading").unwrap();
f(py)
};
ffi::Py_Finalize();
result
}
pub(crate) enum GILGuard {
Assumed,
Ensured {
gstate: ffi::PyGILState_STATE,
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
pool: mem::ManuallyDrop<GILPool>,
},
}
impl GILGuard {
pub(crate) fn acquire() -> Self {
if gil_is_acquired() {
return unsafe { Self::assume() };
}
cfg_if::cfg_if! {
if #[cfg(all(feature = "auto-initialize", not(any(PyPy, GraalPy))))] {
prepare_freethreaded_python();
} else {
#[cfg(not(any(PyPy, GraalPy)))]
if option_env!("CARGO_PRIMARY_PACKAGE").is_some() {
prepare_freethreaded_python();
}
START.call_once_force(|_| unsafe {
assert_ne!(
ffi::Py_IsInitialized(),
0,
"The Python interpreter is not initialized and the `auto-initialize` \
feature is not enabled.\n\n\
Consider calling `pyo3::prepare_freethreaded_python()` before attempting \
to use Python APIs."
);
});
}
}
unsafe { Self::acquire_unchecked() }
}
pub(crate) unsafe fn acquire_unchecked() -> Self {
if gil_is_acquired() {
return Self::assume();
}
let gstate = ffi::PyGILState_Ensure(); increment_gil_count();
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
let pool = mem::ManuallyDrop::new(GILPool::new());
#[cfg(not(pyo3_disable_reference_pool))]
if let Some(pool) = Lazy::get(&POOL) {
pool.update_counts(Python::assume_gil_acquired());
}
GILGuard::Ensured {
gstate,
#[cfg(feature = "gil-refs")]
pool,
}
}
pub(crate) unsafe fn assume() -> Self {
increment_gil_count();
let guard = GILGuard::Assumed;
#[cfg(not(pyo3_disable_reference_pool))]
if let Some(pool) = Lazy::get(&POOL) {
pool.update_counts(guard.python());
}
guard
}
#[inline]
pub fn python(&self) -> Python<'_> {
unsafe { Python::assume_gil_acquired() }
}
}
impl Drop for GILGuard {
fn drop(&mut self) {
match self {
GILGuard::Assumed => {}
GILGuard::Ensured {
gstate,
#[cfg(feature = "gil-refs")]
pool,
} => unsafe {
#[cfg(feature = "gil-refs")]
mem::ManuallyDrop::drop(pool);
ffi::PyGILState_Release(*gstate);
},
}
decrement_gil_count();
}
}
type PyObjVec = Vec<NonNull<ffi::PyObject>>;
#[cfg(not(pyo3_disable_reference_pool))]
struct ReferencePool {
pending_decrefs: sync::Mutex<PyObjVec>,
}
#[cfg(not(pyo3_disable_reference_pool))]
impl ReferencePool {
const fn new() -> Self {
Self {
pending_decrefs: sync::Mutex::new(Vec::new()),
}
}
fn register_decref(&self, obj: NonNull<ffi::PyObject>) {
self.pending_decrefs.lock().unwrap().push(obj);
}
fn update_counts(&self, _py: Python<'_>) {
let mut pending_decrefs = self.pending_decrefs.lock().unwrap();
if pending_decrefs.is_empty() {
return;
}
let decrefs = mem::take(&mut *pending_decrefs);
drop(pending_decrefs);
for ptr in decrefs {
unsafe { ffi::Py_DECREF(ptr.as_ptr()) };
}
}
}
#[cfg(not(pyo3_disable_reference_pool))]
unsafe impl Send for ReferencePool {}
#[cfg(not(pyo3_disable_reference_pool))]
unsafe impl Sync for ReferencePool {}
#[cfg(not(pyo3_disable_reference_pool))]
static POOL: Lazy<ReferencePool> = Lazy::new(ReferencePool::new);
pub(crate) struct SuspendGIL {
count: isize,
tstate: *mut ffi::PyThreadState,
}
impl SuspendGIL {
pub(crate) unsafe fn new() -> Self {
let count = GIL_COUNT.with(|c| c.replace(0));
let tstate = ffi::PyEval_SaveThread();
Self { count, tstate }
}
}
impl Drop for SuspendGIL {
fn drop(&mut self) {
GIL_COUNT.with(|c| c.set(self.count));
unsafe {
ffi::PyEval_RestoreThread(self.tstate);
#[cfg(not(pyo3_disable_reference_pool))]
if let Some(pool) = Lazy::get(&POOL) {
pool.update_counts(Python::assume_gil_acquired());
}
}
}
}
pub(crate) struct LockGIL {
count: isize,
}
impl LockGIL {
pub fn during_traverse() -> Self {
Self::new(GIL_LOCKED_DURING_TRAVERSE)
}
fn new(reason: isize) -> Self {
let count = GIL_COUNT.with(|c| c.replace(reason));
Self { count }
}
#[cold]
fn bail(current: isize) {
match current {
GIL_LOCKED_DURING_TRAVERSE => panic!(
"Access to the GIL is prohibited while a __traverse__ implmentation is running."
),
_ => panic!("Access to the GIL is currently prohibited."),
}
}
}
impl Drop for LockGIL {
fn drop(&mut self) {
GIL_COUNT.with(|c| c.set(self.count));
}
}
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`GILPool` has no function if PyO3's deprecated GIL Refs API is not used"
)]
pub struct GILPool {
start: Option<usize>,
_not_send: NotSend,
}
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
impl GILPool {
#[inline]
pub unsafe fn new() -> GILPool {
#[cfg(not(pyo3_disable_reference_pool))]
if let Some(pool) = Lazy::get(&POOL) {
pool.update_counts(Python::assume_gil_acquired());
}
GILPool {
start: OWNED_OBJECTS
.try_with(|owned_objects| {
#[cfg(debug_assertions)]
let len = owned_objects.borrow().len();
#[cfg(not(debug_assertions))]
let len = unsafe { (*owned_objects.get()).len() };
len
})
.ok(),
_not_send: NOT_SEND,
}
}
#[inline]
pub fn python(&self) -> Python<'_> {
unsafe { Python::assume_gil_acquired() }
}
}
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
impl Drop for GILPool {
fn drop(&mut self) {
if let Some(start) = self.start {
let owned_objects = OWNED_OBJECTS.with(|owned_objects| {
#[cfg(debug_assertions)]
let mut owned_objects = owned_objects.borrow_mut();
#[cfg(not(debug_assertions))]
let owned_objects = unsafe { &mut *owned_objects.get() };
if start < owned_objects.len() {
owned_objects.split_off(start)
} else {
Vec::new()
}
});
for obj in owned_objects {
unsafe {
ffi::Py_DECREF(obj.as_ptr());
}
}
}
}
}
#[cfg(feature = "py-clone")]
#[track_caller]
pub unsafe fn register_incref(obj: NonNull<ffi::PyObject>) {
if gil_is_acquired() {
ffi::Py_INCREF(obj.as_ptr())
} else {
panic!("Cannot clone pointer into Python heap without the GIL being held.");
}
}
#[track_caller]
pub unsafe fn register_decref(obj: NonNull<ffi::PyObject>) {
if gil_is_acquired() {
ffi::Py_DECREF(obj.as_ptr())
} else {
#[cfg(not(pyo3_disable_reference_pool))]
POOL.register_decref(obj);
#[cfg(all(
pyo3_disable_reference_pool,
not(pyo3_leak_on_drop_without_reference_pool)
))]
{
let _trap = PanicTrap::new("Aborting the process to avoid panic-from-drop.");
panic!("Cannot drop pointer into Python heap without the GIL being held.");
}
}
}
#[cfg(feature = "gil-refs")]
pub unsafe fn register_owned(_py: Python<'_>, obj: NonNull<ffi::PyObject>) {
debug_assert!(gil_is_acquired());
let _ = OWNED_OBJECTS.try_with(|owned_objects| {
#[cfg(debug_assertions)]
owned_objects.borrow_mut().push(obj);
#[cfg(not(debug_assertions))]
unsafe {
(*owned_objects.get()).push(obj);
}
});
}
#[inline(always)]
fn increment_gil_count() {
let _ = GIL_COUNT.try_with(|c| {
let current = c.get();
if current < 0 {
LockGIL::bail(current);
}
c.set(current + 1);
});
}
#[inline(always)]
fn decrement_gil_count() {
let _ = GIL_COUNT.try_with(|c| {
let current = c.get();
debug_assert!(
current > 0,
"Negative GIL count detected. Please report this error to the PyO3 repo as a bug."
);
c.set(current - 1);
});
}
#[cfg(test)]
mod tests {
use super::GIL_COUNT;
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
use super::OWNED_OBJECTS;
#[cfg(not(pyo3_disable_reference_pool))]
use super::{gil_is_acquired, POOL};
#[cfg(feature = "gil-refs")]
use crate::{ffi, gil};
use crate::{gil::GILGuard, types::any::PyAnyMethods};
use crate::{PyObject, Python};
use std::ptr::NonNull;
fn get_object(py: Python<'_>) -> PyObject {
py.eval_bound("object()", None, None).unwrap().unbind()
}
#[cfg(feature = "gil-refs")]
fn owned_object_count() -> usize {
#[cfg(debug_assertions)]
let len = OWNED_OBJECTS.with(|owned_objects| owned_objects.borrow().len());
#[cfg(not(debug_assertions))]
let len = OWNED_OBJECTS.with(|owned_objects| unsafe { (*owned_objects.get()).len() });
len
}
#[cfg(not(pyo3_disable_reference_pool))]
fn pool_dec_refs_does_not_contain(obj: &PyObject) -> bool {
!POOL
.pending_decrefs
.lock()
.unwrap()
.contains(&unsafe { NonNull::new_unchecked(obj.as_ptr()) })
}
#[cfg(not(pyo3_disable_reference_pool))]
fn pool_dec_refs_contains(obj: &PyObject) -> bool {
POOL.pending_decrefs
.lock()
.unwrap()
.contains(&unsafe { NonNull::new_unchecked(obj.as_ptr()) })
}
#[test]
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
fn test_owned() {
Python::with_gil(|py| {
let obj = get_object(py);
let obj_ptr = obj.as_ptr();
let _ref = obj.clone_ref(py);
unsafe {
{
let pool = py.new_pool();
gil::register_owned(pool.python(), NonNull::new_unchecked(obj.into_ptr()));
assert_eq!(owned_object_count(), 1);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 2);
}
{
let _pool = py.new_pool();
assert_eq!(owned_object_count(), 0);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
}
}
})
}
#[test]
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
fn test_owned_nested() {
Python::with_gil(|py| {
let obj = get_object(py);
let _ref = obj.clone_ref(py);
let obj_ptr = obj.as_ptr();
unsafe {
{
let _pool = py.new_pool();
assert_eq!(owned_object_count(), 0);
gil::register_owned(py, NonNull::new_unchecked(obj.into_ptr()));
assert_eq!(owned_object_count(), 1);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 2);
{
let _pool = py.new_pool();
let obj = get_object(py);
gil::register_owned(py, NonNull::new_unchecked(obj.into_ptr()));
assert_eq!(owned_object_count(), 2);
}
assert_eq!(owned_object_count(), 1);
}
{
assert_eq!(owned_object_count(), 0);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
}
}
});
}
#[test]
fn test_pyobject_drop_with_gil_decreases_refcnt() {
Python::with_gil(|py| {
let obj = get_object(py);
let reference = obj.clone_ref(py);
assert_eq!(obj.get_refcnt(py), 2);
#[cfg(not(pyo3_disable_reference_pool))]
assert!(pool_dec_refs_does_not_contain(&obj));
drop(reference);
assert_eq!(obj.get_refcnt(py), 1);
#[cfg(not(pyo3_disable_reference_pool))]
assert!(pool_dec_refs_does_not_contain(&obj));
});
}
#[test]
#[cfg(all(not(pyo3_disable_reference_pool), not(target_arch = "wasm32")))] fn test_pyobject_drop_without_gil_doesnt_decrease_refcnt() {
let obj = Python::with_gil(|py| {
let obj = get_object(py);
let reference = obj.clone_ref(py);
assert_eq!(obj.get_refcnt(py), 2);
assert!(pool_dec_refs_does_not_contain(&obj));
std::thread::spawn(move || drop(reference)).join().unwrap();
assert_eq!(obj.get_refcnt(py), 2);
assert!(pool_dec_refs_contains(&obj));
obj
});
Python::with_gil(|py| {
assert_eq!(obj.get_refcnt(py), 1);
assert!(pool_dec_refs_does_not_contain(&obj));
});
}
#[test]
#[allow(deprecated)]
fn test_gil_counts() {
let get_gil_count = || GIL_COUNT.with(|c| c.get());
assert_eq!(get_gil_count(), 0);
Python::with_gil(|_| {
assert_eq!(get_gil_count(), 1);
let pool = unsafe { GILGuard::assume() };
assert_eq!(get_gil_count(), 2);
let pool2 = unsafe { GILGuard::assume() };
assert_eq!(get_gil_count(), 3);
drop(pool);
assert_eq!(get_gil_count(), 2);
Python::with_gil(|_| {
assert_eq!(get_gil_count(), 3);
});
assert_eq!(get_gil_count(), 2);
drop(pool2);
assert_eq!(get_gil_count(), 1);
});
assert_eq!(get_gil_count(), 0);
}
#[test]
fn test_allow_threads() {
assert!(!gil_is_acquired());
Python::with_gil(|py| {
assert!(gil_is_acquired());
py.allow_threads(move || {
assert!(!gil_is_acquired());
Python::with_gil(|_| assert!(gil_is_acquired()));
assert!(!gil_is_acquired());
});
assert!(gil_is_acquired());
});
assert!(!gil_is_acquired());
}
#[cfg(feature = "py-clone")]
#[test]
#[should_panic]
fn test_allow_threads_updates_refcounts() {
Python::with_gil(|py| {
let obj = get_object(py);
assert!(obj.get_refcnt(py) == 1);
py.allow_threads(|| obj.clone());
});
}
#[test]
fn dropping_gil_does_not_invalidate_references() {
Python::with_gil(|py| {
let obj = Python::with_gil(|_| py.eval_bound("object()", None, None).unwrap());
assert_eq!(obj.get_refcnt(), 1);
})
}
#[cfg(feature = "py-clone")]
#[test]
fn test_clone_with_gil() {
Python::with_gil(|py| {
let obj = get_object(py);
let count = obj.get_refcnt(py);
#[allow(clippy::redundant_clone)]
let c = obj.clone();
assert_eq!(count + 1, c.get_refcnt(py));
})
}
#[test]
#[cfg(not(pyo3_disable_reference_pool))]
fn test_update_counts_does_not_deadlock() {
use crate::ffi;
use crate::gil::GILGuard;
Python::with_gil(|py| {
let obj = get_object(py);
unsafe extern "C" fn capsule_drop(capsule: *mut ffi::PyObject) {
let pool = GILGuard::assume();
PyObject::from_owned_ptr(
pool.python(),
ffi::PyCapsule_GetPointer(capsule, std::ptr::null()) as _,
);
}
let ptr = obj.into_ptr();
let capsule =
unsafe { ffi::PyCapsule_New(ptr as _, std::ptr::null(), Some(capsule_drop)) };
POOL.register_decref(NonNull::new(capsule).unwrap());
POOL.update_counts(py);
})
}
#[test]
#[cfg(not(pyo3_disable_reference_pool))]
fn test_gil_guard_update_counts() {
use crate::gil::GILGuard;
Python::with_gil(|py| {
let obj = get_object(py);
POOL.register_decref(NonNull::new(obj.clone_ref(py).into_ptr()).unwrap());
assert!(pool_dec_refs_contains(&obj));
let _guard = GILGuard::acquire();
assert!(pool_dec_refs_does_not_contain(&obj));
POOL.register_decref(NonNull::new(obj.clone_ref(py).into_ptr()).unwrap());
assert!(pool_dec_refs_contains(&obj));
let _guard2 = unsafe { GILGuard::assume() };
assert!(pool_dec_refs_does_not_contain(&obj));
})
}
}