{"record":{"id":"13120bdedc28bf80","repo":"seanmonstar/warp","slug":"polled-after-complete-13120b","errorCode":null,"errorMessage":"polled after complete","messagePattern":"polled after complete","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/filter/or_else.rs","lineNumber":96,"sourceCode":"    type Output = Result<<F::Output as TryFuture>::Ok, <F::Output as TryFuture>::Error>;\n\n    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {\n        loop {\n            let pin = self.as_mut().project();\n            let (err, second) = match pin.state.project() {\n                StateProj::First(first, second) => match ready!(first.try_poll(cx)) {\n                    Ok(ex) => return Poll::Ready(Ok(ex)),\n                    Err(err) => (err, second),\n                },\n                StateProj::Second(second) => {\n                    let ex2 = ready!(second.try_poll(cx));\n                    self.set(OrElseFuture {\n                        state: State::Done,\n                        ..*self\n                    });\n                    return Poll::Ready(ex2);\n                }\n                StateProj::Done => panic!(\"polled after complete\"),\n            };\n\n            pin.original_path_index.reset_path();\n            let fut2 = second.call(err);\n            self.set(OrElseFuture {\n                state: State::Second(fut2),\n                ..*self\n            });\n        }\n    }\n}\n","sourceCodeStart":78,"sourceCodeEnd":108,"githubUrl":"https://github.com/seanmonstar/warp/blob/ff34d7213ed55ec342304aa7ff6ac4b351da9e66/src/filter/or_else.rs#L78-L108","documentation":"Internal invariant panic in the `OrElse` filter future. When the recovery future (`second.call(err)`) resolves, state becomes `State::Done` and the result is returned; polling afterwards is a `Future` contract violation with no recoverable value, so the library panics.","triggerScenarios":"Polling the `OrElse` combinator future again after it returned `Poll::Ready(ex2)` from the recovery path — e.g. a poll loop that continues after `Ready` or an executor that re-dispatches completed tasks.","commonSituations":"Hand-written executors, misuse inside combinators like `join_all` with a future reused across iterations, wrappers lacking `FusedFuture` semantics, double wake handling.","solutions":["Use `.await` or a standard runtime (`tokio::spawn`) to drive the future.","Wrap in `fuses::future::Fuse` and guard manual polls with `is_terminated()`.","Ensure the poll loop breaks immediately on `Poll::Ready`.","Ensure only one task/context polls a given future instance."],"exampleFix":"// before\nlet r1 = Pin::new(&mut fut).poll(cx)?;\nlet r2 = Pin::new(&mut fut).poll(cx)?; // panic: polled after complete\n\n// after\nlet out = filter.or_else(recover).await; // runtime drives once to completion","handlingStrategy":"try-catch","validationCode":"use futures::future::FusedFuture;\nassert!(!fut.is_terminated(), \"OrElse future already completed\");","typeGuard":"fn alive<F: FusedFuture>(fut: &F) -> bool { !fut.is_terminated() }","tryCatchPattern":"// panics aren't catchable; prevent structurally:\nlet out = filter.or_else(recover).await; // runtime handles single-poll contract","preventionTips":["Prefer async/await over manual poll loops.","Add termination checks before every manual poll.","Audit custom executors for re-poll-after-Ready bugs.","Wrap futures in Fuse when stored in collections."],"tags":["panic","future","internal-invariant","rust","poll"],"backgroundTag":"invalid-state-transition","analyzedSha":"ff34d7213ed55ec342304aa7ff6ac4b351da9e66","analyzedAt":"2026-09-09T16:57:46.316Z","contentChangedAt":"2026-09-09T16:57:46.316Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}