{"record":{"id":"d0c254f44b2321ca","repo":"pydantic/monty","slug":"already-looked-up","errorCode":null,"errorMessage":"already looked up","messagePattern":"already looked up","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/monty/src/heap/mod.rs","lineNumber":1430,"sourceCode":"                        heap_entry.readers.get() == 0,\n                        \"Heap::dec_ref: cannot free HeapId({}) with {} active reader(s)\",\n                        current_id.index(),\n                        heap_entry.readers.get(),\n                    );\n                    // If the entry was a pending cycle candidate, decrement\n                    // `purple_count` to reflect that it is leaving the heap before\n                    // the collector reaches it.\n                    if heap_entry.color.get() == CcColor::Purple {\n                        reader.heap.purple_count -= 1;\n                    }\n                    // Remove weak-index entries before the slot becomes available for reuse.\n                    let weak_key = Self::weak_index_key(ptr.data(reader));\n                    reader.heap.remove_weak_index_entry(weak_key, current_id);\n\n                    // It is not possible to free from `HeapPtr` because it is created through\n                    // a &self borrow on `StableHeap`. At least this repeated lookup is already\n                    // on the slow path.\n                    let mut value = reader.heap.entries.entry(current_id).expect(\"already looked up\").free();\n\n                    // Collect child IDs and push onto work stack for iterative processing\n                    py_dec_ref_ids_for_data(value.data.0.get_mut(), &mut work_stack);\n                }\n\n                let Some(next_id) = work_stack.pop() else {\n                    break;\n                };\n                current_id = next_id;\n            }\n        });\n    }\n\n    /// Returns an immutable reference to the heap data stored at the given ID. This can be more efficient\n    /// than `.read()` for short-lived borrows that need read-only access (avoids reader bookkeeping).\n    ///\n    /// # Panics\n    /// Panics if the value ID is invalid, the value has already been freed,","sourceCodeStart":1412,"sourceCodeEnd":1448,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty/src/heap/mod.rs#L1412-L1448","documentation":"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).","triggerScenarios":"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.","commonSituations":"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`.","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`."],"exampleFix":"// before\nlet mut value = reader.heap.entries.entry(current_id).expect(\"already looked up\").free();\n// after\nif let Some(mut entry) = reader.heap.entries.entry(current_id) {\n    let value = entry.free();\n    py_dec_ref_ids_for_data(value.data.0.get_mut(), &mut work_stack);\n} // else: stale weak-index entry, already freed — skip","handlingStrategy":"validation","validationCode":"// before cycle collection, verify weak-index integrity\nfor (key, id) in &heap.weak_index { assert!(id.0 < heap.entries.len(), 'weak index references out-of-bounds id'); }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["rust","heap","gc","cycle-collection","weakref","panic"],"backgroundTag":"internal-invariant-violation","analyzedSha":"adc986b362e3961f407868cb118a99fe831b9e61","analyzedAt":"2026-09-13T19:19:18.698Z","contentChangedAt":"2026-09-13T19:19:18.698Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}