linera-io/linera-protocol · error

Cache size must be greater than zero

Error message

Cache size must be greater than zero

What it means

UniqueValueCache::new converts the requested capacity to NonZeroUsize and expects success, so passing 0 panics immediately at construction. An LRU cache with zero capacity cannot evict-and-store anything, so the constructor treats zero as a programming or configuration error rather than silently doing nothing. The panic happens on the caller's thread, typically during service initialization.

Source

Thrown at linera-cache/src/unique_value_cache.rs:27

/// A bounded cache for values that are inserted and then taken out (moved), not cloned.
///
/// Uses `Mutex<LruCache>` internally. Suitable for caching values that don't implement
/// `Clone`, where the access pattern is insert → remove rather than insert → get.
pub struct UniqueValueCache<K, V>
where
    K: Hash + Eq,
{
    cache: Mutex<LruCache<K, V>>,
}

impl<K, V> UniqueValueCache<K, V>
where
    K: Hash + Eq + Copy,
{
    /// Creates a new `UniqueValueCache` with the given capacity.
    pub fn new(size: usize) -> Self {
        let size = NonZeroUsize::try_from(size).expect("Cache size must be greater than zero");
        UniqueValueCache {
            cache: Mutex::new(LruCache::new(size)),
        }
    }

    /// Inserts a value into the cache if the key is not already present.
    ///
    /// Returns `true` if the value was newly inserted.
    pub fn insert(&self, key: &K, value: V) -> bool {
        let mut cache = self.cache.lock().unwrap();
        if cache.contains(key) {
            cache.promote(key);
            false
        } else {
            cache.push(*key, value);
            true
        }
    }

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Set a positive capacity in the config/CLI value feeding the constructor
  2. Clamp defensively at the boundary: UniqueValueCache::new(size.max(1))
  3. Type the config field as NonZeroUsize so deserialization rejects 0 before runtime
  4. If a zero-size cache means 'disabled' in your app, branch on it and skip cache construction instead of calling new(0)

Example fix

// before
let cache = UniqueValueCache::new(config.max_cached_blocks); // config absent -> 0 -> panic

// after — make zero unrepresentable in config
#[derive(Deserialize)]
struct Config { max_cached_blocks: NonZeroUsize } // 0 rejected at load time
let cache = UniqueValueCache::new(config.max_cached_blocks.get());
// or clamp when the source type cannot change:
let cache = UniqueValueCache::new(raw_size.max(1));
Defensive patterns

Strategy: validation

Validate before calling

// Validate capacity before construction:
let size = config.cache_size;
assert!(size > 0, "cache size must be positive, got {size}");
let cache = UniqueValueCache::new(size);

Type guard

// Rust 'type guard' via NonZeroUsize: zero becomes unrepresentable.
fn cache_size(cfg: &Config) -> std::num::NonZeroUsize {
    cfg.cache_size_nonzero // field typed NonZeroUsize; deserialize fails on 0
}

Try / catch

// Constructing inside a guarded initializer turns a config typo into a clear
// startup error instead of a mid-request panic:
let cache = std::panic::catch_unwind(|| UniqueValueCache::new(raw))
    .map_err(|_| format!("invalid cache size: {raw}"))?;

Prevention

When it happens

Trigger: Passing a config value that defaults to 0 (missing/optional field deserialized as 0); computing capacity arithmetically to zero (max_entries * factor where factor is 0, or a - 1 on an empty collection); passing a size parsed from an empty CLI/env var string.

Common situations: Optional TOML/YAML config keys absent in production but present in dev; feature flags that scale cache size by 0 to 'disable' caching; refactors that change the unit (bytes vs entries) so an old value now evaluates to 0.

Related errors


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