pydantic/monty · critical

pending_externals entry doesn't point to an ExternalFuture

Error message

pending_externals entry doesn't point to an ExternalFuture

What it means

resolve_future looks up the future id from pending_externals and expects an ExternalFuture heap entry; a different type panics. pending_externals should only ever contain ids of ExternalFutures, so this indicates bookkeeping corruption or slot reuse. Not triggerable from user code.

Source

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

    /// `Resolved(value)`, and delivers `value` to the awaiter (if any).
    pub fn resolve_future(&mut self, call_id: u32, value: Value) {
        let call_id = CallId::new(call_id);

        let Some(future_id) = self.scheduler.take_pending_external(call_id) else {
            value.drop_with(self);
            return;
        };

        // Ensure future cleaned up on all paths
        let fut_val = Value::Ref(future_id);
        let this = self;
        defer_drop!(fut_val, this);

        let mut value_guard = DropGuard::new(value, this);
        let (value, this) = value_guard.as_parts_mut();

        let HeapReadOutput::ExternalFuture(mut fut) = this.heap.read(future_id) else {
            panic!("pending_externals entry doesn't point to an ExternalFuture")
        };

        let awaiter_and_value = match &mut fut.get_mut(this.heap).state {
            ExternalFutureState::Pending { awaiter } => awaiter.take().map(|a| (a, value.clone_with_heap(this.heap))),
            ExternalFutureState::Resolved(_) | ExternalFutureState::Failed(_) => {
                panic!("resolve_future: future was already resolved")
            }
        };

        let (value, this) = value_guard.into_parts();
        fut.get_mut(this.heap).state = ExternalFutureState::Resolved(value);

        if let Some((awaiter, value)) = awaiter_and_value {
            this.deliver_awaiter_success(awaiter, value);
        }
    }

    /// Pushes `value` onto `task_id`'s stack and marks it ready. If the task

View on GitHub (pinned to adc986b362)

Solutions

  1. Ensure pending_externals entries are removed exactly when the future is resolved/dropped, not earlier
  2. Check no code dec_refs an ExternalFuture while its id remains in pending_externals
  3. Assert the heap type when inserting into pending_externals and on removal
  4. Report with a repro script if hit without local changes
Defensive patterns

Strategy: fallback

Prevention

When it happens

Trigger: The host resolves a pending external future (resume_with_resolved_futures → resolve_future) and the stored id resolves to a non-ExternalFuture heap entry — stale id after entry freed and slot reused.

Common situations: Encountered while modifying external-future resolution or heap reuse in async_exec.rs; possibly after changing how pending_externals entries are removed.

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/8a172853cd1c899f. Report an issue: GitHub.