{"record":{"id":"ea5bb6de096e251c","repo":"aaif-goose/goose","slug":"failed-to-acquire-lock","errorCode":null,"errorMessage":"Failed to acquire lock","messagePattern":"Failed to acquire lock","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose-download-manager/src/lib.rs","lineNumber":130,"sourceCode":"\n    pub fn list_progress(&self) -> Vec<DownloadProgress> {\n        self.downloads\n            .lock()\n            .map(|downloads| downloads.values().cloned().collect())\n            .unwrap_or_default()\n    }\n\n    pub fn set_progress(&self, progress: DownloadProgress) {\n        if let Ok(mut downloads) = self.downloads.lock() {\n            downloads.insert(progress.model_id.clone(), progress);\n        }\n    }\n\n    pub fn reserve_download(&self, progress: DownloadProgress) -> Result<bool> {\n        let mut downloads = self\n            .downloads\n            .lock()\n            .map_err(|_| anyhow::anyhow!(\"Failed to acquire lock\"))?;\n\n        if let Some(existing) = downloads.get(&progress.model_id) {\n            if existing.status == DownloadStatus::Downloading\n                || (existing.status == DownloadStatus::Cancelled && !existing.task_exited)\n            {\n                return Ok(false);\n            }\n        }\n\n        downloads.insert(progress.model_id.clone(), progress);\n        Ok(true)\n    }\n\n    pub fn update_progress(&self, model_id: &str, update: impl FnOnce(&mut DownloadProgress)) {\n        if let Ok(mut downloads) = self.downloads.lock() {\n            if let Some(progress) = downloads.get_mut(model_id) {\n                update(progress);\n            }","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose-download-manager/src/lib.rs#L112-L148","documentation":"The download manager guards its shared HashMap with a std::sync::Mutex; reserve_download maps a poisoned lock to 'Failed to acquire lock'. Only poisoning — another thread panicking while holding the lock — causes this; a contended but healthy lock simply blocks. Note sibling methods (set_progress, update_progress) silently ignore the same condition.","triggerScenarios":"Any thread panicking while holding self.downloads (for example a download task unwinding inside a lock scope); every subsequent reserve_download call then fails.","commonSituations":"Rare internal failure following a panic in download bookkeeping; not reachable through ordinary API misuse.","solutions":["Update goose — a panic under this lock is a defect worth reporting with logs","Recreate the DownloadManager (restart the process) to obtain a fresh, unpoisoned lock","Capture the first panic's log to find the poisoning site"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"let ok = match manager.reserve_download(progress) {\n    Ok(reserved) => reserved,\n    Err(e) if e.to_string() == \"Failed to acquire lock\" => {\n        // poisoned lock: fall back to a fresh manager\n        let fresh = DownloadManager::new();\n        fresh.reserve_download(progress)?\n    }\n    Err(e) => return Err(e),\n};","preventionTips":["Report panics originating in download bookkeeping — they poison this lock for everyone","Design call sites to recreate the manager rather than retry the same poisoned one","Monitor for the first panic; the lock error is only a symptom"],"tags":["concurrency","mutex","download","internal"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}