{"record":{"id":"c43e839e93a56ff3","repo":"actix/actix","slug":"maperr-must-not-be-polled-after-it-returned-poll","errorCode":null,"errorMessage":"MapErr must not be polled after it returned `Poll::Ready`","messagePattern":"MapErr must not be polled after it returned `Poll::Ready`","errorType":"panic","errorClass":"panic","httpStatus":null,"severity":"error","filePath":"actix/src/fut/try_future/map_err.rs","lineNumber":61,"sourceCode":"\n    fn poll(\n        mut self: Pin<&mut Self>,\n        act: &mut A,\n        ctx: &mut A::Context,\n        task: &mut Context<'_>,\n    ) -> Poll<Self::Output> {\n        match self.as_mut().project() {\n            MapProj::Incomplete { future, .. } => {\n                let output = ready!(future.try_poll(act, ctx, task));\n                match self.project_replace(MapErr::Complete) {\n                    MapProjReplace::Incomplete { f, .. } => {\n                        Poll::Ready(output.map_err(|err| f(err, act, ctx)))\n                    }\n                    MapProjReplace::Complete => unreachable!(),\n                }\n            }\n            MapProj::Complete => {\n                panic!(\"MapErr must not be polled after it returned `Poll::Ready`\")\n            }\n        }\n    }\n}\n","sourceCodeStart":43,"sourceCodeEnd":66,"githubUrl":"https://github.com/actix/actix/blob/36e5d97e41ebd16431c709365e6942db08d867d7/actix/src/fut/try_future/map_err.rs#L43-L66","documentation":"`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`.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nmatch fut.poll(act, ctx, task) {\n    Poll::Ready(res) => results.push(res),\n    Poll::Pending => {}\n}\n// next tick: fut still in vec, polled again -> panic\n\n// after\npoll_set.retain_mut(|fut| fut.poll(act, ctx, task).is_pending());","handlingStrategy":"try-catch","validationCode":"if mapped_done { return Poll::Ready(cached_result); }","typeGuard":null,"tryCatchPattern":"#[should_panic(expected = \"MapErr must not be polled\")] // tests only","preventionTips":["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."],"tags":["actix","try-future","map-err","poll","panic"],"backgroundTag":"invalid-state-transition","analyzedSha":"36e5d97e41ebd16431c709365e6942db08d867d7","analyzedAt":"2026-09-11T16:51:51.370Z","contentChangedAt":"2026-09-11T16:51:51.370Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}