pydantic/monty · critical
already looked up
Error message
already looked up
What it means
During iterative cycle collection (`collect_cycles_inner`), after `remove_weak_index_entry` the code re-looks up an entry it believes it just resolved and calls `.expect("already looked up")`. The comment notes freeing cannot happen through `HeapPtr` because it is borrowed from `&self` on `StableHeap`. A panic here means the entry vanished between the two lookups — a drop/weak-index bookkeeping bug (e.g. a weak-index entry pointing at an already-freed id).
Source
Thrown at crates/monty/src/heap/mod.rs:1430
heap_entry.readers.get() == 0,
"Heap::dec_ref: cannot free HeapId({}) with {} active reader(s)",
current_id.index(),
heap_entry.readers.get(),
);
// If the entry was a pending cycle candidate, decrement
// `purple_count` to reflect that it is leaving the heap before
// the collector reaches it.
if heap_entry.color.get() == CcColor::Purple {
reader.heap.purple_count -= 1;
}
// Remove weak-index entries before the slot becomes available for reuse.
let weak_key = Self::weak_index_key(ptr.data(reader));
reader.heap.remove_weak_index_entry(weak_key, current_id);
// It is not possible to free from `HeapPtr` because it is created through
// a &self borrow on `StableHeap`. At least this repeated lookup is already
// on the slow path.
let mut value = reader.heap.entries.entry(current_id).expect("already looked up").free();
// Collect child IDs and push onto work stack for iterative processing
py_dec_ref_ids_for_data(value.data.0.get_mut(), &mut work_stack);
}
let Some(next_id) = work_stack.pop() else {
break;
};
current_id = next_id;
}
});
}
/// Returns an immutable reference to the heap data stored at the given ID. This can be more efficient
/// than `.read()` for short-lived borrows that need read-only access (avoids reader bookkeeping).
///
/// # Panics
/// Panics if the value ID is invalid, the value has already been freed,View on GitHub (pinned to adc986b362)
Solutions
- Ensure every weak-index insertion has a matching removal on drop (via `py_dec_ref_ids` or the weak container's own drop path).
- Replace the `expect` with a tolerant `if let Some(entry) = ...` that skips ids already absent, logging instead of panicking.
- Add a debug assertion/invariant check that weak-index entries always reference live ids before collection.
- Reproduce with a cyclic-garbage test involving weak refs and run under `--features memory-model-checks`.
Example fix
// before
let mut value = reader.heap.entries.entry(current_id).expect("already looked up").free();
// after
if let Some(mut entry) = reader.heap.entries.entry(current_id) {
let value = entry.free();
py_dec_ref_ids_for_data(value.data.0.get_mut(), &mut work_stack);
} // else: stale weak-index entry, already freed — skip Defensive patterns
Strategy: validation
Validate before calling
// before cycle collection, verify weak-index integrity
for (key, id) in &heap.weak_index { assert!(id.0 < heap.entries.len(), 'weak index references out-of-bounds id'); } Prevention
- Remove weak-index entries on every drop path of weak-referencing objects.
- Treat stale weak-index entries as skippable, not panics.
- Add invariant checks that weak-index ids are live before collection.
- Cover weakref cycles with memory-model-checks tests.
When it happens
Trigger: Running `collect_cycles` when the weak index contains a stale `HeapId` that was already freed; cyclic garbage involving weak references (WeakRef) where the index and refcounts are out of sync.
Common situations: Adding new weak-reference support or weak-keyed containers (WeakValueDict/WeakKeyDict) with incorrect removal on drop; objects dropped outside the collector still present in `weak_index`.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- HeapPtr::entry: slot has been freed
- HeapReader::read_ptr - id out of bounds
- Heap::boundary_uuid: entry already freed
- entry just allocated
- HeapEntries::get - data already freed
AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13).
Data as JSON: /api/errors/d0c254f44b2321ca.
Report an issue: GitHub.