DioxusLabs/dioxus · error
Callback was called after the runtime was dropped
Error message
Callback was called after the runtime was dropped
What it means
A Callback stores a Weak handle to the Dioxus runtime (Rc<Runtime>) that existed when it was created. call() upgrades that Weak handle; if the VirtualDom/runtime has been dropped, upgrade returns None and this expect panics. It is Dioxus' guard against event handlers firing into a dead app.
Source
Thrown at packages/core/src/events.rs:524
Some(ExternalListenerCallback {
callback: Box::new(move |event: Args| f(event).spawn()),
runtime: Rc::downgrade(&runtime),
}),
Location::caller(),
);
Self { callback, origin }
}
/// Call this callback with the appropriate argument type
///
/// This borrows the callback using a RefCell. Recursively calling a callback will cause a panic.
#[track_caller]
pub fn call(&self, arguments: Args) -> Ret {
if let Some(callback) = self.callback.write().as_mut() {
let runtime = callback
.runtime
.upgrade()
.expect("Callback was called after the runtime was dropped");
let _guard = RuntimeGuard::new(runtime.clone());
runtime.with_scope_on_stack(self.origin, || (callback.callback)(arguments))
} else {
panic!("Callback was manually dropped")
}
}
/// Create a `impl FnMut + Copy` closure from the Closure type
pub fn into_closure(self) -> impl FnMut(Args) -> Ret + Copy + 'static {
move |args| self.call(args)
}
/// Forcibly drop the internal handler callback, releasing memory
///
/// This will force any future calls to "call" to not doing anything
pub fn release(&self) {
self.callback.set(None);
}View on GitHub (pinned to 393d190a80)
Solutions
- Detach external listeners and timers in component cleanup (use_effect cleanup / on unmount) before the runtime can be dropped
- Keep the VirtualDom alive as long as anything can still fire its callbacks (store the vdom next to the handler)
- In tests, trigger events while the vdom is still owned rather than after dropping it
- Don't store Callbacks in global/static state; track app teardown with a flag and stop calling after it
Defensive patterns
Strategy: validation
Try / catch
// At an FFI/JS boundary where a stored handler may outlive the app:
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
stored_callback.call(args);
}));
if result.is_err() {
// runtime was dropped: unregister the listener and stop calling it
unregister_listener();
} Prevention
- Unregister external listeners, timers and sockets in component cleanup before teardown
- Tie the Callback's lifetime to the VirtualDom's: store both in the same owner
- Never move a Callback into 'static state; wrap it in an Option your teardown code sets to None
- In tests, trigger events while the vdom is still in scope
When it happens
Trigger: Invoking a Callback prop or a closure produced by into_closure() after the VirtualDom that created it was dropped: a JS addEventListener/setInterval/websocket handler still registered after unmount, a background thread or task that outlives the app, or a test that drops the vdom and then triggers events.
Common situations: Renderer torn down while external listeners (window events, timers, sockets) still hold the handler; passing a Callback into 'static or global state; integration tests firing events after the vdom goes out of scope.
Related errors
- todo: convert_resize_data in dioxus-native. requires support
- todo: convert_visible_data in dioxus-native. requires suppor
- The hook list is already borrowed: This error is likely caus
- The tree has not been built yet. Make sure to call rebuild o
- only an InputElement or TextAreaElement or an element with c
AI-assisted analysis of DioxusLabs/dioxus@393d190a80 (2026-08-16).
Data as JSON: /api/errors/6b1848d0838f8a78.
Report an issue: GitHub.