Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions examples/word-count/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ fn search_sequential(contents: &str, needle: &str) -> usize {
}

#[pyfunction]
fn search_sequential_allow_threads(py: Python<'_>, contents: &str, needle: &str) -> usize {
py.allow_threads(|| search_sequential(contents, needle))
fn search_sequential_detached(py: Python<'_>, contents: &str, needle: &str) -> usize {
py.detach(|| search_sequential(contents, needle))
}

/// Count the occurrences of needle in line, case insensitive
Expand All @@ -36,7 +36,7 @@ fn count_line(line: &str, needle: &str) -> usize {
fn word_count(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(search, m)?)?;
m.add_function(wrap_pyfunction!(search_sequential, m)?)?;
m.add_function(wrap_pyfunction!(search_sequential_allow_threads, m)?)?;
m.add_function(wrap_pyfunction!(search_sequential_detached, m)?)?;

Ok(())
}
8 changes: 2 additions & 6 deletions examples/word-count/tests/test_word_count.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,8 @@ def test_word_count_python_sequential(benchmark, contents):
def run_rust_sequential_twice(
executor: ThreadPoolExecutor, contents: str, needle: str
) -> int:
future_1 = executor.submit(
word_count.search_sequential_allow_threads, contents, needle
)
future_2 = executor.submit(
word_count.search_sequential_allow_threads, contents, needle
)
future_1 = executor.submit(word_count.search_sequential_detached, contents, needle)
future_2 = executor.submit(word_count.search_sequential_detached, contents, needle)
result_1 = future_1.result()
result_2 = future_2.result()
return result_1 + result_2
Expand Down
4 changes: 2 additions & 2 deletions examples/word-count/word_count/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from .word_count import search, search_sequential, search_sequential_allow_threads
from .word_count import search, search_sequential, search_sequential_detached

__all__ = [
"search_py",
"search",
"search_sequential",
"search_sequential_allow_threads",
"search_sequential_detached",
]


Expand Down
2 changes: 1 addition & 1 deletion guide/src/async-await.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ where
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let waker = cx.waker();
Python::attach(|py| {
py.allow_threads(|| pin!(&mut self.0).poll(&mut Context::from_waker(waker)))
py.detach(|| pin!(&mut self.0).poll(&mut Context::from_waker(waker)))
})
}
}
Expand Down
2 changes: 1 addition & 1 deletion guide/src/conversions/tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Using Rust library types as function arguments will incur a conversion cost comp
However, once that conversion cost has been paid, the Rust standard library types offer a number of benefits:
- You can write functionality in native-speed Rust code (free of Python's runtime costs).
- You get better interoperability with the rest of the Rust ecosystem.
- You can use `Python::allow_threads` to release the Python GIL and let other Python threads make progress while your Rust code is executing.
- You can use `Python::detach` to release the Python GIL and let other Python threads make progress while your Rust code is executing.
- You also benefit from stricter type checking. For example you can specify `Vec<i32>`, which will only accept a Python `list` containing integers. The Python-native equivalent, `&PyList`, would accept a Python `list` containing Python objects of any type.

For most PyO3 usage the conversion cost is worth paying to get these benefits. As always, if you're not sure it's worth it in your case, benchmark it!
Expand Down
2 changes: 1 addition & 1 deletion guide/src/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ See [the `#[pyclass]` implementation details](class.md#implementation-details) f

### `nightly`

The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use the `auto_traits` and `negative_impls` features to fix the `Python::allow_threads` function.
The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use the `auto_traits` and `negative_impls` features to fix the `Python::detach` function.

### `resolve-config`

Expand Down
6 changes: 3 additions & 3 deletions guide/src/free-threading.md
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't comment in-context, but the text starting at line 120 through line 140 or so probably needs a revision pass, since you're fixing the API naming issues that the text is apologizing for. I still think the thrust of that text is important - it's a very common misconception that you don't need to explicitly attach or detach on the free-threaded build.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's part of the general docs pass, that I've prepared as a follow up so that we have smaller diff for easier reviewing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good, thanks for already taking care of that :)

Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ This is a non-exhaustive list and there may be other situations in future Python
versions that can trigger global synchronization events.

This means that you should detach from the interpreter runtime using
[`Python::allow_threads`] in exactly the same situations as you should detach
[`Python::detach`] in exactly the same situations as you should detach
from the runtime in the GIL-enabled build: when doing long-running tasks that do
not require the CPython runtime or when doing any task that needs to re-attach
to the runtime (see the [guide
Expand All @@ -197,7 +197,7 @@ Data attached to `pyclass` instances is protected from concurrent access by a
`RefCell`-like pattern of runtime borrow checking. Like a `RefCell`, PyO3 will
raise exceptions (or in some cases panic) to enforce exclusive access for
mutable borrows. It was always possible to generate panics like this in PyO3 in
code that releases the GIL with [`Python::allow_threads`] or calling a python
code that releases the GIL with [`Python::detach`] or calling a python
method accepting `&self` from a `&mut self` (see [the docs on interior
mutability](./class.md#bound-and-interior-mutability),) but now in free-threaded
Python there are more opportunities to trigger these panics from Python because
Expand Down Expand Up @@ -402,7 +402,7 @@ interpreter.
[`OnceLockExt::get_or_init_py_attached`]: {{#PYO3_DOCS_URL}}/pyo3/sync/trait.OnceLockExt.html#tymethod.get_or_init_py_attached
[`OnceLock`]: https://doc.rust-lang.org/stable/std/sync/struct.OnceLock.html
[`OnceLock::get_or_init`]: https://doc.rust-lang.org/stable/std/sync/struct.OnceLock.html#method.get_or_init
[`Python::allow_threads`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.allow_threads
[`Python::detach`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.detach
[`Python::attach`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.attach
[`Python<'py>`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html
[`threading`]: https://docs.python.org/3/library/threading.html
12 changes: 11 additions & 1 deletion guide/src/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@
This guide can help you upgrade code through breaking changes from one PyO3 version to the next.
For a detailed list of all changes, see the [CHANGELOG](changelog.md).

## from 0.25.* to 0.26
### Rename of `Python::with_gil` and `Python::allow_threads`
<details open>
<summary><small>Click to expand</small></summary>
The names for these APIs were created when the global interpreter lock (GIL) was mandatory. With the introduction of free-threading in Python 3.13 this is no longer the case, and the naming does not has no universal meaning anymore.
For this reason we chose to rename these to more modern terminology introduced in free-threading:
- `Python::with_gil` is now called `Python::attach`, it attaches a Python thread-state to the current thread. In GIL enabled builds there can only be 1 thread attached to the interpreter, in free-threading there can be more.
- `Python::allow_threads` is now called `Python::detach`, it detaches a previously attached thread-state.
</details>

## from 0.24.* to 0.25
### `AsPyPointer` removal
<details open>
<details>
<summary><small>Click to expand</small></summary>
The `AsPyPointer` trait is mostly a leftover from the now removed gil-refs API. The last remaining uses were the GC API, namely `PyVisit::call`, and identity comparison (`PyAnyMethods::is` and `Py::is`).

Expand Down
24 changes: 12 additions & 12 deletions guide/src/parallelism.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ fn search_sequential(contents: &str, needle: &str) -> usize {
}
```

To enable parallel execution of this function, the [`Python::allow_threads`] method can be used to temporarily release the GIL, thus allowing other Python threads to run. We then have a function exposed to the Python runtime which calls `search_sequential` inside a closure passed to [`Python::allow_threads`] to enable true parallelism:
To enable parallel execution of this function, the [`Python::detach`] method can be used to temporarily release the GIL, thus allowing other Python threads to run. We then have a function exposed to the Python runtime which calls `search_sequential` inside a closure passed to [`Python::detach`] to enable true parallelism:
```rust,no_run
# #![allow(dead_code)]
# use pyo3::prelude::*;
Expand All @@ -68,23 +68,23 @@ To enable parallel execution of this function, the [`Python::allow_threads`] met
# contents.lines().map(|line| count_line(line, needle)).sum()
# }
#[pyfunction]
fn search_sequential_allow_threads(py: Python<'_>, contents: &str, needle: &str) -> usize {
py.allow_threads(|| search_sequential(contents, needle))
fn search_sequential_detached(py: Python<'_>, contents: &str, needle: &str) -> usize {
py.detach(|| search_sequential(contents, needle))
}
```

Now Python threads can use more than one CPU core, resolving the limitation which usually makes multi-threading in Python only good for IO-bound tasks:
```Python
from concurrent.futures import ThreadPoolExecutor
from word_count import search_sequential_allow_threads
from word_count import search_sequential_detached

executor = ThreadPoolExecutor(max_workers=2)

future_1 = executor.submit(
word_count.search_sequential_allow_threads, contents, needle
word_count.search_sequential_detached, contents, needle
)
future_2 = executor.submit(
word_count.search_sequential_allow_threads, contents, needle
word_count.search_sequential_detached, contents, needle
)
result_1 = future_1.result()
result_2 = future_2.result()
Expand Down Expand Up @@ -149,7 +149,7 @@ struct UserID {

let allowed_ids: Vec<bool> = Python::attach(|outer_py| {
let instances: Vec<Py<UserID>> = (0..10).map(|x| Py::new(outer_py, UserID { id: x }).unwrap()).collect();
outer_py.allow_threads(|| {
outer_py.detach(|| {
instances.par_iter().map(|instance| {
Python::attach(|inner_py| {
instance.borrow(inner_py).id > 5
Expand All @@ -165,13 +165,13 @@ an `inner_py` token. Sharing GIL lifetime tokens between threads is not allowed
and threads must individually acquire the GIL to access data wrapped by a python
object.

It's also important to see that this example uses [`Python::allow_threads`] to
It's also important to see that this example uses [`Python::detach`] to
wrap the code that spawns OS threads via `rayon`. If this example didn't use
`allow_threads`, a rayon worker thread would block on acquiring the GIL while a
`detach`, a rayon worker thread would block on acquiring the GIL while a
thread that owns the GIL spins forever waiting for the result of the rayon
thread. Calling `allow_threads` allows the GIL to be released in the thread
thread. Calling `detach` allows the GIL to be released in the thread
collecting the results from the worker threads. You should always call
`allow_threads` in situations that spawn worker threads, but especially so in
`detach` in situations that spawn worker threads, but especially so in
cases where worker threads need to acquire the GIL, to prevent deadlocks.

[`Python::allow_threads`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.allow_threads
[`Python::detach`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.detach
1 change: 1 addition & 0 deletions newsfragments/5221.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rename `Python::allow_threads` to `Python::detach`
2 changes: 1 addition & 1 deletion pytests/src/pyclasses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ impl PyClassThreadIter {
let current_count = self.count;
self.count += 1;
if current_count == 0 {
py.allow_threads(|| thread::sleep(time::Duration::from_millis(100)));
py.detach(|| thread::sleep(time::Duration::from_millis(100)));
}
self.count
}
Expand Down
6 changes: 3 additions & 3 deletions src/err/err_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl PyErrState {
pub(crate) fn normalized(normalized: PyErrStateNormalized) -> Self {
let state = Self::from_inner(PyErrStateInner::Normalized(normalized));
// This state is already normalized, by completing the Once immediately we avoid
// reaching the `py.allow_threads` in `make_normalized` which is less efficient
// reaching the `py.detach` in `make_normalized` which is less efficient
// and introduces a GIL switch which could deadlock.
// See https://github.com/PyO3/pyo3/issues/4764
state.normalized.call_once(|| {});
Expand Down Expand Up @@ -98,7 +98,7 @@ impl PyErrState {
}

// avoid deadlock of `.call_once` with the GIL
py.allow_threads(|| {
py.detach(|| {
self.normalized.call_once(|| {
self.normalizing_thread
.lock()
Expand Down Expand Up @@ -406,7 +406,7 @@ mod tests {
fn arguments(self, py: Python<'_>) -> PyObject {
// releasing the GIL potentially allows for other threads to deadlock
// with the normalization going on here
py.allow_threads(|| {
py.detach(|| {
std::thread::sleep(std::time::Duration::from_millis(10));
});
py.None()
Expand Down
8 changes: 4 additions & 4 deletions src/gil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -551,13 +551,13 @@ mod tests {
}

#[test]
fn test_allow_threads() {
fn test_detach() {
assert!(!gil_is_acquired());

Python::attach(|py| {
assert!(gil_is_acquired());

py.allow_threads(move || {
py.detach(move || {
assert!(!gil_is_acquired());

Python::attach(|_| assert!(gil_is_acquired()));
Expand All @@ -574,13 +574,13 @@ mod tests {
#[cfg(feature = "py-clone")]
#[test]
#[should_panic]
fn test_allow_threads_updates_refcounts() {
fn test_detach_updates_refcounts() {
Python::attach(|py| {
// Make a simple object with 1 reference
let obj = get_object(py);
assert!(obj.get_refcnt(py) == 1);
// Clone the object without the GIL which should panic
py.allow_threads(|| obj.clone());
py.detach(|| obj.clone());
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ mod bound_object_sealed {
///
/// To access the object in situations where the GIL is not held, convert it to [`Py<T>`]
/// using [`.unbind()`][Bound::unbind]. This includes situations where the GIL is temporarily
/// released, such as [`Python::allow_threads`](crate::Python::allow_threads)'s closure.
/// released, such as [`Python::detach`](crate::Python::detach)'s closure.
///
/// See
#[doc = concat!("[the guide](https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/types.html#boundpy-t)")]
Expand Down
Loading
Loading