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
- Use standard `.await` or `tokio::spawn` to drive the future instead of manually calling `poll()`.
- 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.
- Audit any custom executor or wrapper for the invariant: after `Poll::Ready`, drop the future or never call `poll` again.
- 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
- Never call `poll` manually; use async/await or a runtime spawn.
- Wrap manually-polled futures in `futures::future::Fuse` and check `is_terminated()`.
- Break polling loops immediately on `Poll::Ready`.
- Ensure one owner polls each future instance.
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
- polled after complete
- polled after complete
- polled after complete
- polled after complete
- polled after complete
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)