{"record":{"id":"c7b7d3a3a0085e39","repo":"vectordotdev/vector","slug":"maximum-requests-in-flight-poll-ready-must-be-cal","errorCode":null,"errorMessage":"Maximum requests in-flight; poll_ready must be called first","messagePattern":"Maximum requests in-flight; poll_ready must be called first","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/sinks/util/adaptive_concurrency/service.rs","lineNumber":77,"sourceCode":"            self.state = match self.state {\n                State::Ready(_) => return self.inner.poll_ready(cx).map_err(Into::into),\n                State::Waiting(ref mut fut) => {\n                    tokio::pin!(fut);\n                    let permit = ready!(fut.poll(cx));\n                    State::Ready(permit)\n                }\n                State::Empty => State::Waiting(Box::pin(Arc::clone(&self.controller).acquire())),\n            };\n        }\n    }\n\n    fn call(&mut self, request: Request) -> Self::Future {\n        // Make sure a permit has been acquired\n        let permit = match mem::replace(&mut self.state, State::Empty) {\n            // Take the permit.\n            State::Ready(permit) => permit,\n            // whoopsie!\n            _ => panic!(\"Maximum requests in-flight; poll_ready must be called first\"),\n        };\n\n        self.controller.start_request();\n\n        // Call the inner service\n        let future = self.inner.call(request);\n\n        ResponseFuture::new(future, permit, Arc::clone(&self.controller))\n    }\n}\n\nimpl<S, L> Load for AdaptiveConcurrencyLimit<S, L> {\n    type Metric = f64;\n\n    fn load(&self) -> Self::Metric {\n        self.controller.load()\n    }\n}","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/vectordotdev/vector/blob/3708c39b12a93212ed8b8d7510b4cc7769cb5864/src/sinks/util/adaptive_concurrency/service.rs#L59-L95","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Await readiness before every call: let mut svc = svc.ready().await?; then svc.call(req)","If wrapping the service, delegate poll_ready to the inner AdaptiveConcurrencyLimit and never call inner.call unless your own poll_ready returned Ready","Audit for loops that cache a ready &mut service across multiple call invocations","If hit inside Vector's own pipeline (no custom code), upgrade and report - it violates an internal contract"],"exampleFix":"// before\nlet fut = svc.call(request); // no readiness -> panic\n\n// after\nlet mut svc = svc.ready().await?;\nlet fut = svc.call(request);","handlingStrategy":"validation","validationCode":"// tower contract: acquire readiness before each call\nuse tower::ServiceExt;\nlet mut svc = svc.ready().await?; // poll_ready -> Ready\nlet response = svc.call(request).await?;","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always await ready()/poll_ready before every call - one call per readiness acquisition","Never cache a ready service reference across multiple call invocations","When wrapping services, forward poll_ready to the inner service and preserve the contract","Use tower's combinators (ServiceBuilder, buffered, etc.) rather than hand-driving calls"],"tags":["rust","tower","vector","adaptive-concurrency","service-contract","panic"],"backgroundTag":"service-call-before-poll-ready","analyzedSha":"3708c39b12a93212ed8b8d7510b4cc7769cb5864","analyzedAt":"2026-08-20T07:02:18.786Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}