vectordotdev/vector · error
poll_ready must be called first
Error message
poll_ready must be called first
What it means
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(())).
Source
Thrown at src/sinks/util/service/net/mod.rs:333
Ok(maybe_socket) => match maybe_socket {
Some(socket) => NetworkServiceState::Connected(socket),
None => NetworkServiceState::Disconnected,
},
Err(_) => return Poll::Ready(Err(NetError::ServiceSocketChannelClosed)),
}
}
};
}
Poll::Ready(Ok(()))
}
fn call(&mut self, buf: Vec<u8>) -> Self::Future {
let (tx, rx) = oneshot::channel();
let mut socket = match std::mem::replace(&mut self.state, NetworkServiceState::Sending(rx))
{
NetworkServiceState::Connected(socket) => socket,
_ => panic!("poll_ready must be called first"),
};
Box::pin(async move {
match socket.send(&buf).await.context(net_error::FailedToSend) {
Ok(sent) => {
// Emit an error if we weren't able to send the entire buffer.
if sent != buf.len() {
socket.on_partial_send(buf.len(), sent);
}
// Send the socket back to the service, since theoretically it's still valid to
// reuse given that we may have simply overrun the OS socket buffers, etc.
tx.send(Some(socket)).ok();
Ok(sent)
}
Err(e) => {
// We need to signal back to the service that it needs to create a fresh socketView on GitHub (pinned to 3708c39b12)
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
Example fix
// before let fut = service.call(buf); // panics: state is Connecting, poll_ready never returned Ready // after use tower::ServiceExt; let mut service = service.ready().await?; // poll_ready -> Ready(Ok(())) let fut = service.call(buf);
Defensive patterns
Strategy: validation
Validate before calling
// Enforce the readiness precondition before every call:
match service.poll_ready(cx) {
Poll::Ready(Ok(())) => { let fut = service.call(buf); /* ok */ }
Poll::Ready(Err(e)) => { /* surface connection failure */ }
Poll::Pending => { /* park on the waker; calling now would panic */ }
} Try / catch
// Last resort - the panic is synchronous inside call(): let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| service.call(buf))); // Treat Err(payload) as a programming error and fix the driver protocol instead of shipping this
Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ServiceSink service sender dropped.
- path and query should never fail to parse
- Failed type coercion, {self:?} is not a Sink
- Failed type coercion, {self:?} is not a Stream
- Maximum requests in-flight; poll_ready must be called first
AI-assisted analysis of vectordotdev/vector@3708c39b12 (2026-08-20).
Data as JSON: /api/errors/5c04efda4bfd018d.
Report an issue: GitHub.