vectordotdev/vector · critical

join error or bad poll

Error message

join error or bad poll

What it means

This is Vector's fanout-to-transform task in the topology builder. It pulls input batches, spawns transform futures into in_flight (bounded by the worker-thread-derived TRANSFORM_CONCURRENCY_LIMIT), and forwards outputs. The branch expects Some(Ok(outputs)); anything else - a joined future that panicked (JoinError) or a premature None - trips unreachable!('join error or bad poll'). This panic is the symptom; the root cause is an earlier panic inside a transform task.

Source

Thrown at src/topology/builder.rs:1375

        let mut input_rx =
            super::ready_arrays::ReadyArrays::with_capacity(input_rx, ready_array_capacity());

        let mut in_flight = FuturesOrdered::new();
        let mut shutting_down = false;

        self.timer_tx.try_send_start_wait();
        loop {
            tokio::select! {
                biased;

                result = in_flight.next(), if !in_flight.is_empty() => {
                    match result {
                        Some(Ok(mut outputs_buf)) => {
                            self.send_outputs(&mut outputs_buf).await
                                .map_err(TaskError::wrapped)?;
                        }
                        _ => unreachable!("join error or bad poll"),
                    }
                }

                input_arrays = input_rx.next(), if in_flight.len() < *TRANSFORM_CONCURRENCY_LIMIT && !shutting_down => {
                    match input_arrays {
                        Some(input_arrays) => {
                            let mut len = 0;
                            for events in &input_arrays {
                                self.on_events_received(events);
                                len += events.len();
                            }

                            let mut t = self.transform.clone();
                            let mut outputs_buf = self.outputs.new_buf_with_capacity(len);
                            // Hook CPU-time accounting onto the spawned task at
                            // the `Future::poll` boundary.
                            // This is a separate task from the current one, so there is no double-counting.
                            let task = spawn_timed(

View on GitHub (pinned to 3708c39b12)

Solutions

  1. Scroll up in the logs: with RUST_BACKTRACE=1 the originating panic from the transform task precedes this one and names the real culprit
  2. Identify the transform via the request span and reproduce the triggering event shape in isolation
  3. Fix or guard the offending transform (filter/re-route the events that trigger it)
  4. Upgrade Vector and retest - fanout/concurrency handling improves across releases
Defensive patterns

Strategy: try-catch

Try / catch

// The transform task's panic kills the topology task; supervise the process and alert:
//   systemd: Restart=on-failure, Environment=RUST_BACKTRACE=1
// Embedding: treat JoinError::is_panic() as a crash-loop with backoff and alerting,
// and always pull the FIRST panic in the logs (the transform's own) as root cause.

Prevention

When it happens

Trigger: A spawned transform future panicking while processing events - its JoinError surfaces here - or the futures collection misbehaving (None while non-empty). Real-world event shapes reaching a transform's own panic path (unsupported event types, malformed inputs) are the usual source.

Common situations: A transform hitting one of its own unreachable/panic arms under real traffic (e.g. an unsupported event type or non-standard log format reaching it); high concurrency stressing task spawning; Vector bugs in fanout accounting.

Related errors


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