seanmonstar/warp · error

polled after complete

Error message

polled after complete

What it means

Internal invariant panic in the `OrElse` filter future. When the recovery future (`second.call(err)`) resolves, state becomes `State::Done` and the result is returned; polling afterwards is a `Future` contract violation with no recoverable value, so the library panics.

Solutions

  1. Use `.await` or a standard runtime (`tokio::spawn`) to drive the future.
  2. Wrap in `fuses::future::Fuse` and guard manual polls with `is_terminated()`.
  3. Ensure the poll loop breaks immediately on `Poll::Ready`.
  4. Ensure only one task/context polls a given future instance.

Example fix

// before
let r1 = Pin::new(&mut fut).poll(cx)?;
let r2 = Pin::new(&mut fut).poll(cx)?; // panic: polled after complete

// after
let out = filter.or_else(recover).await; // runtime drives once to completion
Defensive patterns

Strategy: try-catch

Validate before calling

use futures::future::FusedFuture;
assert!(!fut.is_terminated(), "OrElse future already completed");

Type guard

fn alive<F: FusedFuture>(fut: &F) -> bool { !fut.is_terminated() }

Try / catch

// panics aren't catchable; prevent structurally:
let out = filter.or_else(recover).await; // runtime handles single-poll contract

Prevention

When it happens

Trigger: Polling the `OrElse` combinator future again after it returned `Poll::Ready(ex2)` from the recovery path — e.g. a poll loop that continues after `Ready` or an executor that re-dispatches completed tasks.

Common situations: Hand-written executors, misuse inside combinators like `join_all` with a future reused across iterations, wrappers lacking `FusedFuture` semantics, double wake handling.

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/13120bdedc28bf80. Report an issue: GitHub.

Appendix: source

Thrown at src/filter/or_else.rs:96

    type Output = Result<<F::Output as TryFuture>::Ok, <F::Output as TryFuture>::Error>;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        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(ex)),
                    Err(err) => (err, second),
                },
                StateProj::Second(second) => {
                    let ex2 = ready!(second.try_poll(cx));
                    self.set(OrElseFuture {
                        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(OrElseFuture {
                state: State::Second(fut2),
                ..*self
            });
        }
    }
}

View on GitHub (pinned to ff34d7213e)