aaif-goose/goose · error

Failed to acquire lock

Error message

Failed to acquire lock

What it means

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.

Source

Thrown at crates/goose-download-manager/src/lib.rs:130

    pub fn list_progress(&self) -> Vec<DownloadProgress> {
        self.downloads
            .lock()
            .map(|downloads| downloads.values().cloned().collect())
            .unwrap_or_default()
    }

    pub fn set_progress(&self, progress: DownloadProgress) {
        if let Ok(mut downloads) = self.downloads.lock() {
            downloads.insert(progress.model_id.clone(), progress);
        }
    }

    pub fn reserve_download(&self, progress: DownloadProgress) -> Result<bool> {
        let mut downloads = self
            .downloads
            .lock()
            .map_err(|_| anyhow::anyhow!("Failed to acquire lock"))?;

        if let Some(existing) = downloads.get(&progress.model_id) {
            if existing.status == DownloadStatus::Downloading
                || (existing.status == DownloadStatus::Cancelled && !existing.task_exited)
            {
                return Ok(false);
            }
        }

        downloads.insert(progress.model_id.clone(), progress);
        Ok(true)
    }

    pub fn update_progress(&self, model_id: &str, update: impl FnOnce(&mut DownloadProgress)) {
        if let Ok(mut downloads) = self.downloads.lock() {
            if let Some(progress) = downloads.get_mut(model_id) {
                update(progress);
            }

View on GitHub (pinned to 3810898a74)

Solutions

  1. Update goose — a panic under this lock is a defect worth reporting with logs
  2. Recreate the DownloadManager (restart the process) to obtain a fresh, unpoisoned lock
  3. Capture the first panic's log to find the poisoning site
Defensive patterns

Strategy: fallback

Try / catch

let ok = match manager.reserve_download(progress) {
    Ok(reserved) => reserved,
    Err(e) if e.to_string() == "Failed to acquire lock" => {
        // poisoned lock: fall back to a fresh manager
        let fresh = DownloadManager::new();
        fresh.reserve_download(progress)?
    }
    Err(e) => return Err(e),
};

Prevention

When it happens

Trigger: Any thread panicking while holding self.downloads (for example a download task unwinding inside a lock scope); every subsequent reserve_download call then fails.

Common situations: Rare internal failure following a panic in download bookkeeping; not reachable through ordinary API misuse.

Related errors


AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16). Data as JSON: /api/errors/ea5bb6de096e251c. Report an issue: GitHub.