{"record":{"id":"93cdc76ab3d53ca4","repo":"influxdata/influxdb","slug":"ghost-queue-is-not-empty","errorCode":null,"errorMessage":"ghost queue is NOT empty","messagePattern":"ghost queue is NOT empty","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/object_store_mem_cache/src/cache_system/s3_fifo_cache/s3_fifo.rs","lineNumber":810,"sourceCode":"/// their keys, and an [EvictionResult] indicating success or failure.\nstruct EvictionAttempt<K, V>\nwhere\n    K: ?Sized,\n{\n    entries: Vec<CacheEntry<K, V>>,\n    keys: Vec<Arc<K>>,\n}\n\ntype EvictionResult<K, V> = Result<EvictionAttempt<K, V>, EvictionAttempt<K, V>>;\n\nimpl<K, V> LockedState<K, V>\nwhere\n    K: Debug + Eq + Hash + HasSize + Send + Sync + 'static + ?Sized,\n    V: HasSize + InUse + Send + Sync + 'static,\n{\n    fn insert_ghost(&mut self, key: Arc<K>, config: &S3Config<K>, evicted_keys: &mut Vec<Arc<K>>) {\n        while self.ghost.memory_size() >= config.max_ghost_memory_size {\n            evicted_keys.push(self.ghost.pop_front().expect(\"ghost queue is NOT empty\"));\n        }\n\n        self.ghost.push_back(key);\n    }\n\n    /// Evict entries, returning an [EvictionAttempt] with the result.\n    fn evict(&mut self, entries: &Entries<K, V>, config: &S3Config<K>) -> EvictionResult<K, V> {\n        let small_queue_threshold =\n            (config.move_to_main_threshold * config.max_memory_size as f64) as usize;\n        let mut evicted_entries = Vec::with_capacity(8);\n        let mut evicted_keys = Vec::with_capacity(8);\n\n        while self.small.memory_size() + self.main.memory_size() >= config.max_memory_size {\n            if self.small.memory_size() >= small_queue_threshold {\n                match self.evict_from_small_queue(\n                    entries,\n                    config,\n                    &mut evicted_entries,","sourceCodeStart":792,"sourceCodeEnd":828,"githubUrl":"https://github.com/influxdata/influxdb/blob/d28e26e048401c53cbb98cf2d6ab0cf1e98048ca/core/object_store_mem_cache/src/cache_system/s3_fifo_cache/s3_fifo.rs#L792-L828","documentation":"LockedState::insert_ghost trims the ghost queue with `while self.ghost.memory_size() >= config.max_ghost_memory_size { pop_front().expect(\"ghost queue is NOT empty\") }`. The invariant is that a non-empty budget implies a non-empty queue, but with max_ghost_memory_size == 0 an EMPTY queue has memory_size() == 0 >= 0, so pop_front() is called on an empty OrderedSet, returns None, and the expect panics. Unlike the public store builder (which uses NonZeroUsize), the S3Config field is a plain pub usize, so a zero ghost budget is representable and immediately fatal.","triggerScenarios":"Constructing S3Config { max_ghost_memory_size: 0, .. } (or any ghost budget of 0 via direct struct literal / tests) and performing any insert that promotes an evicted key into the ghost set — insert_ghost is called on the first eviction from the small queue.","commonSituations":"Disabling the ghost queue by setting its byte limit to 0 in config (a natural but wrong way to 'turn it off' — use usize::MAX instead); copying test S3Config literals into production code paths; config plumbing that maps an unset/0 default to the raw field, bypassing the NonZeroUsize guard that MemCacheObjectStoreParams enforces.","solutions":["Set max_ghost_memory_size to a positive value (e.g. usize::MAX or a real byte budget) — 0 is invalid, not 'disabled'.","If you build the cache through MemCacheObjectStoreParams, keep s3_fifo_ghost_memory_limit as NonZeroUsize so 0 is unrepresentable.","Add a debug_assert!/validation in S3Config construction that max_ghost_memory_size > 0 (upstream fix worth filing).","Audit generated S3Config literals (tests, benches) for zero ghost budgets before reusing them."],"exampleFix":"// before\nlet config = S3Config {\n    max_ghost_memory_size: 0, // \"disable\" ghost queue -> panics in insert_ghost\n    ..\n};\n\n// after\nlet config = S3Config {\n    max_ghost_memory_size: usize::MAX, // effectively unbounded ghost set\n    ..\n};","handlingStrategy":"validation","validationCode":"// validate S3Config before constructing the cache\nfn valid_ghost_budget(cfg: &S3Config<impl HasSize>) -> bool {\n    cfg.max_ghost_memory_size > 0 // 0 makes insert_ghost pop from an empty queue\n}\n\nif !valid_ghost_budget(&config) {\n    return Err(\"max_ghost_memory_size must be > 0 (use usize::MAX for unbounded)\".into());\n}\nlet cache = S3FifoCache::new(config, &registry);","typeGuard":"// make an invalid budget unrepresentable at your API boundary\nfn ghost_budget(n: usize) -> Option<NonZeroUsize> {\n    NonZeroUsize::new(n)\n}","tryCatchPattern":null,"preventionTips":["Model memory budgets as NonZeroUsize in your config structs (like MemCacheObjectStoreParams does).","Remember 0 does NOT mean 'disabled' for the ghost set — use usize::MAX or a real byte budget.","Add config-schema tests that reject zero-valued cache budgets at load time.","Grep test/bench literals copied into production paths for max_ghost_memory_size: 0."],"tags":["rust","s3-fifo-cache","configuration","ghost-queue","empty-collection-pop","panic"],"backgroundTag":"pop-from-empty-collection","analyzedSha":"d28e26e048401c53cbb98cf2d6ab0cf1e98048ca","analyzedAt":"2026-08-16T19:53:34.623Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}