{"record":{"id":"5b6455fcab198036","repo":"BoundaryML/baml","slug":"failed-to-acquire-lock","errorCode":null,"errorMessage":"Failed to acquire lock","messagePattern":"Failed to acquire lock","errorType":"exception","errorClass":"LogError","httpStatus":null,"severity":"error","filePath":"engine/baml-lib/baml-log/src/logger.rs","lineNumber":419,"sourceCode":"    static ref CONFIG: RwLock<LogConfig> = RwLock::new(LogConfig::from_env());\n    static ref LOGGED_LINES: RwLock<HashSet<(Option<String>, Option<String>, Option<u32>)>> = RwLock::new(HashSet::new());\n    /// Optional log file path — when set, log output goes to this file instead of stdout.\n    static ref LOG_FILE: Mutex<Option<PathBuf>> = Mutex::new(None);\n}\n\n/// Error type for logging operations\n#[derive(Debug, Error)]\npub enum LogError {\n    /// Error writing to output\n    #[error(\"IO error: {0}\")]\n    Io(#[from] io::Error),\n\n    /// Error serializing to JSON\n    #[error(\"JSON serialization error: {0}\")]\n    Json(#[from] serde_json::Error),\n\n    /// Error acquiring lock\n    #[error(\"Failed to acquire lock\")]\n    LockError,\n\n    /// Configuration error\n    #[error(\"Configuration error: {0}\")]\n    Config(String),\n}\n\n// /// JSON-serializable log entry\n// #[derive(Serialize)]\n// struct LogEntry<'a> {\n//     /// Timestamp in ISO 8601 format\n//     timestamp: String,\n//     /// Log level as a string\n//     level: &'a str,\n//     /// Log message\n//     message: String,\n//     /// Optional module path\n//     #[serde(skip_serializing_if = \"Option::is_none\")]","sourceCodeStart":401,"sourceCodeEnd":437,"githubUrl":"https://github.com/BoundaryML/baml/blob/bd85ce9dee1463ff04d27efd20531013a4ff46c1/engine/baml-lib/baml-log/src/logger.rs#L401-L437","documentation":"LogError::LockError is a parameterless variant meaning the logger failed to acquire its internal lock (e.g. a Mutex/PoisonError or try_lock failure) guarding shared logging state. It renders as 'Failed to acquire lock'.","triggerScenarios":"Concurrent logging contention where the lock cannot be acquired, or a lock poisoned by a panic in another thread while it was held.","commonSituations":"High-concurrency multithreaded apps logging simultaneously; a panic inside a logging call poisoning the mutex; using try_lock paths under heavy load.","solutions":["Find and fix any panic occurring while the logger lock is held (poisoned mutex)","Retry the log call; the lock may be transiently unavailable","Serialize logging through fewer writer threads or an async channel","If persistent, reinitialize the logger to replace the poisoned state"],"exampleFix":"// before\nlet _guard = logger.lock().unwrap(); // panics if poisoned\n// after\nmatch logger.lock() {\n    Ok(g) => { /* log */ },\n    Err(_) => eprintln!(\"logger lock unavailable\"),\n}","handlingStrategy":"retry","validationCode":"// Detect a poisoned logger lock before relying on logging\nif logger.lock().is_err() {\n    eprintln!(\"logger lock poisoned; reinitializing logger\");\n}","typeGuard":"fn is_lock_err(e: &LogError) -> bool { matches!(e, LogError::LockError) }","tryCatchPattern":"for attempt in 0..2 {\n    match logger.log(record) {\n        Err(LogError::LockError) if attempt == 0 => continue,\n        Err(e) => { eprintln!(\"log failed: {e}\"); break; }\n        Ok(_) => break,\n    }\n}","preventionTips":["Never panic while holding the logger lock (poisons the mutex)","Keep log-call bodies minimal to avoid panics inside critical sections","Use a channel-based logger to decouple logging from lock contention"],"tags":["concurrency","locking","logging","multithreading"],"backgroundTag":"thread-interrupted","analyzedSha":"bd85ce9dee1463ff04d27efd20531013a4ff46c1","analyzedAt":"2026-09-12T03:38:25.718Z","contentChangedAt":"2026-09-12T03:38:25.718Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}