linera-io/linera-protocol · error

capacity must be > 0

Error message

capacity must be > 0

What it means

QueryResponseCache::new wraps its per-chain LRU capacity in NonZeroUsize::new(capacity_per_chain).expect("capacity must be > 0"). A zero capacity is rejected because the underlying LRU (and the cache design) cannot function with zero slots. The value comes from the `linera service` --query-cache-size CLI argument (node_service.rs:1379), so passing 0 panics during service construction at startup.

Source

Thrown at linera-service/src/node_service.rs:1156

/// each insert carries the chain's `next_block_height` at query time.
/// If a newer block has since been processed, the insert is silently dropped.
struct QueryResponseCache {
    chains: papaya::HashMap<ChainId, StdMutex<PerChainCache>>,
    /// Chains for which we have registered a notification subscription.
    subscribed: papaya::HashSet<ChainId>,
    /// Sender half of the notification channel, used to subscribe new chains lazily.
    notification_sender: StdMutex<Option<tokio::sync::mpsc::UnboundedSender<Notification>>>,
    capacity_per_chain: std::num::NonZeroUsize,
}

impl QueryResponseCache {
    fn new(capacity_per_chain: usize) -> Self {
        Self {
            chains: papaya::HashMap::new(),
            subscribed: papaya::HashSet::new(),
            notification_sender: StdMutex::new(None),
            capacity_per_chain: std::num::NonZeroUsize::new(capacity_per_chain)
                .expect("capacity must be > 0"),
        }
    }

    /// Stores the notification sender (called once during startup).
    fn set_notification_sender(&self, sender: tokio::sync::mpsc::UnboundedSender<Notification>) {
        *self
            .notification_sender
            .lock()
            .expect("sender mutex poisoned") = Some(sender);
    }

    /// Returns the notification sender, if set.
    fn notification_sender(&self) -> Option<tokio::sync::mpsc::UnboundedSender<Notification>> {
        self.notification_sender
            .lock()
            .expect("sender mutex poisoned")
            .clone()
    }

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Omit --query-cache-size entirely if you want no cache — the None default skips cache creation
  2. Otherwise pass a positive value, e.g. --query-cache-size 100 (the values used in tests)
  3. Fix wrapper scripts that coerce 'disabled'/'null' to 0 before invoking the CLI
  4. If size comes from a computed value, clamp it: max(1, computed)

Example fix

# before
linera service --query-cache-size 0   # panics at startup

# after (disable caching)
linera service
# after (keep cache)
linera service --query-cache-size 100
Defensive patterns

Strategy: validation

Validate before calling

# Never pass 0; omit the flag to disable the cache.
CACHE_ARGS=()
if [ -n "$QUERY_CACHE_SIZE" ] && [ "$QUERY_CACHE_SIZE" != '0' ]; then
  CACHE_ARGS=(--query-cache-size "$QUERY_CACHE_SIZE")
fi
linera service "${CACHE_ARGS[@]}"

Prevention

When it happens

Trigger: Starting `linera service --query-cache-size 0` (or a config/script that sets the cache size to zero to 'disable' caching): NonZeroUsize::new(0) returns None and the expect aborts before the service starts listening.

Common situations: Operators trying to disable the query cache by setting its size to 0 (the flag is Option<usize>; disabling is done by omitting it); YAML/JSON-to-CLI translation scripts defaulting unset numeric values to 0; tuning scripts that compute size from a metric which can evaluate to 0.

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/eaa8fd43946c4bf9. Report an issue: GitHub.