{"record":{"id":"10cac6d603115e42","repo":"iced-rs/iced","slug":"hot-function-is-stale","errorCode":null,"errorMessage":"Hot function is stale","messagePattern":"Hot function is stale","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"debug/src/lib.rs","lineNumber":445,"sourceCode":"            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;\n\n        if let Some(hot_functions) = HOT_FUNCTIONS.get() {\n            if hot_functions.contains(&address) {\n                IS_STALE.store(true, atomic::Ordering::Relaxed);\n            }\n        } else {\n            let _ = HOT_FUNCTIONS_PENDING\n                .lock()\n                .expect(\"Lock hot functions\")\n                .insert(address);\n        }\n\n        f.call(())\n    }\n","sourceCodeStart":427,"sourceCodeEnd":463,"githubUrl":"https://github.com/iced-rs/iced/blob/2cffa99b395d84fe469b44dccb56bbacd2f1a157/debug/src/lib.rs#L427-L463","documentation":"`hot::call` wraps your function in an FnOnce closure consumed exactly once via `f.take().expect(\"Hot function is stale\")`. The panic means the subsecond/cargo-hot runtime invoked the wrapper closure a second time after the FnOnce was already taken — stale code for the function ran again after (or during) a hotpatch swap, violating the run-once invariant.","triggerScenarios":"Re-entrant execution of a hot-instrumented function while a patch is being applied; a hotpatched function reached from multiple dispatch paths where cargo-hot swaps it mid-flight; subsecond replaying a frame after a patch.","commonSituations":"Editing recursive or mutually recursive functions under iced hot reload; long-running frames where the patched function is on the stack when the patch lands; version mismatch between cargo-hot/subsecond and iced's debug crate.","solutions":["Make hot-instrumented entry points non-reentrant (hoist recursion out of functions wrapped by hot::call)","Ensure only one subsecond handler/init path is registered (duplicate handlers re-dispatch stale frames)","Align cargo-hot/subsecond versions with the iced debug crate and restart the hot-reload session","If reproducible with hotpatching disabled, or deterministic on one input, report upstream to iced/cargo-hot with the backtrace"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| hot::call(work)));\nif result.is_err() {\n    // stale-frame invariant broken: end the hot-reload session, keep the process\n    log::error!(\"hot function executed twice — restart the hot-reload session\");\n}","preventionTips":["Keep functions wrapped by hot::call non-reentrant; hoist recursion and mutual recursion out","Do not register multiple subsecond handlers — re-dispatch is the main way a frame runs twice","Restart the hot-reload session after any panic or patch anomaly instead of continuing on stale frames","Match cargo-hot/subsecond versions with the iced debug crate you build against"],"tags":["rust","iced","debug","hot-reload","subsecond","reentrancy","panic"],"backgroundTag":"hot-reload-stale-function","analyzedSha":"2cffa99b395d84fe469b44dccb56bbacd2f1a157","analyzedAt":"2026-08-16T19:41:49.083Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}