{"record":{"id":"8049fd65d65a2a3d","repo":"iced-rs/iced","slug":"lock-hot-functions","errorCode":null,"errorMessage":"Lock hot functions","messagePattern":"Lock hot functions","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"debug/src/lib.rs","lineNumber":430,"sourceCode":"mod hot {\n    use std::collections::BTreeSet;\n    use std::sync::atomic::{self, AtomicBool};\n    use std::sync::{Arc, Mutex, OnceLock};\n\n    static IS_STALE: AtomicBool = AtomicBool::new(false);\n\n    static HOT_FUNCTIONS_PENDING: Mutex<BTreeSet<u64>> = Mutex::new(BTreeSet::new());\n\n    static HOT_FUNCTIONS: OnceLock<BTreeSet<u64>> = OnceLock::new();\n\n    pub fn init() {\n        cargo_hot::connect();\n\n        cargo_hot::subsecond::register_handler(Arc::new(|| {\n            if HOT_FUNCTIONS.get().is_none() {\n                HOT_FUNCTIONS\n                    .set(std::mem::take(\n                        &mut HOT_FUNCTIONS_PENDING.lock().expect(\"Lock hot functions\"),\n                    ))\n                    .expect(\"Set hot functions\");\n            }\n\n            IS_STALE.store(false, atomic::Ordering::Relaxed);\n        }));\n    }\n\n    pub fn call<O>(f: impl FnOnce() -> O) -> O {\n        let mut f = Some(f);\n\n        // The `move` here is important. Hotpatching will not work\n        // otherwise.\n        let mut f = cargo_hot::subsecond::HotFn::current(move || {\n            f.take().expect(\"Hot function is stale\")()\n        });\n\n        let address = f.ptr_address().0;","sourceCodeStart":412,"sourceCodeEnd":448,"githubUrl":"https://github.com/iced-rs/iced/blob/2cffa99b395d84fe469b44dccb56bbacd2f1a157/debug/src/lib.rs#L412-L448","documentation":"iced's hot-reload support (cargo-hot/subsecond) buffers addresses of executed functions in `HOT_FUNCTIONS_PENDING: Mutex<BTreeSet<u64>>`. When a patch arrives, the registered handler drains it with `.lock().expect(\"Lock hot functions\")`, which panics if a panic in `hot::call`'s insert path poisoned the mutex earlier.","triggerScenarios":"The first hotpatch lands after a previous panic occurred while `HOT_FUNCTIONS_PENDING` was held (the insert inside `hot::call`, or the `mem::take` in another handler invocation); the handler then fails to drain pending addresses.","commonSituations":"Iterating with iced's debug hot-reload while a panic in a hot-instrumented function was caught and the session kept running; two subsecond handlers registered racing over the same mutex.","solutions":["Find the earlier panic that poisoned HOT_FUNCTIONS_PENDING — the handler failure is secondary","Recover from poison: `lock().unwrap_or_else(PoisonError::into_inner)`","Register exactly one subsecond handler (see the Set hot functions race) so drain paths cannot overlap","Restart the hot-reload session if poisoning repeats — state cannot be repaired in-process"],"exampleFix":"// before\nHOT_FUNCTIONS_PENDING.lock().expect(\"Lock hot functions\")\n// after\nHOT_FUNCTIONS_PENDING\n    .lock()\n    .unwrap_or_else(std::sync::PoisonError::into_inner)","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"// maintainer-side: drain even when poisoned\nlet pending = std::mem::take(\n    &mut *HOT_FUNCTIONS_PENDING\n        .lock()\n        .unwrap_or_else(std::sync::PoisonError::into_inner),\n);","preventionTips":["Register the subsecond handler once so drain paths never overlap","After any panic during a hot-reload session, restart the process instead of continuing","Track the first panic in the session log — this expect is downstream of it","Keep hot-reload tooling versions (cargo-hot/subsecond) in lockstep with the iced debug crate"],"tags":["rust","iced","debug","hot-reload","mutex","lock-poisoning","panic"],"backgroundTag":"lock-poisoned","analyzedSha":"2cffa99b395d84fe469b44dccb56bbacd2f1a157","analyzedAt":"2026-08-16T19:41:49.083Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}