PyO3/pyo3 · error

Attaching a thread to the interpreter is currently prohibite

Error message

Attaching a thread to the interpreter is currently prohibited.

What it means

ForbidAttaching::bail's fallback arm panics when attaching is currently prohibited for a reason other than GC traversal (counter is non-zero but not the traversal sentinel). It indicates a nested/overlapping ForbidAttaching guard or a re-entrancy bug in PyO3-internal critical sections.

Source

Thrown at src/internal/state.rs:339

impl ForbidAttaching {
    const FORBIDDEN_DURING_TRAVERSE: &'static str = "Attaching a thread to the interpreter is prohibited while a __traverse__ implementation is running.";

    /// Lock access to the interpreter while an implementation of `__traverse__` is running
    pub fn during_traverse() -> Self {
        Self::new(ATTACH_FORBIDDEN_DURING_TRAVERSE)
    }

    fn new(reason: isize) -> Self {
        let count = ATTACH_COUNT.with(|c| c.replace(reason));

        Self { count }
    }

    #[cold]
    fn bail(current: isize) {
        match current {
            ATTACH_FORBIDDEN_DURING_TRAVERSE => panic!("{}", Self::FORBIDDEN_DURING_TRAVERSE),
            _ => panic!("Attaching a thread to the interpreter is currently prohibited."),
        }
    }
}

impl Drop for ForbidAttaching {
    fn drop(&mut self) {
        ATTACH_COUNT.with(|c| c.set(self.count));
    }
}

/// Registers a Python object pointer inside the release pool, to have its reference count decreased
/// the next time the thread is attached in pyo3.
#[inline]
pub fn register_decref(obj: Py<PyAny>) {
    #[cfg(not(pyo3_disable_reference_pool))]
    {
        POOL.register_decref(obj);
    }

View on GitHub (pinned to ac9b6899d3)

Solutions

  1. Identify the enclosing critical section and remove the nested attach call
  2. Avoid re-entrant PyO3 API calls from within code that holds internal guards (drops, callbacks, destructors)
  3. Queue the work and execute it after the outer operation completes
  4. Update PyO3 — re-entrancy panics may be fixed in newer releases

Example fix

// before
impl Drop for MyObj { fn drop(&mut self) { Python::with_gil(|py| log(py)); } } // if dropped inside a guarded section
// after
impl Drop for MyObj { fn drop(&mut self) { DEFER.with(|q| q.borrow_mut().push(log_task())); } }
Defensive patterns

Strategy: validation

Validate before calling

// avoid re-entrant attach; check you are not inside a nested PyO3 critical section before calling with_gil

Try / catch

std::panic::catch_unwind(|| pyo3::Python::with_gil(|py| work(py))).unwrap_or_else(|_| schedule_deferred_work());

Prevention

When it happens

Trigger: Attempting to attach while any ForbidAttaching guard is held outside __traverse__ — typically caused by re-entrant calls into APIs that install such a guard, or calling attach from code invoked by another attach-forbidding critical section.

Common situations: Deep re-entrancy: a callback/drop handler invoked inside a protected section calls back into PyO3; misuse of internal gc-safety utilities; bugs in custom wrappers around PyO3 internals.

Related errors


AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05). Data as JSON: /api/errors/3eff5bd9afd52ec3. Report an issue: GitHub.