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

  1. Ensure every weak-index insertion has a matching removal on drop (via `py_dec_ref_ids` or the weak container's own drop path).
  2. Replace the `expect` with a tolerant `if let Some(entry) = ...` that skips ids already absent, logging instead of panicking.
  3. Add a debug assertion/invariant check that weak-index entries always reference live ids before collection.
  4. 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

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


AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13). Data as JSON: /api/errors/d0c254f44b2321ca. Report an issue: GitHub.