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

  1. Find the earlier panic that poisoned HOT_FUNCTIONS_PENDING — the handler failure is secondary
  2. Recover from poison: `lock().unwrap_or_else(PoisonError::into_inner)`
  3. Register exactly one subsecond handler (see the Set hot functions race) so drain paths cannot overlap
  4. 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

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


AI-assisted analysis of iced-rs/iced@2cffa99b39 (2026-08-16). Data as JSON: /api/errors/8049fd65d65a2a3d. Report an issue: GitHub.