{"record":{"id":"5c04efda4bfd018d","repo":"vectordotdev/vector","slug":"poll-ready-must-be-called-first","errorCode":null,"errorMessage":"poll_ready must be called first","messagePattern":"poll_ready must be called first","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/sinks/util/service/net/mod.rs","lineNumber":333,"sourceCode":"                        Ok(maybe_socket) => match maybe_socket {\n                            Some(socket) => NetworkServiceState::Connected(socket),\n                            None => NetworkServiceState::Disconnected,\n                        },\n                        Err(_) => return Poll::Ready(Err(NetError::ServiceSocketChannelClosed)),\n                    }\n                }\n            };\n        }\n        Poll::Ready(Ok(()))\n    }\n\n    fn call(&mut self, buf: Vec<u8>) -> Self::Future {\n        let (tx, rx) = oneshot::channel();\n\n        let mut socket = match std::mem::replace(&mut self.state, NetworkServiceState::Sending(rx))\n        {\n            NetworkServiceState::Connected(socket) => socket,\n            _ => panic!(\"poll_ready must be called first\"),\n        };\n\n        Box::pin(async move {\n            match socket.send(&buf).await.context(net_error::FailedToSend) {\n                Ok(sent) => {\n                    // Emit an error if we weren't able to send the entire buffer.\n                    if sent != buf.len() {\n                        socket.on_partial_send(buf.len(), sent);\n                    }\n\n                    // Send the socket back to the service, since theoretically it's still valid to\n                    // reuse given that we may have simply overrun the OS socket buffers, etc.\n                    tx.send(Some(socket)).ok();\n\n                    Ok(sent)\n                }\n                Err(e) => {\n                    // We need to signal back to the service that it needs to create a fresh socket","sourceCodeStart":315,"sourceCodeEnd":351,"githubUrl":"https://github.com/vectordotdev/vector/blob/3708c39b12a93212ed8b8d7510b4cc7769cb5864/src/sinks/util/service/net/mod.rs#L315-L351","documentation":"NetworkService implements tower::Service for sink connections: poll_ready drives connect/reconnect and leaves the service in NetworkServiceState::Connected; call() then takes the connected socket out of that state. call() invoked while the state is Connecting or otherwise not Connected panics with 'poll_ready must be called first' - it enforces the tower Service contract that call may only follow a poll_ready that returned Poll::Ready(Ok(())).","triggerScenarios":"Calling service.call(buf) on NetworkService (or a wrapper around it) without a prior poll_ready that returned Ready; calling again while the Connecting future from a Pending poll_ready is still outstanding; custom middleware that forwards call() without checking inner readiness.","commonSituations":"Writing custom tower middleware or a hand-rolled sink driver around Vector's internal sink service; porting code from older tower 0.3 ready_and() patterns; polling loops that skip the readiness step under load.","solutions":["Use tower::ServiceExt::ready() before every call: let mut svc = svc.ready().await?; then svc.call(buf)","Audit custom Service middleware to confirm it propagates poll_ready and never calls the inner service while Pending","Ensure a future returned from a Pending poll_ready is polled to completion (the Connecting state must finish) before call()","Add a unit test that drives poll_ready/call in order using tower's test utilities"],"exampleFix":"// before\nlet fut = service.call(buf); // panics: state is Connecting, poll_ready never returned Ready\n\n// after\nuse tower::ServiceExt;\nlet mut service = service.ready().await?; // poll_ready -> Ready(Ok(()))\nlet fut = service.call(buf);","handlingStrategy":"validation","validationCode":"// Enforce the readiness precondition before every call:\nmatch service.poll_ready(cx) {\n    Poll::Ready(Ok(())) => { let fut = service.call(buf); /* ok */ }\n    Poll::Ready(Err(e)) => { /* surface connection failure */ }\n    Poll::Pending => { /* park on the waker; calling now would panic */ }\n}","typeGuard":null,"tryCatchPattern":"// Last resort - the panic is synchronous inside call():\nlet result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| service.call(buf)));\n// Treat Err(payload) as a programming error and fix the driver protocol instead of shipping this","preventionTips":["Pair every call() with a preceding Ready poll_ready in the same task","Prefer tower::ServiceExt::ready() over hand-rolled poll loops","Cover custom middleware with tests that exercise both Pending and Ready paths"],"tags":["rust","vector","tower","service","sink","network","panic","api-misuse"],"backgroundTag":"tower-service-not-ready","analyzedSha":"3708c39b12a93212ed8b8d7510b4cc7769cb5864","analyzedAt":"2026-08-20T07:02:18.786Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}