{"record":{"id":"3d3d9f85ca67934f","repo":"seanmonstar/warp","slug":"polled-after-complete-3d3d9f","errorCode":null,"errorMessage":"polled after complete","messagePattern":"polled after complete","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/filter/recover.rs","lineNumber":106,"sourceCode":"        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((Either::A(ex),))),\n                    Err(err) => (err, second),\n                },\n                StateProj::Second(second) => {\n                    let ex2 = match ready!(second.try_poll(cx)) {\n                        Ok(ex2) => Ok((Either::B((ex2,)),)),\n                        Err(e) => Err(e),\n                    };\n                    self.set(RecoverFuture {\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(RecoverFuture {\n                state: State::Second(fut2),\n                ..*self\n            });\n        }\n    }\n}\n","sourceCodeStart":88,"sourceCodeEnd":118,"githubUrl":"https://github.com/seanmonstar/warp/blob/ff34d7213ed55ec342304aa7ff6ac4b351da9e66/src/filter/recover.rs#L88-L118","documentation":"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.","triggerScenarios":"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.","commonSituations":"Custom scheduler bugs, wrappers that don't implement `FusedFuture`, `select!`-style code written without termination checks, accidental shared ownership of the future.","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."],"exampleFix":"// before\nmatch fut.as_mut().poll(cx) { Poll::Ready(v) => done(v), _ => {} }\n// wake handler polls again later -> panic\n\n// after\nlet mut fut = filter.recover(h).fuse();\nif !fut.is_terminated() {\n    match fut.poll_unpin(cx) { /* ... */ }\n}","handlingStrategy":"try-catch","validationCode":"use futures::future::FusedFuture;\nif fut.is_terminated() { return; } // never poll a completed Recover future","typeGuard":"fn pollable(fut: &(impl FusedFuture + ?Sized)) -> bool { !fut.is_terminated() }","tryCatchPattern":"let mut fut = filter.recover(h).fuse();\nif !fut.is_terminated() {\n    match fut.poll_unpin(cx) { /* handle Ready/Pending once */ }\n}","preventionTips":["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."],"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"}