actix/actix · error · panic
MapErr must not be polled after it returned `Poll::Ready`
Error message
MapErr must not be polled after it returned `Poll::Ready`
What it means
`MapErr` transforms the error of a `TryActorFuture` via a closure. After it yields `Poll::Ready` it moves to `Complete`, having consumed the closure `f`; any further poll cannot proceed, so actix panics with `MapErr must not be polled after it returned Poll::Ready`.
Solutions
- Treat `Poll::Ready` as terminal: drop the `MapErr` future immediately after.
- In manual polling loops, remove futures that returned `Ready`.
- Delegate polling to actix's spawn/context machinery instead of calling `try_poll` yourself.
- If you need the mapped error again, store the result rather than re-polling.
Example fix
// before
match fut.poll(act, ctx, task) {
Poll::Ready(res) => results.push(res),
Poll::Pending => {}
}
// next tick: fut still in vec, polled again -> panic
// after
poll_set.retain_mut(|fut| fut.poll(act, ctx, task).is_pending()); Defensive patterns
Strategy: try-catch
Validate before calling
if mapped_done { return Poll::Ready(cached_result); } Try / catch
#[should_panic(expected = "MapErr must not be polled")] // tests only
Prevention
- Treat the first Ready as terminal; drop the future right away.
- Wrap manually polled futures in a struct with a `done` flag.
- Use retain(is_pending) pruning in any custom executor.
When it happens
Trigger: Polling a `MapErr`-wrapped try future after completion — retaining it in a poll-everything loop, or re-polling within nested combinators after the inner future resolved.
Common situations: Error-handling layers stacked on actor futures that are stored and re-polled; manual future drivers in actors; tests that poll a resolved future to check idempotency.
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
- ActorFuture polled after finish
- MapOk must not be polled after it returned `Poll::Ready`
- Map must not be polled after it returned `Poll::Ready`
- ActorFuture polled after finish
- Fold polled after completion
AI-assisted analysis of actix/actix@36e5d97e41 (2026-09-11).
Data as JSON: /api/errors/c43e839e93a56ff3.
Report an issue: GitHub.
Appendix: source
Thrown at actix/src/fut/try_future/map_err.rs:61
fn poll(
mut self: Pin<&mut Self>,
act: &mut A,
ctx: &mut A::Context,
task: &mut Context<'_>,
) -> Poll<Self::Output> {
match self.as_mut().project() {
MapProj::Incomplete { future, .. } => {
let output = ready!(future.try_poll(act, ctx, task));
match self.project_replace(MapErr::Complete) {
MapProjReplace::Incomplete { f, .. } => {
Poll::Ready(output.map_err(|err| f(err, act, ctx)))
}
MapProjReplace::Complete => unreachable!(),
}
}
MapProj::Complete => {
panic!("MapErr must not be polled after it returned `Poll::Ready`")
}
}
}
}
View on GitHub (pinned to 36e5d97e41)