Skip to content

Commit

Permalink
Add Python::with_pool as a safe alternative to GILPool usage.
Browse files Browse the repository at this point in the history
  • Loading branch information
adamreichold committed May 20, 2023
1 parent 3ec966d commit 2973477
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 41 deletions.
9 changes: 5 additions & 4 deletions guide/src/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,11 @@ this is unsafe.
# fn main() -> PyResult<()> {
Python::with_gil(|py| -> PyResult<()> {
for _ in 0..10 {
let pool = unsafe { py.new_pool() };
let py = pool.python();
let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?;
println!("Python says: {}", hello);
py.with_pool(|py| {
let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?;
println!("Python says: {}", hello);
Ok::<_, PyErr>(())
})?;
}
Ok(())
})?;
Expand Down
1 change: 1 addition & 0 deletions newsfragments/3167.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `Python::with_pool` as a safe alternative to `Python::new_pool`
1 change: 1 addition & 0 deletions newsfragments/3167.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Deprecate `Python::new_pool` as the safe alternative `Python::with_pool` was added.
74 changes: 37 additions & 37 deletions src/gil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,11 +493,11 @@ mod tests {
fn get_object(py: Python<'_>) -> PyObject {
// Convenience function for getting a single unique object, using `new_pool` so as to leave
// the original pool state unchanged.
let pool = unsafe { py.new_pool() };
let py = pool.python();

let obj = py.eval("object()", None, None).unwrap();
obj.to_object(py)
py.with_pool(|py| {
// TODO: abuse old py????
let obj = py.eval("object()", None, None).unwrap();
obj.to_object(py)
})
}

fn owned_object_count() -> usize {
Expand All @@ -520,24 +520,22 @@ mod tests {
fn test_owned() {
Python::with_gil(|py| {
let obj = get_object(py);
let obj_ptr = obj.as_ptr();
// Ensure that obj does not get freed
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);
py.with_pool(|py| {
unsafe {
gil::register_owned(py, NonNull::new_unchecked(obj.into_ptr()));
}
}

assert_eq!(owned_object_count(), 1);
assert_eq!(_ref.get_refcnt(py), 2);
});

py.with_pool(|py| {
assert_eq!(owned_object_count(), 0);
assert_eq!(_ref.get_refcnt(py), 1);
});
})
}

Expand All @@ -547,30 +545,32 @@ mod tests {
let obj = get_object(py);
// Ensure that obj does not get freed
let _ref = obj.clone_ref(py);
let obj_ptr = obj.as_ptr();

unsafe {
{
let _pool = py.new_pool();
assert_eq!(owned_object_count(), 0);
py.with_pool(|py| {
assert_eq!(owned_object_count(), 0);

unsafe {
gil::register_owned(py, NonNull::new_unchecked(obj.into_ptr()));
}

assert_eq!(owned_object_count(), 1);
assert_eq!(_ref.get_refcnt(py), 2);

py.with_pool(|py| {
let obj = get_object(py);

assert_eq!(owned_object_count(), 1);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 2);
{
let _pool = py.new_pool();
let obj = get_object(py);
unsafe {
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);
}
}

assert_eq!(owned_object_count(), 2);
});

assert_eq!(owned_object_count(), 1);
});

assert_eq!(owned_object_count(), 0);
assert_eq!(_ref.get_refcnt(py), 1);
});
}

Expand Down
44 changes: 44 additions & 0 deletions src/marker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,45 @@ impl Python<'_> {
{
f(gil::ensure_gil_unchecked().python())
}

