Skip to content

Commit 6da075d

Browse files
davidhewittIcxolu
andauthored
docs: rework to remove more references to the GIL (#5481)
* docs: rework to remove more references to the GIL * Update src/instance.rs Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com> * update UI tests * revert changes to static slots * use `SLOTS` properly in `string-sum` example * fixup slots, again * fixup docs --------- Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>
1 parent 254b29f commit 6da075d

File tree

12 files changed

+106
-87
lines changed

12 files changed

+106
-87
lines changed

guide/src/python-from-rust/function-calls.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Both of these APIs take `args` and `kwargs` arguments (for positional and keywor
1212
* [`call1`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.call1) and [`call_method1`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.call_method1) to call only with positional `args`.
1313
* [`call0`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.call0) and [`call_method0`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.call_method0) to call with no arguments.
1414

15-
For convenience the [`Py<T>`](../types.md#pyt) smart pointer also exposes these same six API methods, but needs a `Python` token as an additional first argument to prove the GIL is held.
15+
For convenience the [`Py<T>`](../types.md#pyt) smart pointer also exposes these same six API methods, but needs a `Python` token as an additional first argument to prove the thread is attached to the Python interpreter.
1616

1717
The example below calls a Python function behind a `Py<PyAny>` reference:
1818

pyo3-ffi/README.md

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ static mut MODULE_DEF: PyModuleDef = PyModuleDef {
7777
m_doc: c_str!("A Python module written in Rust.").as_ptr(),
7878
m_size: 0,
7979
m_methods: unsafe { METHODS as *const [PyMethodDef] as *mut PyMethodDef },
80-
m_slots: std::ptr::null_mut(),
80+
m_slots: unsafe { SLOTS as *const [PyModuleDef_Slot] as *mut PyModuleDef_Slot },
8181
m_traverse: None,
8282
m_clear: None,
8383
m_free: None,
@@ -96,22 +96,31 @@ static mut METHODS: &[PyMethodDef] = &[
9696
PyMethodDef::zeroed(),
9797
];
9898
99+
static mut SLOTS: &[PyModuleDef_Slot] = &[
100+
// NB: only include this slot if the module does not store any global state in `static` variables
101+
// or other data which could cross between subinterpreters
102+
#[cfg(Py_3_12)]
103+
PyModuleDef_Slot {
104+
slot: Py_mod_multiple_interpreters,
105+
value: Py_MOD_PER_INTERPRETER_GIL_SUPPORTED,
106+
},
107+
// NB: only include this slot if the module does not depend on the GIL for thread safety
108+
#[cfg(Py_GIL_DISABLED)]
109+
PyModuleDef_Slot {
110+
slot: Py_mod_gil,
111+
value: Py_MOD_GIL_NOT_USED,
112+
},
113+
PyModuleDef_Slot {
114+
slot: 0,
115+
value: ptr::null_mut(),
116+
},
117+
];
118+
99119
// The module initialization function, which must be named `PyInit_<your_module>`.
100120
#[allow(non_snake_case)]
101121
#[no_mangle]
102122
pub unsafe extern "C" fn PyInit_string_sum() -> *mut PyObject {
103-
let module = PyModule_Create(ptr::addr_of_mut!(MODULE_DEF));
104-
if module.is_null() {
105-
return module;
106-
}
107-
#[cfg(Py_GIL_DISABLED)]
108-
{
109-
if PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED) < 0 {
110-
Py_DECREF(module);
111-
return std::ptr::null_mut();
112-
}
113-
}
114-
module
123+
PyModuleDef_Init(ptr::addr_of_mut!(MODULE_DEF))
115124
}
116125
117126
/// A helper to parse function arguments

pyo3-ffi/examples/sequential/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# sequential
22

3-
A project built using only `pyo3_ffi`, without any of PyO3's safe api. It can be executed by subinterpreters that have their own GIL.
3+
A project built using only `pyo3_ffi`, without any of PyO3's safe api. It supports both subinterpreters and free-threaded Python.
44

55
## Building and Testing
66

pyo3-ffi/examples/sequential/src/module.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ static mut SEQUENTIAL_SLOTS: &[PyModuleDef_Slot] = &[
1919
slot: Py_mod_exec,
2020
value: sequential_exec as *mut c_void,
2121
},
22+
#[cfg(Py_3_12)]
2223
PyModuleDef_Slot {
2324
slot: Py_mod_multiple_interpreters,
2425
value: Py_MOD_PER_INTERPRETER_GIL_SUPPORTED,

pyo3-ffi/examples/string-sum/src/lib.rs

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ static mut MODULE_DEF: PyModuleDef = PyModuleDef {
99
m_doc: c_str!("A Python module written in Rust.").as_ptr(),
1010
m_size: 0,
1111
m_methods: unsafe { METHODS as *const [PyMethodDef] as *mut PyMethodDef },
12-
m_slots: std::ptr::null_mut(),
12+
m_slots: unsafe { SLOTS as *const [PyModuleDef_Slot] as *mut PyModuleDef_Slot },
1313
m_traverse: None,
1414
m_clear: None,
1515
m_free: None,
@@ -28,22 +28,28 @@ static mut METHODS: &[PyMethodDef] = &[
2828
PyMethodDef::zeroed(),
2929
];
3030

31+
static mut SLOTS: &[PyModuleDef_Slot] = &[
32+
#[cfg(Py_3_12)]
33+
PyModuleDef_Slot {
34+
slot: Py_mod_multiple_interpreters,
35+
value: Py_MOD_PER_INTERPRETER_GIL_SUPPORTED,
36+
},
37+
#[cfg(Py_GIL_DISABLED)]
38+
PyModuleDef_Slot {
39+
slot: Py_mod_gil,
40+
value: Py_MOD_GIL_NOT_USED,
41+
},
42+
PyModuleDef_Slot {
43+
slot: 0,
44+
value: ptr::null_mut(),
45+
},
46+
];
47+
3148
// The module initialization function, which must be named `PyInit_<your_module>`.
3249
#[allow(non_snake_case)]
3350
#[no_mangle]
3451
pub unsafe extern "C" fn PyInit_string_sum() -> *mut PyObject {
35-
let module = PyModule_Create(ptr::addr_of_mut!(MODULE_DEF));
36-
if module.is_null() {
37-
return module;
38-
}
39-
#[cfg(Py_GIL_DISABLED)]
40-
{
41-
if PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED) < 0 {
42-
Py_DECREF(module);
43-
return std::ptr::null_mut();
44-
}
45-
}
46-
module
52+
PyModuleDef_Init(ptr::addr_of_mut!(MODULE_DEF))
4753
}
4854

4955
/// A helper to parse function arguments

pyo3-ffi/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
//! generally the following apply:
1818
//! - Pointer arguments have to point to a valid Python object of the correct type,
1919
//! although null pointers are sometimes valid input.
20-
//! - The vast majority can only be used safely while the GIL is held.
20+
//! - The vast majority can only be used safely while the thread is attached to the Python interpreter.
2121
//! - Some functions have additional safety requirements, consult the
2222
//! [Python/C API Reference Manual][capi]
2323
//! for more information.

pyo3-macros-backend/src/pymethod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -468,8 +468,8 @@ fn impl_traverse_slot(
468468
if let (Some(py_arg), _) = split_off_python_arg(&spec.signature.arguments) {
469469
return Err(syn::Error::new_spanned(py_arg.ty, "__traverse__ may not take `Python`. \
470470
Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \
471-
should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited \
472-
inside implementations of `__traverse__`, i.e. `Python::attach` will panic."));
471+
should do nothing but calls to `visit.call`. Most importantly, safe access to the Python interpreter is \
472+
prohibited inside implementations of `__traverse__`, i.e. `Python::attach` will panic."));
473473
}
474474

475475
// check that the receiver does not try to smuggle an (implicit) `Python` token into here
@@ -482,8 +482,8 @@ fn impl_traverse_slot(
482482
bail_spanned! { span =>
483483
"__traverse__ may not take a receiver other than `&self`. Usually, an implementation of \
484484
`__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \
485-
should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited \
486-
inside implementations of `__traverse__`, i.e. `Python::attach` will panic."
485+
should do nothing but calls to `visit.call`. Most importantly, safe access to the Python interpreter is \
486+
prohibited inside implementations of `__traverse__`, i.e. `Python::attach` will panic."
487487
}
488488
}
489489

pytests/src/misc.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use pyo3::{
55

66
#[pyfunction]
77
fn issue_219() {
8-
// issue 219: acquiring GIL inside #[pyfunction] deadlocks.
8+
// issue 219: attaching inside #[pyfunction] deadlocks.
99
Python::attach(|_| {});
1010
}
1111

@@ -15,13 +15,14 @@ struct LockHolder {
1515
sender: std::sync::mpsc::Sender<()>,
1616
}
1717

18-
// This will hammer the GIL once the LockHolder is dropped.
18+
// This will repeatedly attach and detach from the Python interpreter
19+
// once the LockHolder is dropped.
1920
#[pyfunction]
20-
fn hammer_gil_in_thread() -> LockHolder {
21+
fn hammer_attaching_in_thread() -> LockHolder {
2122
let (sender, receiver) = std::sync::mpsc::channel();
2223
std::thread::spawn(move || {
2324
receiver.recv().ok();
24-
// now the interpreter has shut down, so hammer the GIL. In buggy
25+
// now the interpreter has shut down, so hammer the attach API. In buggy
2526
// versions of PyO3 this will cause a crash.
2627
loop {
2728
Python::try_attach(|_py| ());
@@ -56,7 +57,7 @@ fn get_item_and_run_callback(dict: Bound<'_, PyDict>, callback: Bound<'_, PyAny>
5657
#[pymodule(gil_used = false)]
5758
pub fn misc(m: &Bound<'_, PyModule>) -> PyResult<()> {
5859
m.add_function(wrap_pyfunction!(issue_219, m)?)?;
59-
m.add_function(wrap_pyfunction!(hammer_gil_in_thread, m)?)?;
60+
m.add_function(wrap_pyfunction!(hammer_attaching_in_thread, m)?)?;
6061
m.add_function(wrap_pyfunction!(get_type_fully_qualified_name, m)?)?;
6162
m.add_function(wrap_pyfunction!(accepts_bool, m)?)?;
6263
m.add_function(wrap_pyfunction!(get_item_and_run_callback, m)?)?;

pytests/tests/test_hammer_gil_in_thread.py renamed to pytests/tests/test_hammer_attaching_in_thread.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ def make_loop():
2424
sysconfig.get_config_var("Py_DEBUG"),
2525
reason="causes a crash on debug builds, see discussion in https://github.com/PyO3/pyo3/pull/4874",
2626
)
27-
def test_hammer_gil():
28-
loopy.append(misc.hammer_gil_in_thread())
27+
def test_hammer_attaching_in_thread():
28+
loopy.append(misc.hammer_attaching_in_thread())

src/buffer.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ pub struct PyBuffer<T>(
4646
struct RawBuffer(ffi::Py_buffer, PhantomPinned);
4747

4848
// PyBuffer is thread-safe: the shape of the buffer is immutable while a Py_buffer exists.
49-
// Accessing the buffer contents is protected using the GIL.
5049
unsafe impl<T> Send for PyBuffer<T> {}
5150
unsafe impl<T> Sync for PyBuffer<T> {}
5251

@@ -641,7 +640,7 @@ impl<T: Element> PyBuffer<T> {
641640
/// This will automatically be called on drop.
642641
pub fn release(self, _py: Python<'_>) {
643642
// First move self into a ManuallyDrop, so that PyBuffer::drop will
644-
// never be called. (It would acquire the GIL and call PyBuffer_Release
643+
// never be called. (It would attach to the interpreter and call PyBuffer_Release
645644
// again.)
646645
let mut mdself = mem::ManuallyDrop::new(self);
647646
unsafe {

0 commit comments

Comments
 (0)