pydantic/monty · critical

Awaiter::GatherSlot gather id is not a GatherFuture

Error message

Awaiter::GatherSlot gather id is not a GatherFuture

What it means

When a child task of asyncio.gather completes successfully, deliver_awaiter_success reads the Awaiter::GatherSlot's gather id expecting a GatherFuture heap entry; any other type panics. This means the gather's id in the awaiter chain is stale or corrupt — normally impossible because the gather holds the awaiter alive.

Source

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

    ///   optionally switch VM context into `task_id` (calling
    ///   `remove_from_ready_queue` first since `deliver_value_to_task`
    ///   already queued it).
    /// - `None` if the chain was consumed by an intermediate gather that's
    ///   still in flight, or if the terminal task is gone (in which case
    ///   the value is dropped).
    fn deliver_awaiter_success(&mut self, mut awaiter: Awaiter, mut value: Value) -> Option<TaskId> {
        let this = self;
        loop {
            match awaiter {
                Awaiter::Task(t) => {
                    this.deliver_value_to_task(t, value);
                    return Some(t);
                }
                Awaiter::GatherSlot { gather, source } => {
                    let gather_val = Value::Ref(gather);
                    defer_drop!(gather_val, this);
                    let HeapReadOutput::GatherFuture(mut outer) = this.heap.read(gather) else {
                        panic!("Awaiter::GatherSlot gather id is not a GatherFuture")
                    };
                    let success = outer.resolve_child(this, source, value)?;
                    awaiter = success.awaiter;
                    value = Value::Ref(success.list_id);
                }
            }
        }
    }

    /// Walks the awaiter chain starting at `awaiter`, tearing each
    /// intermediate gather down with `error`, and fails the terminal task.
    ///
    /// Returns:
    /// - `Some(task_id)` if failure reached a live task — the caller may
    ///   optionally switch VM context into it; the task's state is already
    ///   `Failed(error)` so `resume_with_resolved_futures`'s post-loop check
    ///   will raise the exception when control returns. (Callers that need
    ///   the task in `Ready` instead — `handle_task_failure` — should

View on GitHub (pinned to adc986b362)

Solutions

  1. Verify the GatherFuture is kept referenced while any child task holds a GatherSlot awaiter
  2. Check py_dec_ref_ids on GatherFuture releases child awaiters only at destruction
  3. Assert the heap type when creating the GatherSlot awaiter
  4. Report upstream with a repro if hit unmodified
Defensive patterns

Strategy: fallback

Prevention

When it happens

Trigger: A gather child task finishes and the stored gather HeapId resolves to a non-GatherFuture entry, typically after the gather was freed early and the heap slot reused.

Common situations: Hit during development on gather internals (resolve_child, Awaiter chaining) or heap lifetime changes.

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/5300fe2707806710. Report an issue: GitHub.