PyO3/pyo3 · error
Cannot attach to the Python interpreter while it is…
Error message
Cannot attach to the Python interpreter while it is finalizing.
What it means
This panic fires from Python::attach()/AttachGuard::attach when the interpreter has begun finalizing (Python 3.13+, via AttachError::Finalizing). Once CPython is shutting down, attaching a thread to obtain the GIL is unsafe and undefined, so PyO3 refuses with a panic instead of blocking forever or crashing in C code.
Solutions
- Ensure worker threads are joined/stopped before Py_Finalize or process exit
- Use Python::with_embedded_python_interpreter/teardown utilities that shut threads down first
- Check the interpreter isn't finalizing with Python::version/pool guards — use try_attach-based APIs (Python::try_attach) and skip work on error
- In 3.13+ embeddings, call Py_Finalize only after all PyO3-using threads have terminated
Example fix
// before
std::thread::spawn(|| Python::with_gil(|py| run(py))); // may run during finalize
// after
let handle = std::thread::spawn(|| Python::with_gil(|py| run(py)));
handle.join().expect("worker");
// then finalize the interpreter Defensive patterns
Strategy: validation
Validate before calling
// PyO3 0.23+: check via pool/attach result before work
if pyo3::Python::try_attach().is_none() { return; } // skip if not attachable Type guard
fn interpreter_usable() -> bool { pyo3::Python::try_attach().is_some() } Try / catch
std::panic::catch_unwind(|| pyo3::Python::with_gil(|py| work(py))).unwrap_or_default();
Prevention
- Join all Python-touching threads before Py_Finalize / process exit
- Stop background workers (rayon/tokio) during interpreter teardown
- Avoid Python calls in Drop impls that may run at shutdown
When it happens
Trigger: Calling Python::with_gil / Python::attach during interpreter shutdown: destructors/drop handlers running after Py_Finalize, daemon threads still executing Python calls, atexit or __del__ code that re-enters PyO3 late in teardown.
Common situations: Background worker threads (rayon/tokio/std threads) still alive when the embedding application or interpreter calls Py_Finalize; objects dropped in process exit paths; long-lived caches flushed after finalization started.
Related errors
- Attaching a thread to the interpreter is currently…
- Attaching a thread to the interpreter is prohibited while a…
- 0 < N <= 12
- Cannot clone pointer into Python heap without the thread…
- Cannot drop pointer into Python heap without the thread…
AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05).
Data as JSON: /api/errors/6603801bcbd91e62.
Report an issue: GitHub.
Appendix: source
Thrown at src/internal/state.rs:81
///
/// If the thread was already attached via PyO3, this returns
/// `AttachGuard::Assumed`. Otherwise, the thread will attach now and
/// `AttachGuard::Ensured` will be returned.
pub(crate) fn attach() -> Self {
match Self::try_attach() {
Ok(guard) => guard,
Err(AttachError::ForbiddenDuringTraverse) => {
panic!("{}", ForbidAttaching::FORBIDDEN_DURING_TRAVERSE)
}
Err(AttachError::NotInitialized) => {
// try to initialize the interpreter and try again
crate::interpreter_lifecycle::ensure_initialized();
// SAFETY: just initialized the interpreter
unsafe { Self::do_attach_unchecked() }
}
#[cfg(Py_3_13)]
Err(AttachError::Finalizing) => {
panic!("Cannot attach to the Python interpreter while it is finalizing.");
}
}
}
/// Variant of the above which will will return gracefully if the interpreter cannot be attached to.
pub(crate) fn try_attach() -> Result<Self, AttachError> {
match ATTACH_COUNT.try_with(|c| c.get()) {
Ok(i) if i > 0 => {
// SAFETY: We just checked that the thread is already attached.
return Ok(unsafe { Self::assume() });
}
// Cannot attach during GC traversal.
Ok(ATTACH_FORBIDDEN_DURING_TRAVERSE) => {
return Err(AttachError::ForbiddenDuringTraverse)
}
// other cases handled below
_ => {}
}View on GitHub (pinned to ac9b6899d3)