{"record":{"id":"a4bd2c1556831918","repo":"seanmonstar/warp","slug":"polled-after-complete-a4bd2c","errorCode":null,"errorMessage":"polled after complete","messagePattern":"polled after complete","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/filter/and_then.rs","lineNumber":106,"sourceCode":"    >;\n\n    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {\n        loop {\n            match self.as_mut().project() {\n                StateProj::First(first, second) => {\n                    let ex1 = ready!(first.try_poll(cx))?;\n                    let fut2 = second.call(ex1);\n                    self.set(State::Second(fut2));\n                }\n                StateProj::Second(second) => {\n                    let ex2 = match ready!(second.try_poll(cx)) {\n                        Ok(item) => Ok((item,)),\n                        Err(err) => Err(From::from(err)),\n                    };\n                    self.set(State::Done);\n                    return Poll::Ready(ex2);\n                }\n                StateProj::Done => panic!(\"polled after complete\"),\n            }\n        }\n    }\n}\n","sourceCodeStart":88,"sourceCodeEnd":111,"githubUrl":"https://github.com/seanmonstar/warp/blob/ff34d7213ed55ec342304aa7ff6ac4b351da9e66/src/filter/and_then.rs#L88-L111","documentation":"Internal invariant panic in the `AndThen` filter's combinator future. Once the chained future completes and the result is returned, the state machine moves to `State::Done`; polling after that violates the `Future` contract and there is no value to return, so the library panics.","triggerScenarios":"Polling the `AndThen` combinator future after it has returned `Poll::Ready` (the `StateProj::Done` branch) — caused by a custom executor re-polling a completed future or a wrapper that ignores `is_terminated`.","commonSituations":"Hand-rolled executors, select!/timeout wrappers written incorrectly, reusing a consumed future from a cache, or combining raw futures without `Fuse`.","solutions":["Drive the filter future with `.await` or `tokio::spawn` rather than manual `poll` calls.","Wrap the future with `futures::future::Fuse` and check `is_terminated()` before polling.","Never poll a future again after it yields `Poll::Ready`; drop it instead.","Review custom executors/wrappers for double-polling logic."],"exampleFix":"// before\nloop {\n    let _ = Pin::new(&mut fut).poll(cx); // keeps polling after Ready -> panic\n}\n\n// after\nuse futures::future::FusedFuture;\nlet mut fut = filter.and_then(svc).fuse();\nwhile !fut.is_terminated() {\n    let _ = futures::poll!(fut.as_mut());\n}","handlingStrategy":"try-catch","validationCode":"use futures::future::FusedFuture;\nif fut.is_terminated() { return; } // skip poll after completion","typeGuard":"fn can_poll<F: FusedFuture>(fut: &F) -> bool { !fut.is_terminated() }","tryCatchPattern":"// panic is not recoverable; avoid by construction:\nlet mut fut = filter.and_then(svc).fuse();\nif !fut.is_terminated() { let _ = fut.poll_unpin(cx); }","preventionTips":["Use `.await` instead of manual `poll` wherever possible.","Implement `FusedFuture` semantics in custom wrappers.","Guard poll sites with `is_terminated()` checks.","Don't cache and reuse futures after they resolve."],"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"}