quickwit-oss/quickwit · error
`poll_ready` should be called before `call`
Error message
`poll_ready` should be called before `call`
What it means
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.
Source
Thrown at quickwit/quickwit-common/src/tower/load_shed.rs:73
type Error = S::Error;
type Future = LoadShedFuture<S::Future>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
if self.permit_opt.is_none() {
if let Ok(permit) = self.permits.clone().try_acquire_owned() {
self.permit_opt = Some(permit);
} else {
return Poll::Ready(Err(S::Error::make_load_shed_error()));
}
}
self.inner.poll_ready(cx)
}
fn call(&mut self, request: R) -> Self::Future {
let permit = self
.permit_opt
.take()
.expect("`poll_ready` should be called before `call`");
LoadShedFuture {
inner: self.inner.call(request),
permit,
}
}
}
#[pin_project]
#[derive(Debug)]
pub struct LoadShedFuture<F> {
#[pin]
inner: F,
permit: OwnedSemaphorePermit,
}
impl<F, T, E> Future for LoadShedFuture<F>
where F: Future<Output = Result<T, E>>View on GitHub (pinned to a39730c5cd)
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.
Example fix
// before: protocol violation let fut = load_shed_service.call(req); // panics // after use tower::ServiceExt; let fut = load_shed_service.ready().await?.call(req);
Defensive patterns
Strategy: type-guard
Validate before calling
use tower::{Service, ServiceExt};
// Correct usage: readiness first
// let res = load_shed.ready().await?.call(req).await?; Type guard
fn service_ready<S, R>(svc: &mut S, cx: &mut std::task::Context<'_>) -> bool
where S: Service<R>, S::Error: std::fmt::Debug {
matches!(svc.poll_ready(cx), std::task::Poll::Ready(Ok(())))
} Try / catch
// This panic is a caller-protocol bug; in tests, assert the protocol instead: // poll_ready must succeed (or return the load-shed error) before each call assert!(matches!(svc.poll_ready(cx), std::task::Poll::Ready(Ok(())))); let fut = svc.call(req);
Prevention
- 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.
When it happens
Trigger: 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`.
Common situations: 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.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- request should be set
- lock should not be poisoned
- node not found in pending
- OTP logs or traces do not support VRL transforms
- `doc_batch` should not be empty
AI-assisted analysis of quickwit-oss/quickwit@a39730c5cd (2026-09-08).
Data as JSON: /api/errors/8fb8a291b49a1794.
Report an issue: GitHub.