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

  1. Ensure worker threads are joined/stopped before Py_Finalize or process exit
  2. Use Python::with_embedded_python_interpreter/teardown utilities that shut threads down first
  3. Check the interpreter isn't finalizing with Python::version/pool guards — use try_attach-based APIs (Python::try_attach) and skip work on error
  4. 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

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


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)