pydantic/monty · critical
Heap::boundary_uuid: entry already freed
Error message
Heap::boundary_uuid: entry already freed
What it means
`Heap::boundary_uuid` panics if `entries.entry(id)` returns `None` (the entry was already freed) or, one line later, if the entry is not an `Instance` or `Class`. It is used to obtain the boundary uuid identifying a class/instance for the host boundary (snapshot/pool identity). A panic means a caller passed a stale id or a non-boundary type — an internal invariant violation.
Source
Thrown at crates/monty/src/heap/mod.rs:1295
}
Some(WeakIndexKey::HostType(uuid)) if self.host_type_index.get(&uuid) == Some(&id) => {
self.host_type_index.remove(&uuid);
}
Some(WeakIndexKey::Boundary(_) | WeakIndexKey::HostType(_)) | None => {}
}
}
/// Boundary uuid of the sandbox class or instance at `id`, generated and
/// indexed on its first crossing to the host so the host can hand the
/// object back by id (see [`Heap::resolve_boundary_uuid`]).
///
/// # Panics
/// If `id` is not a live `Instance` or `Class` entry.
pub(crate) fn boundary_uuid(&mut self, id: HeapId) -> MontyUuid {
let mut entry = self
.entries
.entry(id)
.expect("Heap::boundary_uuid: entry already freed");
let uuid = match entry.get_mut().data.0.get_mut() {
HeapData::Instance(instance) => instance.boundary_uuid(),
HeapData::Class(class) => class.boundary_uuid(),
_ => unreachable!("Heap::boundary_uuid: only classes and instances carry a boundary uuid"),
};
self.boundary_index.insert(uuid, id);
uuid
}
/// The live sandbox class or instance that crossed to the host as `uuid`,
/// if it still exists; the index holds no reference, so the returned id is
/// borrowed.
#[must_use]
pub(crate) fn resolve_boundary_uuid(&self, uuid: &MontyUuid) -> Option<HeapId> {
self.boundary_index.get(uuid).copied()
}
/// The live type object for the host class `uuid`, if the sandbox holdsView on GitHub (pinned to adc986b362)
Solutions
- Check liveness before calling: use `try_entry`-style lookup or verify the ref is still reachable.
- Verify the value's type is Instance/Class before requesting a boundary uuid.
- Fix the upstream drop ordering that frees the object before boundary resolution completes (audit `drop_with`/GC paths).
Example fix
// before
let uuid = vm.heap.boundary_uuid(id); // id may be freed or non-class
// after
if let Some(kind) = reader.heap.try_entry_kind(id) {
assert!(matches!(kind, Instance | Class));
let uuid = vm.heap.boundary_uuid(id);
} Defensive patterns
Strategy: type-guard
Validate before calling
// confirm type before requesting a boundary uuid assert!(matches!(reader.heap.entries.entry(id).map(|e| e.data_kind()), Some(Instance | Class)), 'id must be a live Instance or Class');
Type guard
fn is_boundary_object(vm: &VM, value: &Value) -> bool { matches!(value, Value::Ref(_)) && matches!(value.py_type(vm), Type::Instance(_) | Type::Class(_)) } Prevention
- Resolve boundary uuids before the object can be dropped (call early in snapshot flows).
- Check the value type is Instance/Class before calling.
- Audit drop/GC ordering for objects whose ids are handed to host-boundary APIs.
When it happens
Trigger: Calling `boundary_uuid` with a `HeapId` of an object already freed by refcount drop or cycle collection; passing the id of a List/Dict/String instead of an Instance/Class.
Common situations: Snapshotting or resolving boundary identity after the referenced object was dropped; type confusion where a value's type was assumed to be class/instance without checking.
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
- HeapReader::read_ptr - id out of bounds
- HeapPtr::entry: slot has been freed
- already looked up
- entry just allocated
- HeapEntries::get - data already freed
AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13).
Data as JSON: /api/errors/28dd0bc6e78e892e.
Report an issue: GitHub.