diff --git a/Cargo.toml b/Cargo.toml index 3d25f578d76..7bd341131d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,9 @@ experimental-inspect = [] # Enables macros: #[pyclass], #[pymodule], #[pyfunction] etc. macros = ["pyo3-macros", "indoc", "unindent"] +# Enables GIL-bound references backed by a thread-local pool +pool = [] + # Enables multiple #[pymethods] per #[pyclass] multiple-pymethods = ["inventory", "pyo3-macros/multiple-pymethods"] diff --git a/src/conversion.rs b/src/conversion.rs index 0a842f9a419..fd44af5e813 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -5,10 +5,9 @@ use crate::inspect::types::TypeInfo; use crate::pyclass::boolean_struct::False; use crate::type_object::PyTypeInfo; use crate::types::PyTuple; -use crate::{ - ffi, gil, Py, PyAny, PyCell, PyClass, PyNativeType, PyObject, PyRef, PyRefMut, Python, -}; +use crate::{ffi, Py, PyAny, PyCell, PyClass, PyNativeType, PyObject, PyRef, PyRefMut, Python}; use std::cell::Cell; +#[cfg(feature = "pool")] use std::ptr::NonNull; /// Returns a borrowed pointer to a Python object. @@ -543,12 +542,13 @@ pub unsafe trait FromPyPointer<'p>: Sized { } } +#[cfg(feature = "pool")] unsafe impl<'p, T> FromPyPointer<'p> for T where T: 'p + crate::PyNativeType, { unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option<&'p Self> { - gil::register_owned(py, NonNull::new(ptr)?); + crate::gil::register_owned(py, NonNull::new(ptr)?); Some(&*(ptr as *mut Self)) } unsafe fn from_borrowed_ptr_or_opt( diff --git a/src/gil.rs b/src/gil.rs index d346ad95ea9..2a66ba7758c 100644 --- a/src/gil.rs +++ b/src/gil.rs @@ -1,12 +1,13 @@ //! Interaction with Python's global interpreter lock +#[cfg(feature = "pool")] use crate::impl_::not_send::{NotSend, NOT_SEND}; use crate::{ffi, Python}; use parking_lot::{const_mutex, Mutex, Once}; use std::cell::Cell; -#[cfg(debug_assertions)] +#[cfg(all(feature = "pool", debug_assertions))] use std::cell::RefCell; -#[cfg(not(debug_assertions))] +#[cfg(all(feature = "pool", not(debug_assertions)))] use std::cell::UnsafeCell; use std::{mem, ptr::NonNull}; @@ -37,9 +38,9 @@ thread_local_const_init! { static GIL_COUNT: Cell = const { Cell::new(0) }; /// Temporarily hold objects that will be released when the GILPool drops. - #[cfg(debug_assertions)] + #[cfg(all(feature = "pool", debug_assertions))] static OWNED_OBJECTS: RefCell = const { RefCell::new(Vec::new()) }; - #[cfg(not(debug_assertions))] + #[cfg(all(feature = "pool", not(debug_assertions)))] static OWNED_OBJECTS: UnsafeCell = const { UnsafeCell::new(Vec::new()) }; } @@ -139,17 +140,27 @@ where ffi::Py_InitializeEx(0); // Safety: the GIL is already held because of the Py_IntializeEx call. + #[cfg(feature = "pool")] let pool = GILPool::new(); + #[cfg(feature = "pool")] + let py = pool.python(); + #[cfg(not(feature = "pool"))] + let gil = WithGIL::during_call(); + #[cfg(not(feature = "pool"))] + let py = gil.python(); // Import the threading module - this ensures that it will associate this thread as the "main" // thread, which is important to avoid an `AssertionError` at finalization. - pool.python().import("threading").unwrap(); + py.import("threading").unwrap(); // Execute the closure. - let result = f(pool.python()); + let result = f(py); // Drop the pool before finalizing. + #[cfg(feature = "pool")] drop(pool); + #[cfg(not(feature = "pool"))] + drop(gil); // Finalize the Python interpreter. ffi::Py_Finalize(); @@ -160,6 +171,7 @@ where /// RAII type that represents the Global Interpreter Lock acquisition. pub(crate) struct GILGuard { gstate: ffi::PyGILState_STATE, + #[cfg(feature = "pool")] pool: mem::ManuallyDrop, } @@ -222,9 +234,14 @@ impl GILGuard { } let gstate = unsafe { ffi::PyGILState_Ensure() }; // acquire GIL + #[cfg(feature = "pool")] let pool = unsafe { mem::ManuallyDrop::new(GILPool::new()) }; - Some(GILGuard { gstate, pool }) + Some(GILGuard { + gstate, + #[cfg(feature = "pool")] + pool, + }) } } @@ -233,6 +250,7 @@ impl Drop for GILGuard { fn drop(&mut self) { unsafe { // Drop the objects in the pool before attempting to release the thread state + #[cfg(feature = "pool")] mem::ManuallyDrop::drop(&mut self.pool); ffi::PyGILState_Release(self.gstate); @@ -317,6 +335,30 @@ impl Drop for SuspendGIL { } } +/// Used to indicate safe access to the GIL +pub(crate) struct WithGIL; + +impl WithGIL { + pub unsafe fn during_call() -> Self { + increment_gil_count(); + + // Update counts of PyObjects / Py that have been cloned or dropped since last acquisition + POOL.update_counts(Python::assume_gil_acquired()); + + Self + } + + pub fn python(&self) -> Python<'_> { + unsafe { Python::assume_gil_acquired() } + } +} + +impl Drop for WithGIL { + fn drop(&mut self) { + decrement_gil_count(); + } +} + /// Used to lock safe access to the GIL pub(crate) struct LockGIL { count: isize, @@ -355,9 +397,9 @@ impl Drop for LockGIL { /// /// See the [Memory Management] chapter of the guide for more information about how PyO3 uses /// [`GILPool`] to manage memory. - /// /// [Memory Management]: https://pyo3.rs/main/memory.html#gil-bound-memory +#[cfg(feature = "pool")] pub struct GILPool { /// Initial length of owned objects and anys. /// `Option` is used since TSL can be broken when `new` is called from `atexit`. @@ -365,6 +407,7 @@ pub struct GILPool { _not_send: NotSend, } +#[cfg(feature = "pool")] impl GILPool { /// Creates a new [`GILPool`]. This function should only ever be called with the GIL held. /// @@ -401,6 +444,7 @@ impl GILPool { } } +#[cfg(feature = "pool")] impl Drop for GILPool { fn drop(&mut self) { if let Some(start) = self.start { @@ -463,6 +507,7 @@ pub unsafe fn register_decref(obj: NonNull) { /// /// # Safety /// The object must be an owned Python reference. +#[cfg(feature = "pool")] pub unsafe fn register_owned(_py: Python<'_>, obj: NonNull) { debug_assert!(gil_is_acquired()); // Ignores the error in case this function called from `atexit`. diff --git a/src/impl_/not_send.rs b/src/impl_/not_send.rs index 97c2984aff8..8f6fd012533 100644 --- a/src/impl_/not_send.rs +++ b/src/impl_/not_send.rs @@ -6,4 +6,5 @@ use crate::Python; /// Workaround for lack of !Send on stable (). pub(crate) struct NotSend(PhantomData<*mut Python<'static>>); +#[cfg(feature = "pool")] pub(crate) const NOT_SEND: NotSend = NotSend(PhantomData); diff --git a/src/impl_/trampoline.rs b/src/impl_/trampoline.rs index 5ae75e0fd2e..fa10fe96444 100644 --- a/src/impl_/trampoline.rs +++ b/src/impl_/trampoline.rs @@ -9,9 +9,13 @@ use std::{ panic::{self, UnwindSafe}, }; +#[cfg(feature = "pool")] +use crate::gil::GILPool; +#[cfg(not(feature = "pool"))] +use crate::gil::WithGIL; use crate::{ callback::PyCallbackOutput, ffi, impl_::panic::PanicTrap, methods::IPowModulo, - panic::PanicException, types::PyModule, GILPool, Py, PyResult, Python, + panic::PanicException, types::PyModule, Py, PyResult, Python, }; #[inline] @@ -174,8 +178,14 @@ where R: PyCallbackOutput, { let trap = PanicTrap::new("uncaught panic at ffi boundary"); + #[cfg(feature = "pool")] let pool = unsafe { GILPool::new() }; + #[cfg(feature = "pool")] let py = pool.python(); + #[cfg(not(feature = "pool"))] + let gil = unsafe { WithGIL::during_call() }; + #[cfg(not(feature = "pool"))] + let py = gil.python(); let out = panic_result_into_callback_output( py, panic::catch_unwind(move || -> PyResult<_> { body(py) }), @@ -219,8 +229,14 @@ where F: for<'py> FnOnce(Python<'py>) -> PyResult<()> + UnwindSafe, { let trap = PanicTrap::new("uncaught panic at ffi boundary"); - let pool = GILPool::new(); + #[cfg(feature = "pool")] + let pool = unsafe { GILPool::new() }; + #[cfg(feature = "pool")] let py = pool.python(); + #[cfg(not(feature = "pool"))] + let gil = unsafe { WithGIL::during_call() }; + #[cfg(not(feature = "pool"))] + let py = gil.python(); if let Err(py_err) = panic::catch_unwind(move || body(py)) .unwrap_or_else(|payload| Err(PanicException::from_panic_payload(payload))) { diff --git a/src/lib.rs b/src/lib.rs index e28da283cde..8dea4a4b968 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -298,6 +298,7 @@ pub use crate::conversion::{AsPyPointer, FromPyObject, FromPyPointer, IntoPy, To #[allow(deprecated)] pub use crate::conversion::{PyTryFrom, PyTryInto}; pub use crate::err::{PyDowncastError, PyErr, PyErrArguments, PyResult}; +#[cfg(feature = "pool")] pub use crate::gil::GILPool; #[cfg(not(PyPy))] pub use crate::gil::{prepare_freethreaded_python, with_embedded_python_interpreter}; diff --git a/src/marker.rs b/src/marker.rs index a6517fa1844..a4a56bb2ade 100644 --- a/src/marker.rs +++ b/src/marker.rs @@ -116,7 +116,9 @@ //! [`Rc`]: std::rc::Rc //! [`Py`]: crate::Py use crate::err::{self, PyDowncastError, PyErr, PyResult}; -use crate::gil::{GILGuard, GILPool, SuspendGIL}; +#[cfg(feature = "pool")] +use crate::gil::GILPool; +use crate::gil::{GILGuard, SuspendGIL}; use crate::impl_::not_send::NotSend; use crate::type_object::HasPyGilRef; use crate::types::{ @@ -967,6 +969,7 @@ impl<'py> Python<'py> { /// /// [`.python()`]: crate::GILPool::python #[inline] + #[cfg(feature = "pool")] pub unsafe fn new_pool(self) -> GILPool { GILPool::new() } @@ -1025,6 +1028,7 @@ impl Python<'_> { /// }); /// ``` #[inline] + #[cfg(feature = "pool")] pub fn with_pool(&self, f: F) -> R where F: for<'py> FnOnce(Python<'py>) -> R + Ungil,