/// Create a new pool for managing PyO3's owned references.
///
/// When the given closure returns, all PyO3 owned references created by it will
/// all have their Python reference counts decremented, potentially allowing Python to drop
/// the corresponding Python objects.
///
/// Typical usage of PyO3 will not need this API, as [`Python::with_gil`] automatically creates
/// a `GILPool` where appropriate.
///
/// Advanced uses of PyO3 which perform long-running tasks which never free the GIL may need
/// to use this API to clear memory, as PyO3 usually does not clear memory until the GIL is
/// released.
///
/// # Examples
///
/// ```rust
/// # use pyo3::prelude::*;
/// Python::with_gil(|py| {
/// // Some long-running process like a webserver, which never releases the GIL.
/// loop {
/// // Create a new pool, so that PyO3 can clear memory at the end of the loop.
/// py.with_pool(|py| {
/// // do stuff...
/// });
/// # break; // Exit the loop so that doctest terminates!
/// }
/// });
/// ```
#[inline]
pub fn with_pool<F, R>(self, f: F) -> R
where
F: for<'py> FnOnce(Python<'py>) -> R + Ungil,
R: Ungil,
{
let pool = unsafe { GILPool::new() };

f(pool.python())
}
}

impl<'py> Python<'py> {
Expand Down Expand Up @@ -785,9 +824,13 @@ impl<'py> Python<'py> {
/// to use this API to clear memory, as PyO3 usually does not clear memory until the GIL is
/// released.
///
/// [`Python::with_pool`] provides a safe alternative albeit adding the requirement that all
/// types interacting with the new pool are marked with the `Ungil` trait.
///
/// # Examples
///
/// ```rust
/// # #![allow(deprecated)]
/// # use pyo3::prelude::*;
/// Python::with_gil(|py| {
/// // Some long-running process like a webserver, which never releases the GIL.
Expand Down Expand Up @@ -827,6 +870,7 @@ impl<'py> Python<'py> {
///
/// [`.python()`]: crate::GILPool::python
#[inline]
#[deprecated(since = "0.19.0", note = "Please use `with_pool` instead.")]
pub unsafe fn new_pool(self) -> GILPool {
GILPool::new()
}
Expand Down
1 change: 1 addition & 0 deletions tests/test_compile_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ fn _test_compile_errors() {
t.compile_fail("tests/ui/invalid_pymethod_names.rs");
t.compile_fail("tests/ui/invalid_pymodule_args.rs");
t.compile_fail("tests/ui/reject_generics.rs");
t.compile_fail("tests/ui/with_pool.rs");

tests_rust_1_49(&t);
tests_rust_1_56(&t);
Expand Down
11 changes: 11 additions & 0 deletions tests/ui/with_pool.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use pyo3::prelude::*;

fn reuse_old_token_in_with_pool(old_py: Python<'_>) {
old_py.with_pool(|new_py| { drop(old_py); });
}

fn main() {
Python::with_gil(|py| {
reuse_old_token_in_with_pool(py);
})
}
26 changes: 26 additions & 0 deletions tests/ui/with_pool.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
error[E0277]: `*mut pyo3::Python<'static>` cannot be shared between threads safely
--> tests/ui/with_pool.rs:4:22
|
4 | old_py.with_pool(|new_py| { drop(old_py); });
| --------- ^^^^^^^^^^^^^^^^^^^^^^^^^^ `*mut pyo3::Python<'static>` cannot be shared between threads safely
| |
| required by a bound introduced by this call
|
= help: within `pyo3::Python<'_>`, the trait `Sync` is not implemented for `*mut pyo3::Python<'static>`
= note: required because it appears within the type `PhantomData<*mut Python<'static>>`
= note: required because it appears within the type `NotSend`
= note: required because it appears within the type `(&EnsureGIL, NotSend)`
= note: required because it appears within the type `PhantomData<(&EnsureGIL, NotSend)>`
= note: required because it appears within the type `Python<'_>`
= note: required for `&pyo3::Python<'_>` to implement `Send`
note: required because it's used within this closure
--> tests/ui/with_pool.rs:4:22
|
4 | old_py.with_pool(|new_py| { drop(old_py); });
| ^^^^^^^^
= note: required for `[closure@$DIR/tests/ui/with_pool.rs:4:22: 4:30]` to implement `Ungil`
note: required by a bound in `pyo3::Python::<'_>::with_pool`
--> src/marker.rs
|
| F: for<'py> FnOnce(Python<'py>) -> R + Ungil,
| ^^^^^ required by this bound in `Python::<'_>::with_pool`

0 comments on commit 2973477

Please sign in to comment.