seanmonstar/warp · error

polled after complete

Error message

polled after complete

What it means

This is an internal invariant panic from the `And` filter's combinator future. The future transitions to `State::Done` after both inner futures complete, and polling it afterwards is a protocol violation by the executor: futures must not be polled after returning `Poll::Ready`. The library panics instead of returning a value because there is no valid result to produce.

Solutions

  1. Use standard `.await` or `tokio::spawn` to drive the future instead of manually calling `poll()`.
  2. If manual polling is required, stop polling once `Poll::Ready` is returned, or wrap the future in `futures::future::Fuse` (or a `FusedFuture` guard) so it is never re-polled.
  3. Audit any custom executor or wrapper for the invariant: after `Poll::Ready`, drop the future or never call `poll` again.
  4. Check for shared/aliased use of the same future from two tasks, which can lead to a post-completion poll.

Example fix

// before
let mut fut = filter.and(other_filter);
let out1 = Pin::new(&mut fut).poll(cx);
let out2 = Pin::new(&mut fut).poll(cx); // panic: polled after complete

// after
let out = filter.and(other_filter).await; // drive via async/await
// or with manual polling:
if !fut.is_terminated() {
    let out = Pin::new(&mut fut).poll(cx);
}
Defensive patterns

Strategy: try-catch

Validate before calling

use futures::future::FusedFuture;
fn safe_to_poll<F: FusedFuture>(fut: &F) -> bool { !fut.is_terminated() }

Type guard

fn is_pollable(fut: &dyn FusedFuture) -> bool { !fut.is_terminated() }

Try / catch

// Panics cannot be caught normally; drive via await so it never occurs:
async { filter.and(other).await } // executor guarantees single poll to completion

Prevention

When it happens

Trigger: Polling the `And` combinator future (`StateProj::Done` branch in `poll`) a second time after it already returned `Poll::Ready(Ok(ex3))` — e.g. a buggy custom executor, wrapping the future in a combinator that re-polls, or storing the future and calling `poll` again after completion.

Common situations: Custom tokio executors that double-poll, home-grown `Future` wrappers (e.g. fused logic implemented incorrectly), task spawning frameworks that poll after `Ready`, or misuse of the raw future type instead of `.await` / `Pending`.

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/99ddb3887f04d447. Report an issue: GitHub.

Appendix: source

Thrown at src/filter/and.rs:93

    U::Error: CombineRejection<E>,
{
    type Output = Result<CombinedTuples<TE, U::Extract>, <U::Error as CombineRejection<E>>::One>;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        loop {
            match self.as_mut().project() {
                StateProj::First(first, second) => {
                    let ex1 = ready!(first.poll(cx))?;
                    let fut2 = second.filter(Internal);
                    self.set(State::Second(Some(ex1), fut2));
                }
                StateProj::Second(ex1, second) => {
                    let ex2 = ready!(second.poll(cx))?;
                    let ex3 = ex1.take().unwrap().combine(ex2);
                    self.set(State::Done);
                    return Poll::Ready(Ok(ex3));
                }
                StateProj::Done => panic!("polled after complete"),
            }
        }
    }
}

View on GitHub (pinned to ff34d7213e)