iced-rs/iced · error
Lock hot functions
Error message
Lock hot functions
What it means
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.
Source
Thrown at debug/src/lib.rs:430
mod hot {
use std::collections::BTreeSet;
use std::sync::atomic::{self, AtomicBool};
use std::sync::{Arc, Mutex, OnceLock};
static IS_STALE: AtomicBool = AtomicBool::new(false);
static HOT_FUNCTIONS_PENDING: Mutex<BTreeSet<u64>> = Mutex::new(BTreeSet::new());
static HOT_FUNCTIONS: OnceLock<BTreeSet<u64>> = OnceLock::new();
pub fn init() {
cargo_hot::connect();
cargo_hot::subsecond::register_handler(Arc::new(|| {
if HOT_FUNCTIONS.get().is_none() {
HOT_FUNCTIONS
.set(std::mem::take(
&mut HOT_FUNCTIONS_PENDING.lock().expect("Lock hot functions"),
))
.expect("Set hot functions");
}
IS_STALE.store(false, atomic::Ordering::Relaxed);
}));
}
pub fn call<O>(f: impl FnOnce() -> O) -> O {
let mut f = Some(f);
// The `move` here is important. Hotpatching will not work
// otherwise.
let mut f = cargo_hot::subsecond::HotFn::current(move || {
f.take().expect("Hot function is stale")()
});
let address = f.ptr_address().0;View on GitHub (pinned to 2cffa99b39)
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
Example fix
// before
HOT_FUNCTIONS_PENDING.lock().expect("Lock hot functions")
// after
HOT_FUNCTIONS_PENDING
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) Defensive patterns
Strategy: fallback
Try / catch
// maintainer-side: drain even when poisoned
let pending = std::mem::take(
&mut *HOT_FUNCTIONS_PENDING
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner),
); Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- lock font name cache
- Write application metadata
- Read last palette
- Write last palette
- Read application metadata
AI-assisted analysis of iced-rs/iced@2cffa99b39 (2026-08-16).
Data as JSON: /api/errors/8049fd65d65a2a3d.
Report an issue: GitHub.