vectordotdev/vector · error

paired task no longer holding wait trigger

Error message

paired task no longer holding wait trigger

What it means

WaitHandle::wait awaits a oneshot receiver whose sender ('trigger') is held by the paired coordinated task. rx.await returning Err means the sender was dropped without firing — the paired task died or was torn down while this handle was still waiting — so the wait can never be satisfied and the code panics deliberately.

Solutions

  1. Find why the paired task died: check its JoinHandle/panic output and crash_rx messages from the topology.
  2. Ensure the task holding the WaitTrigger is spawned before calling wait() and outlives the wait.
  3. Check WaitHandle isn't awaited after the TaskCoordinator transitioned past its phase and dropped triggers.
Defensive patterns

Strategy: try-catch

Try / catch

match rx.await {
    Ok(()) => {},
    Err(_) => panic!("paired task dropped its wait trigger without firing"),
}

Prevention

When it happens

Trigger: Awaiting a WaitHandle whose paired task (topology, input HTTP server, gRPC server, telemetry driver) panicked, returned early, or never got spawned, so the WaitTrigger it held was dropped; also awaiting a trigger after TaskCoordinator shutdown was already dropped.

Common situations: A component topology crashes during validation so the 'started'/'completed' trigger never fires; a test forgets to spawn the task that holds the trigger; teardown races in run_validation where the coordinator drops triggers before wait() is polled.

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/35f7977c0a60b2ae. Report an issue: GitHub.

Appendix: source

Thrown at src/components/validation/sync.rs:145

        (trigger, handle)
    }

    /// Triggers the wait handle to wake up.
    pub fn trigger(self) {
        // We don't care if our trigger is actually received, because the receiver side may
        // intentionally not be used i.e. if the code is generic in a way where only some codepaths
        // wait to be triggered and others don't, but the trigger must always be called regardless.
        _ = self.tx.send(());
    }
}

impl WaitHandle {
    /// Waits until triggered.
    pub async fn wait(&mut self) {
        match self.rx.as_mut() {
            Some(rx) => rx
                .await
                .expect("paired task no longer holding wait trigger"),
            None => panic!("tried to await wait trigger signal but has already been received"),
        }

        // If we're here, we've successfully received the signal, so we consume the
        // receiver, as it cannot be used/polled again.
        self.rx.take();
    }
}

pub struct Configuring {
    tasks_started: WaitGroup,
    tasks_completed: WaitGroup,
    shutdown_triggers: Mutex<Vec<WaitTrigger>>,
}

pub struct Started {
    tasks_completed: Option<WaitGroup>,
    shutdown_triggers: Vec<WaitTrigger>,

View on GitHub (pinned to bdb87aeaa4)