t8y2/dbx · error

JRE install lock table poisoned

Error message

JRE install lock table poisoned

What it means

Same family as the driver lock table: jre_operation_lock locks the jre_install_locks mutex table and panics with "JRE install lock table poisoned" if the mutex is poisoned, i.e. a previous thread panicked while holding it. Called by uninstall_agent_jre, reinstall_agent_jre_from, ensure_jre_from_registry and JRE lock tests.

Source

Thrown at crates/dbx-core/src/agent_service.rs:899

    error.contains(AGENT_DOWNLOAD_CANCELED_ERROR)
}

async fn can_fallback_to_local_agent(
    _am: &AgentManager,
    _db_type: &str,
    cancellations: &[&AgentInstallCancellation],
) -> bool {
    !cancellations.iter().any(|token| token.is_cancelled())
}

fn driver_operation_lock<'a>(am: &'a AgentManager, db_type: &str) -> OperationLockHandle<'a> {
    let mut locks = am.driver_operation_locks.lock().expect("driver operation lock table poisoned");
    let lock = locks.entry(db_type.to_string()).or_insert_with(|| Arc::new(tokio::sync::Mutex::new(()))).clone();
    OperationLockHandle::new(&am.driver_operation_locks, db_type, lock)
}

fn jre_operation_lock<'a>(am: &'a AgentManager, jre_key: &str) -> OperationLockHandle<'a> {
    let mut locks = am.jre_install_locks.lock().expect("JRE install lock table poisoned");
    let lock = locks.entry(jre_key.to_string()).or_insert_with(|| Arc::new(tokio::sync::Mutex::new(()))).clone();
    OperationLockHandle::new(&am.jre_install_locks, jre_key, lock)
}

/// Future that resolves as soon as any cancellation token fires.
fn first_cancellation<'a>(
    cancellations: &'a [&'a AgentInstallCancellation],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 'a>> {
    Box::pin(async move {
        let ((), _index, _rest) =
            futures::future::select_all(cancellations.iter().map(|token| Box::pin(token.cancelled()))).await;
    })
}

/// Acquire a per-driver/JRE operation lock, aborting promptly if any
/// cancellation token fires while the lock is held elsewhere. Without this
/// race a cancelled install would wait for the current lock holder to finish
/// before it could observe its token.

View on GitHub (pinned to c0390bff16)

Solutions

  1. Fix the root-cause panic that poisoned jre_install_locks first.
  2. Recover with lock().unwrap_or_else(|p| p.into_inner()) if the table can be safely reused after a panic.
  3. Avoid panicking inside the mutex critical section; return Results from mutation code.
  4. Switch the table to parking_lot::Mutex to eliminate poisoning entirely.

Example fix

// before
let mut locks = am.jre_install_locks.lock().expect("JRE install lock table poisoned");
// after
let mut locks = am.jre_install_locks.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
Defensive patterns

Strategy: try-catch

Validate before calling

// health check for JRE lock table
if am.jre_install_locks.is_poisoned() {
    tracing::error!("jre_install_locks mutex is poisoned");
}

Type guard

fn jre_locks_healthy(locks: &std::sync::Mutex<JreLockTable>) -> bool {
    !locks.is_poisoned()
}

Try / catch

let mut locks = am.jre_install_locks.lock().unwrap_or_else(|poisoned| {
    tracing::warn!("JRE lock table poisoned; recovering: {:?}", poisoned);
    poisoned.into_inner()
});

Prevention

When it happens

Trigger: A JRE install/uninstall/reinstall path panics while holding the jre_install_locks mutex guard; the next call to jre_operation_lock (for any jre_key) observes the poisoned mutex and panics.

Common situations: A prior JRE operation aborted with a panic (e.g. failed expect or assertion in extraction logic); in tests, a case that intentionally panics poisons the shared manager state for later assertions; a long-running app where one corrupted JRE package causes cascading failures on subsequent JRE operations.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/824d3b7f52ec9b38. Report an issue: GitHub.