{"record":{"id":"924877ff5aaecf89","repo":"vectordotdev/vector","slug":"mutex-poisoned-settings","errorCode":null,"errorMessage":"mutex poisoned","messagePattern":"mutex poisoned","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/vector-core/src/tls/settings.rs","lineNumber":414,"sourceCode":"            }\n        } else {\n            connection.set_verify_hostname(self.verify_hostname);\n        }\n        Ok(())\n    }\n}\n\n/// Return a `'static` copy of a server ALPN protocol list, leaking each distinct list at most once.\n///\n/// `SslContextBuilder::set_alpn_select_callback` requires the protocol list to outlive the context\n/// with a `'static` lifetime, so the bytes must be leaked. Interning by content means rebuilding an\n/// acceptor with the same ALPN configuration (e.g. on every certificate reload) reuses the existing\n/// allocation instead of leaking a fresh copy each time, keeping the leak bounded and one-time.\nfn intern_alpn_protocols(protocols: &[u8]) -> &'static [u8] {\n    static INTERNED: LazyLock<Mutex<HashMap<Vec<u8>, &'static [u8]>>> =\n        LazyLock::new(|| Mutex::new(HashMap::new()));\n\n    let mut interned = INTERNED.lock().expect(\"mutex poisoned\");\n\n    if let Some(existing) = interned.get(protocols).copied() {\n        return existing;\n    }\n    let leaked: &'static [u8] = Box::leak(protocols.to_vec().into_boxed_slice());\n    interned.insert(protocols.to_vec(), leaked);\n    leaked\n}\n\nimpl TlsConfig {\n    fn load_authorities(&self) -> Result<Vec<X509>> {\n        match &self.ca_file {\n            None => Ok(vec![]),\n            Some(filename) => {\n                let (data, filename) = open_read(filename, \"certificate\")?;\n                der_or_pem(\n                    data,\n                    |der| X509::from_der(&der).map(|x509| vec![x509]),","sourceCodeStart":396,"sourceCodeEnd":432,"githubUrl":"https://github.com/vectordotdev/vector/blob/bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013/lib/vector-core/src/tls/settings.rs#L396-L432","documentation":"`intern_alpn_protocols` interns ALPN protocol byte strings into a leaked static table guarded by a `Mutex`; `.expect(\"mutex poisoned\")` panics if another thread panicked while holding the lock, poisoning it. After poisoning, every subsequent TLS context build with ALPN configuration on this process will panic.","triggerScenarios":"A thread panics while holding the INTERNED mutex lock (e.g. during allocation), then any later call to `intern_alpn_protocols` (via `apply_context_base`, e.g. on certificate reload) panics on the poisoned lock.","commonSituations":"Panics inside TLS context setup cascading into persistent 'mutex poisoned' failures across cert reloads; long-running services doing repeated `TlsSettings::apply_context` calls.","solutions":["Find and fix the original panic that occurred while the mutex was held — the poisoning is secondary","Upgrade/patch so the critical section cannot panic (allocation failures aside, it is panic-free)","Restart the process — poisoning is permanent for the process lifetime","If resilience matters, replace `Mutex::lock().expect` with `lock().unwrap_or_else(|p| p.into_inner())` since the map tolerates a torn insert"],"exampleFix":"// before\nlet mut interned = INTERNED.lock().expect(\"mutex poisoned\");\n// after\nlet mut interned = INTERNED\n    .lock()\n    .unwrap_or_else(|poisoned| poisoned.into_inner());","handlingStrategy":"try-catch","validationCode":"// Cannot pre-validate; ensure no panics occur while the intern lock is held\nassert!(!protocols.is_empty() && protocols.len() % 2 == 0, \"ALPN wire format is length-prefixed\");","typeGuard":null,"tryCatchPattern":"// poisoning is permanent; detect at first use and restart the component\nmatch std::panic::catch_unwind(|| settings.apply_context(&mut builder)) {\n    Ok(_) => {},\n    Err(_) => restart_tls_subsystem(), // 'mutex poisoned' observed\n}","preventionTips":["Keep the interned-map critical section panic-free (it currently only allocates)","If you maintain this code, recover via poisoned.into_inner() since the map tolerates re-inserts","Monitor for earlier panics in TLS context setup — they are the root cause","Restart the process after poisoning; it cannot clear itself"],"tags":["tls","rust","concurrency","mutex","alpn"],"backgroundTag":"mutex-poisoned","analyzedSha":"bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013","analyzedAt":"2026-09-16T02:53:35.741Z","contentChangedAt":"2026-09-16T02:53:35.741Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}