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
1 change: 1 addition & 0 deletions newsfragments/6404.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix crash when a detached thread is terminated while trying to reattach during interpreter finalization.
35 changes: 20 additions & 15 deletions pytests/src/misc.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::cell::Cell;

use pyo3::{
prelude::*,
types::{PyDict, PyString},
Expand Down Expand Up @@ -31,32 +33,35 @@ fn hammer_attaching_in_thread() -> LockHolder {
LockHolder { sender }
}

/// Wrapper to mark Receiver as Sync.
struct SyncReceiver<T>(std::sync::mpsc::Receiver<T>);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

not needed since Rust 1.73


impl<T> std::ops::Deref for SyncReceiver<T> {
type Target = std::sync::mpsc::Receiver<T>;
#[pyclass]
struct MustDropWhileAttached;

fn deref(&self) -> &Self::Target {
&self.0
impl Drop for MustDropWhileAttached {
fn drop(&mut self) {
// SAFETY: always callable; fatal error (abort) if the thread is not attached.
unsafe { pyo3::ffi::PyThreadState_Get() };
}
}

// SAFETY: only used to allow the receiver to be used after detaching
unsafe impl<T> Sync for SyncReceiver<T> {}
thread_local! {
// Dropped when the thread exits, which on older CPython happens inside
// PyEval_RestoreThread when reattaching during finalization.
static DROPPED_ON_THREAD_EXIT: Cell<Option<Py<MustDropWhileAttached>>> = const { Cell::new(None) };
}

#[pyfunction]
fn detach_during_finalization() -> LockHolder {
fn detach_during_finalization(py: Python<'_>) -> LockHolder {
let (sender, receiver) = std::sync::mpsc::channel();
let receiver = SyncReceiver(receiver);
let (ready_sender, ready_receiver) = std::sync::mpsc::channel();
std::thread::spawn(move || {
Python::attach(|py| {
py.detach(|| {
receiver.recv().ok();
// Interpreter is finalizing while we try to reattach after returning
});
DROPPED_ON_THREAD_EXIT.set(Some(Py::new(py, MustDropWhileAttached).unwrap()));
ready_sender.send(()).unwrap();
py.detach(move || receiver.recv().ok());
// Interpreter is finalizing while we try to reattach after returning
});
});
py.detach(move || ready_receiver.recv()).unwrap();
LockHolder { sender }
}

Expand Down
2 changes: 1 addition & 1 deletion src/internal/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,9 @@ impl SuspendAttach {

impl Drop for SuspendAttach {
fn drop(&mut self) {
ATTACH_COUNT.with(|c| c.set(self.count));
// SAFETY: tstate come from call to PyEval_SaveThread and it was not re-attached yet
unsafe { ffi::PyEval_RestoreThread(self.tstate) };
ATTACH_COUNT.with(|c| c.set(self.count));
// Update counts of `Py<T>` that were dropped while not attached.
#[cfg(not(pyo3_disable_reference_pool))]
{
Expand Down
Loading