vectordotdev/vector · error

Indexer acknowledgements channel must allow at least one pen

Error message

Indexer acknowledgements channel must allow at least one pending ack

What it means

The splunk_hec acknowledgement tracker caps pending ack ids per channel; when len() exceeds the cap it removes the smallest (oldest) id. The removal is guarded by min() returning None => unreachable, because the config type (NonZeroU64, default 1_000_000) guarantees max_pending_acks_per_channel >= 1 and the set is non-empty whenever len() > cap - the branch cannot fire unless that guarantee is broken.

Source

Thrown at src/sources/splunk_hec/acknowledgements.rs:219

    ack_event_finalizer: UnorderedFinalizer<u64>,
}

impl Channel {
    fn new(max_pending_acks_per_channel: u64, shutdown: ShutdownSignal) -> Self {
        let ack_ids_status = Arc::new(Mutex::new(RoaringTreemap::new()));
        let finalizer_ack_ids_status = Arc::clone(&ack_ids_status);
        let (ack_event_finalizer, mut ack_stream) = UnorderedFinalizer::new(Some(shutdown));
        crate::spawn_in_current_span(async move {
            while let Some((status, ack_id)) = ack_stream.next().await {
                if status == BatchStatus::Delivered {
                    let mut ack_ids_status = finalizer_ack_ids_status.lock().unwrap();
                    ack_ids_status.insert(ack_id);
                    if ack_ids_status.len() > max_pending_acks_per_channel {
                        match ack_ids_status.min() {
                            Some(min) => ack_ids_status.remove(min),
                            // max pending acks per channel is guaranteed to be >= 1,
                            // thus there must be at least one ack id available to remove
                            None => unreachable!(
                                "Indexer acknowledgements channel must allow at least one pending ack"
                            ),
                        };
                    }
                }
            }
        });

        Self {
            last_used_timestamp: RwLock::new(Instant::now()),
            currently_available_ack_id: AtomicU64::new(0),
            ack_ids_status,
            ack_event_finalizer,
        }
    }

    fn get_ack_id(&self, batch_rx: BatchStatusReceiver) -> u64 {
        {

View on GitHub (pinned to 3708c39b12)

Solutions

  1. If embedding, construct via AcknowledgementsConfig (NonZeroU64) or pass a value >= 1 to Channel::new
  2. Audit any code computing the cap arithmetically before it reaches the channel
  3. In stock Vector, report it - the NonZeroU64 invariant makes this a bug by definition
  4. Capture RUST_BACKTRACE=1 output when it fires to identify which construction path passed 0

Example fix

// before
let channel = Channel::new(computed_cap, shutdown); // computed_cap == 0 -> invariant broken

// after
let cap = std::num::NonZeroU64::new(computed_cap.max(1)).unwrap();
let config = AcknowledgementsConfig { max_pending_acks_per_channel: cap, ..Default::default() };
Defensive patterns

Strategy: validation

Validate before calling

// Before enabling acknowledgements, assert the invariant yourself:
assert!(u64::from(config.max_pending_acks_per_channel) >= 1); // NonZeroU64 makes this static
// and never call Channel::new directly with a computed value below 1

Prevention

When it happens

Trigger: Only if max_pending_acks_per_channel is 0 or the set/cap invariant breaks: the NonZeroU64 config type prevents 0 through YAML, so in practice this requires code constructing Channel::new with a computed 0 capacity or corrupting ack_ids_status (custom builds, refactors bypassing AcknowledgementsConfig).

Common situations: Custom embeddings building the acknowledgements Channel directly with an arithmetically computed capacity (integer division/underflow to 0); modifications to the ack-id set logic; unreachable through stock configuration.

Related errors


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