vectordotdev/vector · error

Maximum requests in-flight; poll_ready must be called first

Error message

Maximum requests in-flight; poll_ready must be called first

What it means

AdaptiveConcurrencyLimit implements tower::Service with a permit state machine: poll_ready acquires a concurrency permit (via the controller semaphore) and stores it in State::Ready; call consumes that permit via mem::replace. Calling call() when no permit is stored - state Empty or Waiting - panics with 'Maximum requests in-flight; poll_ready must be called first'. This enforces the tower Service contract that call may only follow a Ready poll_ready, one call per readiness.

Source

Thrown at src/sinks/util/adaptive_concurrency/service.rs:77

            self.state = match self.state {
                State::Ready(_) => return self.inner.poll_ready(cx).map_err(Into::into),
                State::Waiting(ref mut fut) => {
                    tokio::pin!(fut);
                    let permit = ready!(fut.poll(cx));
                    State::Ready(permit)
                }
                State::Empty => State::Waiting(Box::pin(Arc::clone(&self.controller).acquire())),
            };
        }
    }

    fn call(&mut self, request: Request) -> Self::Future {
        // Make sure a permit has been acquired
        let permit = match mem::replace(&mut self.state, State::Empty) {
            // Take the permit.
            State::Ready(permit) => permit,
            // whoopsie!
            _ => panic!("Maximum requests in-flight; poll_ready must be called first"),
        };

        self.controller.start_request();

        // Call the inner service
        let future = self.inner.call(request);

        ResponseFuture::new(future, permit, Arc::clone(&self.controller))
    }
}

impl<S, L> Load for AdaptiveConcurrencyLimit<S, L> {
    type Metric = f64;

    fn load(&self) -> Self::Metric {
        self.controller.load()
    }
}

View on GitHub (pinned to 3708c39b12)

Solutions

  1. Await readiness before every call: let mut svc = svc.ready().await?; then svc.call(req)
  2. If wrapping the service, delegate poll_ready to the inner AdaptiveConcurrencyLimit and never call inner.call unless your own poll_ready returned Ready
  3. Audit for loops that cache a ready &mut service across multiple call invocations
  4. If hit inside Vector's own pipeline (no custom code), upgrade and report - it violates an internal contract

Example fix

// before
let fut = svc.call(request); // no readiness -> panic

// after
let mut svc = svc.ready().await?;
let fut = svc.call(request);
Defensive patterns

Strategy: validation

Validate before calling

// tower contract: acquire readiness before each call
use tower::ServiceExt;
let mut svc = svc.ready().await?; // poll_ready -> Ready
let response = svc.call(request).await?;

Prevention

When it happens

Trigger: Issuing svc.call(request) without a preceding successful poll_ready/ready().await - e.g. calling twice after a single readiness, driving calls in a loop without re-checking readiness, or a wrapper service that forwards call but not poll_ready. With Vector's adaptive_concurrency retries/sinks settings, an internal violation would be a Vector bug.

Common situations: Custom sinks or middlewares wrapping Vector's service stack that cache a ready service and reuse it for multiple calls; misuse of tower APIs (svc.call instead of ServiceExt::ready().await + call); batching/partitioning layers that issue multiple requests per readiness slot.

Related errors


AI-assisted analysis of vectordotdev/vector@3708c39b12 (2026-08-20). Data as JSON: /api/errors/c7b7d3a3a0085e39. Report an issue: GitHub.