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
- Drive the future via `.await` or `tokio::spawn` instead of manual `poll`.
- Use `Fuse` + `is_terminated()` before any manual poll.
- Break polling loops the moment `Poll::Ready` is observed.
- 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
- Return immediately after `Poll::Ready`.
- Use standard runtime spawning instead of custom poll drivers.
- Test custom executors with loom/futures-test for double-poll bugs.
- Never reuse completed futures from caches.
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
- 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/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)