DioxusLabs / DioxusLabs/dioxus
When hot patching on WASM, wasm_bindgen_futures cause `RefCell: Already borrowed`
- Dominant language
- Rust
- Stars
- 39.1k
- Forks
- 1.9k
- Avg merge
- 4d 10h
- Merged PRs (30d)
- 4
Description
When hot reloading and running futures, oftentimes a panic will be printed of the sort at the bottom of this issue.
A couple of ways that I can imagine fixing this are below. Very curious for your thoughts and for if I might be missing anything!
I'm currently running with the latter (simpler) PR – #4905 – in dev.
## [Make subsecond defer handler execution on wasm (link to PR)](https://github.com/DioxusLabs/dioxus/pull/4904)
Keep the immediate behaviour for native targets, but when commit_patch runs on wasm schedule the handlers onto a fresh microtask instead of calling them inline. That way the current executor poll can finish (dropping the RefMut) before any wakeups fire.
Adjust from:
https://github.com/DioxusLabs/dioxus/blob/06f39cd1407e21cac1785a5202c18fc685df6a4d/packages/subsecond/subsecond/src/lib.rs#L308-L321
to:
```rust
unsafe fn commit_patch(table: JumpTable) {
APP_JUMP_TABLE.store(
Box::into_raw(Box::new(table)),
std::sync::atomic::Ordering::Relaxed,
);
let handlers = HOTRELOAD_HANDLERS.lock().unwrap().clone();
#[cfg(target_arch = "wasm32")]
{
for handler in handlers {
// run after the current Task::run has unwound
wasm_bindgen_futures::spawn_local(async move {
handler();
});
}
}
#[cfg(not(target_arch = "wasm32"))]
{
for handler in handlers {
handler();
}
}
}
```
Because spawn_local enqueues a brand-new executor task, it doesn’t wake the one that’s still actively polling the patch future, so the RefCell isn’t re‑borrowed.
## [Make Dioxus schedule its handler asynchronously (link to PR)](https://github.com/DioxusLabs/dioxus/pull/4905)
Wrap the channel send inside another spawn_local so that even if commit_patch stays synchronous the wake happens later, changing from:
https://github.com/DioxusLabs/dioxus/blob/06f39cd1407e21cac1785a5202c18fc685df6a4d/packages/core/src/virtual_dom.rs#L770-L776
to:
```rust
#[cfg(debug_assertions)]
fn register_subsecond_handler(&self) {
let sender = self.runtime().sender.clone();
subsecond::register_handler(std::sync::Arc::new(move || {
let sender = sender.clone();
wasm_bindgen_futures::spawn_local(async move {
let _ = sender.unbounded_send(SchedulerMsg::AllDirty);
});
}));
}
```
This has the caveat that it'd not work for other handlers 'out of the box', but is more contained.
### The panic
```
13:40:15 [web] panicked at /Users/amar/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/wasm-bindgen-futures-0.4.54/src/task/singlethreadRefCell already borrowed
Stack:
Error
at https://domain/wasm/render.js:10325:21
at logError (https://domain/wasm/render.js:14:18)
at imports.wbg.__wbg_new_8a6f238a6ece86ea (https://domain/wasm/render.js:10324:66)
at render.wasm.__wbg_new_8a6f238a6ece86ea externref shim (https://domain/wasm/render_bg.wasm:wasm-function[76599]:0x14f5c72)
at render.wasm._ZN24console_error_panic_hook4hook17h10f7c3e6803ac586E (https://domain/wasm/render_bg.wasm:wasm-function[6095]:0x78a4dc)
at render.wasm._ZN4core3ops8function2Fn4call17h59128e3e78c15912E.llvm.8575340594148377989 (https://domain/wasm/render_bg.wasm:wasm-function[96367]:0x1540d03)
at render.wasm._ZN3std9panicking15panic_with_hook17h96a36e151620b995E (https://domain/wasm/render_bg.wasm:wasm-function[27212]:0x10b03b2)
at render.wasm._ZN3std9panicking13panic_handler28_$u7b$$u7b$closure$u7d$$u7d$17he6e0314da46f86d3E (https://domain/wasm/render_bg.wasm:wasm-function[33231]:0x11cbeb9)
at render.wasm._ZN3std3sys9backtrace26__rust_end_short_backtrace17hb34feac54bc1249aE (https://domain/wasm/render_bg.wasm:wasm-function[93125]:0x1538f87)
at render.wasm._RNvCs50wjZh92VUG_7___rustc17rust_begin_unwind (https://domain/wasm/render_bg.wasm:wasm-function[63548]:0x149598d
```
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.