Skip to content

Commit

Permalink
add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Ariel Ben-Yehuda committed Jan 27, 2025
1 parent cb81581 commit abf1395
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 14 deletions.
17 changes: 3 additions & 14 deletions pyo3-ffi/src/acquire_gil.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
#if defined(_WIN32)
#include <windows.h>
#include <synchapi.h>
#else
#include <pthread.h>
#include <unistd.h>
Expand All @@ -19,18 +17,9 @@ int gil_func_name(void);

#if defined(_WIN32)
int wrapped_func_name(void) {
// Do the equivalent of https://github.com/python/cpython/issues/87135 (included
// in Python 3.14) to avoid pthread_exit unwinding the current thread, which tends
// to cause undefined behavior in Rust.
//
// Unfortunately, I don't know of a way to do a catch(...) from Rust.
__try {
return gil_func_name();
} __catch(void) {
while(1) {
SleepEx(INFINITE, TRUE);
}
}
// In MSVC, PyThread_exit_thread calls _endthreadex(0), which does not use SEH. This can
// cause Rust-level UB if there is pinned memory, but AFAICT there's not much we can do about it.
return gil_func_name();
}
#else
static void hang_thread(void *ignore) {
Expand Down
20 changes: 20 additions & 0 deletions pytests/src/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@ fn issue_219() {
Python::with_gil(|_| {});
}

#[pyclass]
struct LockHolder {
#[allow(unused)]
sender: std::sync::mpsc::Sender<()>,
}

// This will hammer the GIL once the retyrbed LockHolder is dropped.
#[pyfunction]
fn hammer_gil_in_thread() -> PyResult<LockHolder> {
let (sender, receiver) = std::sync::mpsc::channel();
std::thread::spawn(move || {
receiver.recv().ok();
loop {
Python::with_gil(|_py| ());
}
});
Ok(LockHolder { sender })
}

#[pyfunction]
fn get_type_fully_qualified_name<'py>(obj: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyString>> {
obj.get_type().fully_qualified_name()
Expand All @@ -35,6 +54,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!(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
20 changes: 20 additions & 0 deletions pytests/tests/test_hammer_gil_in_thread.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from pyo3_pytests import misc


def make_loop():
# create a reference loop that will only be destroyed when the GC is called at the end
# of execution
start = []
cur = [start]
for _ in range(1000 * 1000 * 10):
cur = [cur]
start.append(cur)
return start


# set a bomb that will explode when modules are cleaned up
loopy = [make_loop()]


def test_hammer_gil():
loopy.append(misc.hammer_gil_in_thread())

0 comments on commit abf1395

Please sign in to comment.