{"record":{"id":"4ce02454e3a2bb24","repo":"getzola/zola","slug":"result-cache-lock-4ce024","errorCode":null,"errorMessage":"result cache lock","messagePattern":"result cache lock","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"components/templates/src/functions/images.rs","lineNumber":130,"sourceCode":"        let allow_missing: bool = kwargs.get(\"allow_missing\")?.unwrap_or(false);\n\n        let (src_path, unified_path) =\n            match search_for_file(&self.base_path, &path, &self.theme, &self.output_path)\n                .map_err(|e| Error::message(format!(\"`get_image_metadata`: {}\", e)))?\n            {\n                Some((f, p)) => (f, p),\n                None => {\n                    if allow_missing {\n                        return Ok(Value::none());\n                    }\n                    return Err(Error::message(format!(\n                        \"`get_image_metadata`: Cannot find path: {}\",\n                        path\n                    )));\n                }\n            };\n\n        let mut cache = self.result_cache.lock().expect(\"result cache lock\");\n        if let Some(cached_result) = cache.get(&unified_path) {\n            return Ok(cached_result.clone());\n        }\n\n        let response = imageproc::read_image_metadata(src_path)\n            .map_err(|e| Error::message(format!(\"`get_image_metadata`: {}\", e)))?;\n        let out = Value::from_serializable(&response);\n        cache.insert(unified_path, out.clone());\n\n        Ok(out)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::{GetImageMetadata, ResizeImage};\n\n    use std::path::{Path, PathBuf};","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/getzola/zola/blob/61d30828217957120309db657950224edf9707e2/components/templates/src/functions/images.rs#L112-L148","documentation":"The get_image_metadata template function uses the same shared result_cache Mutex to memoize image metadata lookups. It panics with 'result cache lock' when the mutex is poisoned by a panic on another thread while the lock was held.","triggerScenarios":"A template calls get_image_metadata after result_cache was poisoned — e.g. by a panic in a concurrent get_url/get_image_metadata call in another render thread.","commonSituations":"Parallel page rendering in zola build/server where one image lookup panicked and subsequent renders hit the poisoned mutex.","solutions":["Locate and fix the earlier panic that poisoned the cache (original backtrace precedes this one).","Use unwrap_or_else(|p| p.into_inner()) on the lock to tolerate poisoning.","Restart the build after any image-metadata panic.","Make read_image_metadata paths return errors instead of panicking under the lock."],"exampleFix":"// before\nlet mut cache = self.result_cache.lock().expect(\"result cache lock\");\n// after\nlet mut cache = self.result_cache.lock().unwrap_or_else(|p| p.into_inner());","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let mut cache = match self.result_cache.lock() {\n    Ok(g) => g,\n    Err(poisoned) => poisoned.into_inner(),\n};","preventionTips":["Ensure read_image_metadata paths don't panic; return Result.","Catch and report render-thread panics before they poison shared caches.","Restart builds after an image-metadata failure.","Recover with into_inner() since cached metadata is recomputable."],"tags":["mutex","poisoned-lock","templates","images"],"backgroundTag":"mutex-poisoned","analyzedSha":"61d30828217957120309db657950224edf9707e2","analyzedAt":"2026-09-03T14:39:09.727Z","contentChangedAt":"2026-09-03T14:39:09.727Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}