pydantic/monty · critical

gather_id doesn't point to a GatherFuture

Error message

gather_id doesn't point to a GatherFuture

What it means

When a task awaiting a gather future fails, the scheduler looks up the gather future on the heap; this panic fires if the stored HeapId is not a `GatherFuture`. Gather ids come from the scheduler's own allocation, so this means heap corruption, premature free of the gather, or a scheduler bug — not a Python-level error.

Source

Thrown at crates/monty/src/bytecode/vm/scheduler.rs:513

            None => None,
            Some(Awaiter::Task(task_id)) => {
                // A task's own awaiter is the `GatherSlot` its gather gave it;
                // borrow that gather's id without taking the task's ref, which
                // stays until the task is cancelled.
                let gather_id = match self.tasks.get(&task_id).and_then(|t| t.awaiter.as_ref()) {
                    Some(Awaiter::GatherSlot { gather, .. }) => Some(*gather),
                    Some(Awaiter::Task(_)) | None => None,
                };
                match gather_id {
                    // The task that was awaiting the future is itself in a
                    // gather. Settle that gather here so the failure anchors
                    // at the same site as the resolution; return the gather's
                    // awaiter for the caller to chain. A gather that has
                    // already settled takes nothing more, and the failure is
                    // the parked task's own to raise.
                    Some(gather_id) => {
                        let HeapReadOutput::GatherFuture(mut gather_rd) = heap.read(gather_id) else {
                            panic!("gather_id doesn't point to a GatherFuture")
                        };
                        let outer_awaiter = gather_rd.fail(heap, error);
                        // Dropped before the cancel below: that releases the
                        // task's `GatherSlot` inc_ref on this gather, which
                        // must not run under a live reader on it.
                        drop(gather_rd);
                        match outer_awaiter {
                            // The gather handed the failure outwards, so
                            // nothing will ever deliver it to `task_id`. Its
                            // `await` raised, so it is finished — drop it, or
                            // it stays `Blocked` forever on a future that was
                            // just failed and unregistered, holding its
                            // coroutine and this gather alive for the rest of
                            // the session. Siblings are untouched: the task is
                            // blocked on an external future, so the cancel
                            // walk finds no gather to cascade into.
                            Some(outer) => {
                                self.cancel_task(task_id, heap);

View on GitHub (pinned to adc986b362)

Solutions

  1. File a bug with the async reproducer
  2. Audit GatherFuture free/dec_ref ordering (the code deliberately drops the reader before releasing the GatherSlot inc_ref — preserve that ordering in edits)
  3. Run async/gather tests with memory-model-checks enabled

Example fix

// not applicable — internal scheduler invariant
Defensive patterns

Strategy: fallback

Try / catch

// Internal panic — no user-side catch; isolate and report.
match scheduler.fail_for_call(call_id, &err, heap) {
    Some(awaiter) => chain(awaiter),
    None => (),
}

Prevention

When it happens

Trigger: `fail_for_call` unwinding an awaiter chain that reaches an `Awaiter::Task` whose linked gather HeapId resolves to a non-GatherFuture heap entry; via heap corruption or a gather-lifecycle bug.

Common situations: Developing/fuzzing asyncio-style gather support; patches that free a GatherFuture while tasks still hold `GatherSlot` references to it.

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/354b149db6c42c71. Report an issue: GitHub.