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

  1. Always poll `poll_ready` to completion before each `call`, e.g. `service.ready().await?.call(request)`.
  2. Use `tower::ServiceExt::oneshot` which handles the readiness dance automatically.
  3. In custom layers/tunnels, make sure `poll_ready` is forwarded to the `LoadShed` layer and its result respected.
  4. 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

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


AI-assisted analysis of quickwit-oss/quickwit@a39730c5cd (2026-09-08). Data as JSON: /api/errors/8fb8a291b49a1794. Report an issue: GitHub.