iced-rs/iced · error

Set hot functions

Error message

Set hot functions

What it means

`HOT_FUNCTIONS: OnceLock<BTreeSet<u64>>` is filled exactly once when the first hotpatch arrives. `.set(...).expect("Set hot functions")` fires when `set` runs after the OnceLock is already populated. The `is_none()` pre-check does not make this atomic: two handler invocations (or one handler registered twice) can both observe None and both try to set; the loser panics.

Source

Thrown at debug/src/lib.rs:432

    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;

        if let Some(hot_functions) = HOT_FUNCTIONS.get() {

View on GitHub (pinned to 2cffa99b39)

Solutions

  1. Guard initialization with std::sync::Once (or call it from a single, documented entry point) so register_handler runs once
  2. Use `HOT_FUNCTIONS.get_or_init(|| mem::take(...))` instead of check-then-set to make it race-free
  3. If duplicates are benign, ignore the failure: `let _ = HOT_FUNCTIONS.set(...)`
  4. Audit for duplicate handler registration: add a debug log/assert in init counting calls

Example fix

// before
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");
}
// after
let pending = std::mem::take(
    &mut *HOT_FUNCTIONS_PENDING.lock().unwrap_or_else(std::sync::PoisonError::into_inner),
);
let _ = HOT_FUNCTIONS.set(pending); // idempotent: losing a benign race is fine
Defensive patterns

Strategy: validation

Validate before calling

static HOTPATCH_INIT: std::sync::Once = std::sync::Once::new();

pub fn init() {
    HOTPATCH_INIT.call_once(real_init); // second caller is a no-op, handler registered once
}

Prevention

When it happens

Trigger: Calling the init that invokes `cargo_hot::subsecond::register_handler` more than once, so multiple handlers run on patch arrival; or concurrent handler invocations racing the check-then-set sequence on the first patch.

Common situations: Debug helpers that get initialized from several entry points (main, tests, workspace tools); refactors that made init idempotent everywhere except this registration; upgrading iced debug versions that changed handler wiring.

Related errors


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