pydantic/monty · critical

resolve_child called on a gather that was never awaited

Error message

resolve_child called on a gather that was never awaited

What it means

A GatherFuture tracks whether it has been awaited; resolve_child is called when a child finishes and requires the gather to be in the Awaited state. Pending means the child finished before the gather was ever awaited, which the spawn/await ordering should prevent, so it panics. Completed/Failed gathers are tolerated (child result discarded), only Pending is fatal.

Source

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

    /// `VM::handle_task_failure` for in-frame exceptions). Both settle the
    /// gather before any other sibling has a chance to resolve, and the
    /// siblings then keep running: their results arrive here afterwards and
    /// are dropped.
    ///
    /// Returns `None` while children are still in flight, or if the gather has
    /// already settled; otherwise `Some(GatherSuccess)` with the cached result
    /// list.
    fn resolve_child(&mut self, vm: &mut VM<'h>, child_id: HeapId, value: Value) -> Option<GatherSuccess> {
        // A sibling failed (or the commit pass rolled back) while this child
        // was still running: it has a result, and nowhere for it to go.
        // `Pending` is not reachable — a child only exists once awaited.
        match &self.get(vm.heap).state {
            GatherState::Awaited(_) => {}
            GatherState::Completed(_) | GatherState::Failed(_) => {
                value.drop_with(vm.heap);
                return None;
            }
            GatherState::Pending => panic!("resolve_child called on a gather that was never awaited"),
        }

        // Remove this child's slot-index mapping.
        let indices: SmallVec<[usize; 1]> = self
            .get_mut(vm.heap)
            .as_awaited_mut()
            .expect("resolve_child called on non-Awaited gather")
            .pending_children
            .remove(&child_id)
            .expect("resolve_child: child not registered with this gather");

        // Take `results` out so the writes can do their clones (which need
        // `&Heap` access) without fighting the `&mut`-chain that
        // `as_awaited_mut` requires. We put it back into the gather right
        // after, before the completion scan.
        let mut results = mem::take(
            &mut self
                .get_mut(vm.heap)

View on GitHub (pinned to adc986b362)

Solutions

  1. Ensure gather's await path transitions state to Awaited before children can ever complete — or before spawning them
  2. Verify resolve_child is only reachable through awaiters installed by the Awaited transition
  3. Check that Pending gathers never install GatherSlot awaiters on children
  4. Report the repro upstream if produced by ordinary asyncio.gather usage
Defensive patterns

Strategy: fallback

Prevention

When it happens

Trigger: A gather child task resolves while the GatherFuture is still Pending — e.g. the children were spawned and ran to completion before the gather object's __await__ registered the Awaited state, or an awaiter was attached to children without awaiting the gather itself.

Common situations: Hit when modifying gather's await path (exec_get_awaitable / resolve_child wiring) or when adding a way for children to start before the gather is awaited.

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/5b193069f84b2f80. Report an issue: GitHub.