pydantic/monty · error

gather commit frame id is not a GatherFuture

Error message

gather commit frame id is not a GatherFuture

What it means

During async gather commit processing, step_gather_commit reads the heap entry identified by GatherCommit::gather and expects it to be a GatherFuture. If the heap read returns any other variant, this panic fires. It is an internal invariant: only code that just created a gather future should ever push a commit frame for it, so a mismatch means heap bookkeeping is corrupted.

Source

Thrown at crates/monty/src/bytecode/vm/async_exec.rs:215

            awaiter,
            parent_slot,
            next: 0,
            results: (0..item_count).map(|_| None).collect(),
            pending_children: PendingChildren::new(),
        }
    }

    /// Commits `frame`'s items left-to-right into result slots or pending
    /// children, spawning coroutine children and installing awaiters on
    /// external futures as it goes.
    ///
    /// Stops early at a `Pending` nested gather, returning the frame the caller
    /// must commit before this one can continue; `Ok(None)` means every item is
    /// committed. Any error leaves the items handled so far in `frame`, which
    /// [`Self::unwind_gather_commits`] settles.
    fn step_gather_commit(&mut self, frame: &mut GatherCommit) -> Result<Option<GatherCommit>, RunError> {
        let HeapReadOutput::GatherFuture(gather) = self.heap.read(frame.gather) else {
            panic!("gather commit frame id is not a GatherFuture")
        };
        let gather_id = frame.gather;

        while frame.next < frame.results.len() {
            let idx = frame.next;
            let item_id = gather.get(self.heap).items[idx];
            if let Some(slots) = frame.pending_children.get_mut(&item_id) {
                // Dedup: We've already registered this item in this commit pass —
                // this is a duplicate item (e.g. `gather(coro, coro)`). Just
                // append the new slot index to the existing entry.
                slots.push(idx);
                frame.next += 1;
                continue;
            }

            let poll = match self.heap.read(item_id) {
                HeapReadOutput::Coroutine(coro) => {
                    // Reject reuse up-front: either the coroutine is no longer

View on GitHub (pinned to adc986b362)

Solutions

  1. Reproduce with a minimal async gather snippet and file a bug against monty with the code
  2. Check for a refcount/heap bug: run with --features memory-model-checks to validate heap invariants
  3. Inspect commit_gather_tree/open_gather_commit to confirm only fresh GatherFuture ids are stored in GatherCommit frames

Example fix

// before (interpreter bug)
let frame = GatherCommit { gather: wrong_id, .. };

// after
let HeapReadOutput::GatherFuture(_) = self.heap.read(gather_id) else { panic!(...) };
let frame = GatherCommit { gather: gather_id, .. };
Defensive patterns

Strategy: try-catch

Try / catch

// this is an interpreter-internal panic, not a catchable library error
// isolate: run each async snippet in a fresh session so a corrupted heap cannot poison subsequent runs

Prevention

When it happens

Trigger: An internal bug where frame.gather points at a heap entry that was replaced, freed, or never a GatherFuture (e.g. refcount bug causing reuse, or wrong HeapId stored in the commit frame).

Common situations: Not user-triggerable in normal operation; surfaces during interpreter development, after changes to gather/await machinery, or under heap corruption from another bug (possibly exposed with memory-model-checks).

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/914e8fedea59c89e. Report an issue: GitHub.