nikivdev/code · error
http client cache mutex poisoned
Error message
http client cache mutex poisoned
What it means
blocking_with_timeout caches reqwest blocking Clients keyed by timeout in a global Mutex; if another thread panicked while holding the lock, the Mutex is poisoned and lock() returns Err, producing this error. It surfaces a poisoned shared-state condition instead of propagating a poison panic.
Source
Thrown at src/http_client.rs:20
use std::sync::{Mutex, OnceLock};
use std::time::Duration;
use anyhow::{Context, Result};
use reqwest::blocking::Client;
static BLOCKING_CLIENTS: OnceLock<Mutex<HashMap<u64, Client>>> = OnceLock::new();
fn timeout_key(timeout: Duration) -> u64 {
timeout.as_millis().min(u64::MAX as u128) as u64
}
/// Reuse blocking reqwest clients by timeout bucket to avoid repeated TLS/client init.
pub fn blocking_with_timeout(timeout: Duration) -> Result<Client> {
let clients = BLOCKING_CLIENTS.get_or_init(|| Mutex::new(HashMap::new()));
let key = timeout_key(timeout);
let mut guard = clients
.lock()
.map_err(|_| anyhow::anyhow!("http client cache mutex poisoned"))?;
if let Some(client) = guard.get(&key) {
return Ok(client.clone());
}
let client = Client::builder()
.timeout(timeout)
.build()
.with_context(|| format!("failed to build http client with timeout {:?}", timeout))?;
guard.insert(key, client.clone());
Ok(client)
}
View on GitHub (pinned to a747e741ae)
Solutions
- Fix the underlying panic that poisoned the mutex (check logs from the first panicking thread)
- Restart the process to clear the poisoned mutex
- Replace Mutex with a poisoning-tolerant pattern (lock, recover data with into_inner, or use a lock-free cache)
Example fix
// before
let mut guard = clients.lock().map_err(|_| anyhow::anyhow!("http client cache mutex poisoned"))?;
// after
let mut guard = match clients.lock() {
Ok(g) => g,
Err(poisoned) => poisoned.into_inner(), // recover cache instead of failing
}; Defensive patterns
Strategy: try-catch
Try / catch
match blocking_with_timeout(Duration::from_secs(30)) {
Err(e) if e.to_string().contains("mutex poisoned") => {
// restart process or bypass cache with a fresh Client::new()
let client = reqwest::blocking::Client::builder().timeout(Duration::from_secs(30)).build()?;
}
other => other?,
} Prevention
- Never panic while holding the client-cache mutex
- Use into_inner() recovery instead of failing on poison
- Keep client construction fallible (use ?) rather than unwrap under the lock
When it happens
Trigger: Any call to blocking_with_timeout after a thread panicked while holding the BLOCKING_CLIENTS mutex (e.g. panic inside client construction under the lock).
Common situations: A prior request thread panicked during TLS/client init, leaving the cache mutex poisoned for all subsequent requests in the process.
Related errors
- device auth start failed: HTTP {}
- device auth poll failed: HTTP {}
- Maple MCP request failed ({}): {}
- codex app-server reader disconnected
- remote review URL not configured
AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01).
Data as JSON: /api/errors/030a0d6613a7cf5d.
Report an issue: GitHub.