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
- Guard initialization with std::sync::Once (or call it from a single, documented entry point) so register_handler runs once
- Use `HOT_FUNCTIONS.get_or_init(|| mem::take(...))` instead of check-then-set to make it race-free
- If duplicates are benign, ignore the failure: `let _ = HOT_FUNCTIONS.set(...)`
- 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
- Initialize hot-reload support from exactly one documented entry point
- If multiple init paths are unavoidable, wrap registration in std::sync::Once or an AtomicBool
- Prefer get_or_init over check-then-set when filling shared OnceLocks — the naive pattern races by construction
- Ignore duplicate `set` results (let _ = ...) when losing the race is benign
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
- Lock hot functions
- Hot function is stale
- Encode input message
- Encode input message
- Write application metadata
AI-assisted analysis of iced-rs/iced@2cffa99b39 (2026-08-16).
Data as JSON: /api/errors/f427ff8c11785151.
Report an issue: GitHub.