{"record":{"id":"7de2d419d180ad3b","repo":"linera-io/linera-protocol","slug":"cache-size-must-be-greater-than-zero","errorCode":null,"errorMessage":"Cache size must be greater than zero","messagePattern":"Cache size must be greater than zero","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"linera-cache/src/unique_value_cache.rs","lineNumber":27,"sourceCode":"\n/// A bounded cache for values that are inserted and then taken out (moved), not cloned.\n///\n/// Uses `Mutex<LruCache>` internally. Suitable for caching values that don't implement\n/// `Clone`, where the access pattern is insert → remove rather than insert → get.\npub struct UniqueValueCache<K, V>\nwhere\n    K: Hash + Eq,\n{\n    cache: Mutex<LruCache<K, V>>,\n}\n\nimpl<K, V> UniqueValueCache<K, V>\nwhere\n    K: Hash + Eq + Copy,\n{\n    /// Creates a new `UniqueValueCache` with the given capacity.\n    pub fn new(size: usize) -> Self {\n        let size = NonZeroUsize::try_from(size).expect(\"Cache size must be greater than zero\");\n        UniqueValueCache {\n            cache: Mutex::new(LruCache::new(size)),\n        }\n    }\n\n    /// Inserts a value into the cache if the key is not already present.\n    ///\n    /// Returns `true` if the value was newly inserted.\n    pub fn insert(&self, key: &K, value: V) -> bool {\n        let mut cache = self.cache.lock().unwrap();\n        if cache.contains(key) {\n            cache.promote(key);\n            false\n        } else {\n            cache.push(*key, value);\n            true\n        }\n    }","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-cache/src/unique_value_cache.rs#L9-L45","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set a positive capacity in the config/CLI value feeding the constructor","Clamp defensively at the boundary: UniqueValueCache::new(size.max(1))","Type the config field as NonZeroUsize so deserialization rejects 0 before runtime","If a zero-size cache means 'disabled' in your app, branch on it and skip cache construction instead of calling new(0)"],"exampleFix":"// before\nlet cache = UniqueValueCache::new(config.max_cached_blocks); // config absent -> 0 -> panic\n\n// after — make zero unrepresentable in config\n#[derive(Deserialize)]\nstruct Config { max_cached_blocks: NonZeroUsize } // 0 rejected at load time\nlet cache = UniqueValueCache::new(config.max_cached_blocks.get());\n// or clamp when the source type cannot change:\nlet cache = UniqueValueCache::new(raw_size.max(1));","handlingStrategy":"validation","validationCode":"// Validate capacity before construction:\nlet size = config.cache_size;\nassert!(size > 0, \"cache size must be positive, got {size}\");\nlet cache = UniqueValueCache::new(size);","typeGuard":"// Rust 'type guard' via NonZeroUsize: zero becomes unrepresentable.\nfn cache_size(cfg: &Config) -> std::num::NonZeroUsize {\n    cfg.cache_size_nonzero // field typed NonZeroUsize; deserialize fails on 0\n}","tryCatchPattern":"// Constructing inside a guarded initializer turns a config typo into a clear\n// startup error instead of a mid-request panic:\nlet cache = std::panic::catch_unwind(|| UniqueValueCache::new(raw))\n    .map_err(|_| format!(\"invalid cache size: {raw}\"))?;","preventionTips":["Type capacity fields as NonZeroUsize in config structs","Clamp operator-supplied sizes with .max(1) at the load boundary","Add a config-lint test that fails on zero-size caches in CI"],"tags":["rust","cache","lru","constructor","validation","panic"],"backgroundTag":"zero-cache-capacity","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}