PyO3/pyo3 · critical
Cannot drop pointer into Python heap without the thread bein
Error message
Cannot drop pointer into Python heap without the thread being attached.
What it means
register_decref panics with this message and then aborts the process (PanicTrap) when reference-count dropping is attempted without a thread attached, under the pyo3_disable_reference_pool configuration (with pyo3_leak_on_drop_without_reference_pool off). PyO3 cannot safely decref a Python object from an unattached thread, so it deliberately aborts rather than corrupt refcounts.
Source
Thrown at src/internal/state.rs:364
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);
}
#[cfg(all(
pyo3_disable_reference_pool,
not(pyo3_leak_on_drop_without_reference_pool)
))]
{
let _trap = PanicTrap::new("Aborting the process to avoid panic-from-drop.");
panic!("Cannot drop pointer into Python heap without the thread being attached.");
}
}
/// Private helper function to check if we are currently in a GC traversal (as detected by PyO3).
#[cfg(any(not(Py_LIMITED_API), Py_3_11))]
pub(crate) fn is_in_gc_traversal() -> bool {
ATTACH_COUNT
.try_with(|c| c.get() == ATTACH_FORBIDDEN_DURING_TRAVERSE)
.unwrap_or(false)
}
/// Increments pyo3's internal attach count - to be called whenever an AttachGuard is created.
#[inline(always)]
fn increment_attach_count() {
// Ignores the error in case this function called from `atexit`.
let _ = ATTACH_COUNT.try_with(|c| {
let current = c.get();
if current < 0 {View on GitHub (pinned to ac9b6899d3)
Solutions
- Re-enable PyO3's reference pool (remove pyo3_disable_reference_pool from cargo features)
- Enable pyo3_leak_on_drop_without_reference_pool if leaking instead of aborting is acceptable
- Wrap drops in Python::with_gil(|py| { drop(obj) }) or Python::detach boundaries so decref happens on an attached thread
- Avoid moving Py<T> values to threads that drop them unattached; send owned handles back to a GIL-holding thread
Example fix
// before let obj: Py<MyType> = ...; std::thread::spawn(move || drop(obj)); // unattached drop: aborts // after let obj: Py<MyType> = ...; std::thread::spawn(move || Python::with_gil(|_py| drop(obj)));
Defensive patterns
Strategy: validation
Validate before calling
// before dropping a Py<T> on this thread, ensure attachment
fn drop_attached<T: pyo3::IntoPyObjectExt + 'static>(obj: pyo3::Py<T>) {
pyo3::Python::with_gil(|_py| drop(obj));
} Type guard
fn can_drop_py_value() -> bool { pyo3::Python::try_attach().is_some() || cfg!(not(feature = "pyo3_disable_reference_pool")) } Prevention
- Only drop Py<T> values while the thread is attached (inside with_gil) or via the reference pool
- Do not compile with pyo3_disable_reference_pool unless all unattached drops are avoided or leaks are accepted via pyo3_leak_on_drop_without_reference_pool
- Keep Python object ownership on the GIL-holding thread
When it happens
Trigger: Dropping a PyObject/GIL-independent wrapper (e.g. Py<T>) on a thread that never called Python::with_gil, when compiled with pyo3_disable_reference_pool and not pyo3_leak_on_drop_without_reference_pool.
Common situations: Sending Py<T> handles across threads and dropping them in workers; dropping cached objects at thread teardown; custom builds that disabled the reference pool for performance.
Related errors
- Attaching a thread to the interpreter is prohibited while a
- Cannot attach to the Python interpreter while it is finalizi
- Attaching a thread to the interpreter is currently prohibite
- Cannot clone pointer into Python heap without the thread bei
- {}
AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05).
Data as JSON: /api/errors/25278c09b0647c19.
Report an issue: GitHub.