{"record":{"id":"8fb8a291b49a1794","repo":"quickwit-oss/quickwit","slug":"poll-ready-should-be-called-before-call","errorCode":null,"errorMessage":"`poll_ready` should be called before `call`","messagePattern":"`poll_ready` should be called before `call`","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"quickwit/quickwit-common/src/tower/load_shed.rs","lineNumber":73,"sourceCode":"    type Error = S::Error;\n    type Future = LoadShedFuture<S::Future>;\n\n    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n        if self.permit_opt.is_none() {\n            if let Ok(permit) = self.permits.clone().try_acquire_owned() {\n                self.permit_opt = Some(permit);\n            } else {\n                return Poll::Ready(Err(S::Error::make_load_shed_error()));\n            }\n        }\n        self.inner.poll_ready(cx)\n    }\n\n    fn call(&mut self, request: R) -> Self::Future {\n        let permit = self\n            .permit_opt\n            .take()\n            .expect(\"`poll_ready` should be called before `call`\");\n\n        LoadShedFuture {\n            inner: self.inner.call(request),\n            permit,\n        }\n    }\n}\n\n#[pin_project]\n#[derive(Debug)]\npub struct LoadShedFuture<F> {\n    #[pin]\n    inner: F,\n    permit: OwnedSemaphorePermit,\n}\n\nimpl<F, T, E> Future for LoadShedFuture<F>\nwhere F: Future<Output = Result<T, E>>","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/quickwit-oss/quickwit/blob/a39730c5cdcd1a4fe798403737ae293999ea21f8/quickwit/quickwit-common/src/tower/load_shed.rs#L55-L91","documentation":"This `.expect(\"`poll_ready` should be called before `call`\")` panic occurs in `LoadShed::call` (quickwit-common/src/tower/load_shed.rs). The load-shed service acquires a semaphore permit in `poll_ready` and consumes it in `call`; if `call` is invoked without a prior successful `poll_ready`, no permit exists and the service panics. This enforces the tower `Service` protocol, which mandates readiness polling before each request.","triggerScenarios":"Calling `LoadShed::call` (or `call` through a stack containing `LoadShedLayer`) without first obtaining `Poll::Ready(Ok(()))` from `poll_ready`; calling `call` twice without re-polling readiness in between; using the service directly rather than via `ServiceExt::ready`/`oneshot`.","commonSituations":"Custom servers or test harnesses that invoke `service.call(req)` directly; middleware that forgets to forward `poll_ready`; benchmarks or replay tools issuing calls in a loop without readiness checks.","solutions":["Always poll `poll_ready` to completion before each `call`, e.g. `service.ready().await?.call(request)`.","Use `tower::ServiceExt::oneshot` which handles the readiness dance automatically.","In custom layers/tunnels, make sure `poll_ready` is forwarded to the `LoadShed` layer and its result respected.","Note `poll_ready` may return a load-shed error when at capacity — handle it rather than retrying `call` without readiness."],"exampleFix":"// before: protocol violation\nlet fut = load_shed_service.call(req); // panics\n\n// after\nuse tower::ServiceExt;\nlet fut = load_shed_service.ready().await?.call(req);","handlingStrategy":"type-guard","validationCode":"use tower::{Service, ServiceExt};\n// Correct usage: readiness first\n// let res = load_shed.ready().await?.call(req).await?;","typeGuard":"fn service_ready<S, R>(svc: &mut S, cx: &mut std::task::Context<'_>) -> bool\nwhere S: Service<R>, S::Error: std::fmt::Debug {\n    matches!(svc.poll_ready(cx), std::task::Poll::Ready(Ok(())))\n}","tryCatchPattern":"// This panic is a caller-protocol bug; in tests, assert the protocol instead:\n// poll_ready must succeed (or return the load-shed error) before each call\nassert!(matches!(svc.poll_ready(cx), std::task::Poll::Ready(Ok(()))));\nlet fut = svc.call(req);","preventionTips":["Use ServiceExt::oneshot or ready().await before every call.","Do not call `call` twice without re-polling readiness.","Handle the load-shed error from poll_ready instead of forcing calls.","Ensure surrounding layers forward poll_ready faithfully."],"tags":["rust","tower","middleware","panic","backpressure"],"backgroundTag":"internal-invariant-violation","analyzedSha":"a39730c5cdcd1a4fe798403737ae293999ea21f8","analyzedAt":"2026-09-08T13:19:37.784Z","contentChangedAt":"2026-09-08T13:19:37.784Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}