Heap::dec_ref: object already freed under sustained MontyRepl.feed_run() pressure
# `Heap::dec_ref: object already freed` under sustained `MontyRepl.feed_run()` pressure
## Summary
`MontyRepl` aborts the host process with a Rust panic during a heavy aggregation loop over a deeply-nested injected data structure. The panic is on Monty's internal heap reference counting, originates inside the interpreter, and cannot be caught from Python (`MontyRuntimeError` is not raised — the process aborts).
```
thread '<unnamed>' panicked at crates/monty/src/heap.rs:938:34:
HeapEntries::get - data already freed
thread '<unnamed>' panicked at crates/monty/src/heap.rs:956:39:
Heap::dec_ref: object already freed
panic in a destructor during cleanup
thread caused non-unwinding panic. aborting.
```
Reproduces on the latest release (`pydantic-monty 0.0.17`) and on `0.0.15`. The GC-related fixes in 0.0.17 (#371, #376, #381) do not address it.
[Here is a script to reproduce the error](https://gist.github.com/dbreunig/6daa83efb2ae08f8357172ce1124f675).
## Environment
- `pydantic-monty`: **0.0.17** (also reproduced on 0.0.15)
- Python: **3.12.4**
- OS: **macOS 15.7.3, arm64 (Apple Silicon)**
- Install: `pip install pydantic-monty` (PyPI wheel)
## Expected behavior
`MontyRepl.feed_run()` should either succeed, or raise a catchable `MontyRuntimeError` if a resource limit is exceeded. It should never abort the host process from Rust.
## What seems to drive it
Heap pressure across multiple `feed_run()` calls against a persistent REPL, not raw record count alone. Bisecting the repro:
- **String variety matters more than `NUM_RECORDS`.** An earlier version used 8 fixed strings via `random.choice`; the panic did not reproduce even at 100K records. Switching to template-formatted, mostly-unique strings reproduced consistently.
- **The deliberately-failing `iter 0`** (`from collections import Counter`, which Monty rejects) appears to participate. It walks Monty's exception/unwind path before the heavy iteration runs. Whether the panic still fires without it is worth confirming; in our hands, the combination above panics every run.
- **Repeating the heavy aggregator** (`REPEATS = 3`) keeps `monthly` and its nested dicts alive across calls, growing the heap further between iterations.
## What we ruled out
- Not a user-code error — the symptoms (`MontySyntaxError`, `MontyRuntimeError`) would surface as catchable Python exceptions.
- Not specific to any embedding/adapter (e.g. DSPy) — repro uses `MontyRepl` directly.
- Not the `0.0.17` GC fixes (#371, #376, #381) — same panic before and after upgrading.
## Notes for triage
- Panic message `Heap::dec_ref: object already freed` plus the destructor-cleanup secondary panic is a refcount-management bug rather than a resource-limit issue.
- `HeapEntries::get - data already freed` at `heap.rs:938` and `Heap::dec_ref: object already freed` at `heap.rs:956` are the right pointers in the source.
And yes: I ran into this error while running an RLM 😉
0 条评论