vectordotdev/vector · critical

poll_ready must be called first

Error message

poll_ready must be called first

What it means

NetService::call panics when invoked while the internal state is not NetworkServiceState::Connected. The service swaps state to Sending on call, so like any Tower service it must have poll_ready() return Ready (restoring Connected) before call(). Hitting this means the ready/call protocol was violated. It is an internal invariant violation of the framed-network service state machine.

Solutions

  1. Ensure poll_ready() is awaited until Ready(Ok(())) immediately before each call()
  2. Do not reuse the service for a second request before re-polling poll_ready()
  3. Use tower::buffer::Buffer or Vector's existing service builders so the protocol is enforced for you

Example fix

// before
net_service.call(buf).await;

// after
ready!(net_service.poll_ready(cx))?;
net_service.call(buf);
Defensive patterns

Strategy: type-guard

Validate before calling

// Check readiness before sending
ready!(service.poll_ready(cx))?; // must yield Ready(Ok(())) before call

Type guard

fn is_connected(state: &NetworkServiceState) -> bool { matches!(state, NetworkServiceState::Connected(_)) }

Prevention

When it happens

Trigger: Calling the service without a prior successful poll_ready(); calling call() twice without re-polling poll_ready(); wrapping the service in middleware that ignores readiness.

Common situations: Custom sink drivers or Tower layers composed around Vector's net service during development; regressions after refactoring batch/send drivers that bypass the readiness handshake.

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 vectordotdev/vector@bdb87aeaa4 (2026-09-16). Data as JSON: /api/errors/5c04efda4bfd018d. Report an issue: GitHub.

Appendix: 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 socket

View on GitHub (pinned to bdb87aeaa4)