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
2 changes: 1 addition & 1 deletion guide/src/python-from-rust/function-calls.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Both of these APIs take `args` and `kwargs` arguments (for positional and keywor
* [`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`.
* [`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.

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.
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.

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

Expand Down
35 changes: 22 additions & 13 deletions pyo3-ffi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ static mut MODULE_DEF: PyModuleDef = PyModuleDef {
m_doc: c_str!("A Python module written in Rust.").as_ptr(),
m_size: 0,
m_methods: unsafe { METHODS as *const [PyMethodDef] as *mut PyMethodDef },
m_slots: std::ptr::null_mut(),
m_slots: unsafe { SLOTS as *const [PyModuleDef_Slot] as *mut PyModuleDef_Slot },
m_traverse: None,
m_clear: None,
m_free: None,
Expand All @@ -96,22 +96,31 @@ static mut METHODS: &[PyMethodDef] = &[
PyMethodDef::zeroed(),
];

static mut SLOTS: &[PyModuleDef_Slot] = &[
// NB: only include this slot if the module does not store any global state in `static` variables
// or other data which could cross between subinterpreters
#[cfg(Py_3_12)]
PyModuleDef_Slot {
slot: Py_mod_multiple_interpreters,
value: Py_MOD_PER_INTERPRETER_GIL_SUPPORTED,
},
// NB: only include this slot if the module does not depend on the GIL for thread safety
#[cfg(Py_GIL_DISABLED)]
PyModuleDef_Slot {
slot: Py_mod_gil,
value: Py_MOD_GIL_NOT_USED,
},
PyModuleDef_Slot {
slot: 0,
value: ptr::null_mut(),
},
];

// The module initialization function, which must be named `PyInit_<your_module>`.
#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "C" fn PyInit_string_sum() -> *mut PyObject {
let module = PyModule_Create(ptr::addr_of_mut!(MODULE_DEF));
if module.is_null() {
return module;
}
#[cfg(Py_GIL_DISABLED)]
{
if PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED) < 0 {
Py_DECREF(module);
return std::ptr::null_mut();
}
}
module
PyModuleDef_Init(ptr::addr_of_mut!(MODULE_DEF))
}

/// A helper to parse function arguments
Expand Down
2 changes: 1 addition & 1 deletion pyo3-ffi/examples/sequential/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# sequential

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.
A project built using only `pyo3_ffi`, without any of PyO3's safe api. It supports both subinterpreters and free-threaded Python.

## Building and Testing

Expand Down
1 change: 1 addition & 0 deletions pyo3-ffi/examples/sequential/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ static mut SEQUENTIAL_SLOTS: &[PyModuleDef_Slot] = &[
slot: Py_mod_exec,
value: sequential_exec as *mut c_void,
},
#[cfg(Py_3_12)]
PyModuleDef_Slot {
slot: Py_mod_multiple_interpreters,
value: Py_MOD_PER_INTERPRETER_GIL_SUPPORTED,
Expand Down
32 changes: 19 additions & 13 deletions pyo3-ffi/examples/string-sum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ static mut MODULE_DEF: PyModuleDef = PyModuleDef {
m_doc: c_str!("A Python module written in Rust.").as_ptr(),
m_size: 0,
m_methods: unsafe { METHODS as *const [PyMethodDef] as *mut PyMethodDef },
m_slots: std::ptr::null_mut(),
m_slots: unsafe { SLOTS as *const [PyModuleDef_Slot] as *mut PyModuleDef_Slot },
m_traverse: None,
m_clear: None,
m_free: None,
Expand All @@ -28,22 +28,28 @@ static mut METHODS: &[PyMethodDef] = &[
PyMethodDef::zeroed(),
];

static mut SLOTS: &[PyModuleDef_Slot] = &[
#[cfg(Py_3_12)]
PyModuleDef_Slot {
slot: Py_mod_multiple_interpreters,
value: Py_MOD_PER_INTERPRETER_GIL_SUPPORTED,
},
#[cfg(Py_GIL_DISABLED)]
PyModuleDef_Slot {
slot: Py_mod_gil,
value: Py_MOD_GIL_NOT_USED,
},
PyModuleDef_Slot {
slot: 0,
value: ptr::null_mut(),
},
];

// The module initialization function, which must be named `PyInit_<your_module>`.
#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "C" fn PyInit_string_sum() -> *mut PyObject {
let module = PyModule_Create(ptr::addr_of_mut!(MODULE_DEF));
if module.is_null() {
return module;
}
#[cfg(Py_GIL_DISABLED)]
{
if PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED) < 0 {
Py_DECREF(module);
return std::ptr::null_mut();
}
}
module
PyModuleDef_Init(ptr::addr_of_mut!(MODULE_DEF))
}

/// A helper to parse function arguments
Expand Down
2 changes: 1 addition & 1 deletion pyo3-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
//! generally the following apply:
//! - Pointer arguments have to point to a valid Python object of the correct type,
//! although null pointers are sometimes valid input.
//! - The vast majority can only be used safely while the GIL is held.
//! - The vast majority can only be used safely while the thread is attached to the Python interpreter.
//! - Some functions have additional safety requirements, consult the
//! [Python/C API Reference Manual][capi]
//! for more information.
Expand Down
8 changes: 4 additions & 4 deletions pyo3-macros-backend/src/pymethod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -468,8 +468,8 @@ fn impl_traverse_slot(
if let (Some(py_arg), _) = split_off_python_arg(&spec.signature.arguments) {
return Err(syn::Error::new_spanned(py_arg.ty, "__traverse__ may not take `Python`. \
Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \
should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited \
inside implementations of `__traverse__`, i.e. `Python::attach` will panic."));
should do nothing but calls to `visit.call`. Most importantly, safe access to the Python interpreter is \
prohibited inside implementations of `__traverse__`, i.e. `Python::attach` will panic."));
}

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

Expand Down
11 changes: 6 additions & 5 deletions pytests/src/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use pyo3::{

#[pyfunction]
fn issue_219() {
// issue 219: acquiring GIL inside #[pyfunction] deadlocks.
// issue 219: attaching inside #[pyfunction] deadlocks.
Python::attach(|_| {});
}

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

// This will hammer the GIL once the LockHolder is dropped.
// This will repeatedly attach and detach from the Python interpreter
// once the LockHolder is dropped.
#[pyfunction]
fn hammer_gil_in_thread() -> LockHolder {
fn hammer_attaching_in_thread() -> LockHolder {
let (sender, receiver) = std::sync::mpsc::channel();
std::thread::spawn(move || {
receiver.recv().ok();
// now the interpreter has shut down, so hammer the GIL. In buggy
// now the interpreter has shut down, so hammer the attach API. In buggy
// versions of PyO3 this will cause a crash.
loop {
Python::try_attach(|_py| ());
Expand Down Expand Up @@ -56,7 +57,7 @@ fn get_item_and_run_callback(dict: Bound<'_, PyDict>, callback: Bound<'_, PyAny>
#[pymodule(gil_used = false)]
pub fn misc(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(issue_219, m)?)?;
m.add_function(wrap_pyfunction!(hammer_gil_in_thread, m)?)?;
m.add_function(wrap_pyfunction!(hammer_attaching_in_thread, m)?)?;
m.add_function(wrap_pyfunction!(get_type_fully_qualified_name, m)?)?;
m.add_function(wrap_pyfunction!(accepts_bool, m)?)?;
m.add_function(wrap_pyfunction!(get_item_and_run_callback, m)?)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ def make_loop():
sysconfig.get_config_var("Py_DEBUG"),
reason="causes a crash on debug builds, see discussion in https://github.com/PyO3/pyo3/pull/4874",
)
def test_hammer_gil():
loopy.append(misc.hammer_gil_in_thread())
def test_hammer_attaching_in_thread():
loopy.append(misc.hammer_attaching_in_thread())
3 changes: 1 addition & 2 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ pub struct PyBuffer<T>(
struct RawBuffer(ffi::Py_buffer, PhantomPinned);

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

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