seanmonstar/warp · error

polled after complete

Error message

polled after complete

What it means

Internal invariant panic in the `Recover` filter future. After the recovery future completes, the state machine is set to `State::Done` and the result returned; any later poll violates the `Future` contract and panics because the computation is finished.

Solutions

  1. Drive the future via `.await` or `tokio::spawn` instead of manual `poll`.
  2. Use `Fuse` + `is_terminated()` before any manual poll.
  3. Break polling loops the moment `Poll::Ready` is observed.
  4. Drop the future after completion; never store it for re-polling.

Example fix

// before
match fut.as_mut().poll(cx) { Poll::Ready(v) => done(v), _ => {} }
// wake handler polls again later -> panic

// after
let mut fut = filter.recover(h).fuse();
if !fut.is_terminated() {
    match fut.poll_unpin(cx) { /* ... */ }
}
Defensive patterns

Strategy: try-catch

Validate before calling

use futures::future::FusedFuture;
if fut.is_terminated() { return; } // never poll a completed Recover future

Type guard

fn pollable(fut: &(impl FusedFuture + ?Sized)) -> bool { !fut.is_terminated() }

Try / catch

let mut fut = filter.recover(h).fuse();
if !fut.is_terminated() {
    match fut.poll_unpin(cx) { /* handle Ready/Pending once */ }
}

Prevention

When it happens

Trigger: Polling the `Recover` combinator future after `Poll::Ready` was returned (`StateProj::Done` arm) — via custom executors, repeated wake dispatch, or re-polling a future cached after completion.

Common situations: Custom scheduler bugs, wrappers that don't implement `FusedFuture`, `select!`-style code written without termination checks, accidental shared ownership of the future.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of seanmonstar/warp@ff34d7213e (2026-09-09). Data as JSON: /api/errors/3d3d9f85ca67934f. Report an issue: GitHub.

Appendix: source

Thrown at src/filter/recover.rs:106

        loop {
            let pin = self.as_mut().project();
            let (err, second) = match pin.state.project() {
                StateProj::First(first, second) => match ready!(first.try_poll(cx)) {
                    Ok(ex) => return Poll::Ready(Ok((Either::A(ex),))),
                    Err(err) => (err, second),
                },
                StateProj::Second(second) => {
                    let ex2 = match ready!(second.try_poll(cx)) {
                        Ok(ex2) => Ok((Either::B((ex2,)),)),
                        Err(e) => Err(e),
                    };
                    self.set(RecoverFuture {
                        state: State::Done,
                        ..*self
                    });
                    return Poll::Ready(ex2);
                }
                StateProj::Done => panic!("polled after complete"),
            };

            pin.original_path_index.reset_path();
            let fut2 = second.call(err);
            self.set(RecoverFuture {
                state: State::Second(fut2),
                ..*self
            });
        }
    }
}

View on GitHub (pinned to ff34d7213e)