DioxusLabs / DioxusLabs/dioxus
[desktop] UI freezes in an existing Tokio runtime after cooperative budget exhaustion
- Dominant language
- Rust
- Stars
- 39.1k
- Forks
- 1.9k
- Avg merge
- 4d 10h
- Merged PRs (30d)
- 4
Description
**Problem**
A Dioxus Desktop application enters an infinite polling loop and becomes unresponsive when launched from an existing multi-thread Tokio runtime.
When Tokio's cooperative budget is exhausted, a Tokio primitive returns `Poll::Pending` and defers the current waker. Dioxus immediately schedules and polls the same local task again without returning to a Tokio task scheduling boundary, so the cooperative budget is never reset.
The same Dioxus task is then polled indefinitely on the desktop UI thread. This was initially observed with `reqwest`, but the issue does not appear to be specific to HTTP.
The relevant stack is:
```text
dioxus_core::tasks::LocalTaskHandle::wake_by_ref
futures_task::waker::wake_by_ref_arc_raw
core::task::Waker::wake_by_ref
tokio::runtime::context::defer
tokio::task::coop::register_waker
tokio::task::coop::poll_proceed
tokio::sync::oneshot::Inner::poll_recv
hyper::client::conn::http1::try_send_request
hyper_util::client::legacy::Client::try_send_request
```
The Dioxus task ID is identical on every iteration and its poll result is always `Poll::Pending`.
**Steps To Reproduce**
A minimal reproduction that exhausts the cooperative budget without requiring an HTTP server:
```rust
use dioxus::prelude::*;
#[tokio::main]
async fn main() {
LaunchBuilder::desktop().launch(App);
}
#[component]
fn App() -> Element {
let mut completed = use_signal(|| false);
use_future(move || async move {
let (sender, mut receiver) = tokio::sync::mpsc::channel(256);
for value in 0..256 {
sender.try_send(value).unwrap();
}
for _ in 0..256 {
receiver.recv().await.unwrap();
}
completed.set(true);
});
rsx! {
button { "The window should remain responsive" }
p { "Completed: {completed}" }
}
}
```
The real application reproduces this reliably while awaiting:
```rust
reqwest::Client::builder()
.timeout(Duration::from_secs(15))
.build()?
.get(url)
.send()
.await
```
The timeout also cannot recover because Tokio's `Sleep::poll` goes through `coop::poll_proceed`.
**Expected behavior**
The Dioxus task should yield without the desktop event loop repeatedly polling the same task. The window should remain responsive and Tokio's cooperative budget should be able to reset.
**Suspected cause**
In `dioxus-desktop/src/launch.rs`, the branch that creates its own Tokio runtime wraps the blocking desktop event loop in:
```rust
tokio::task::unconstrained(async move {
launch_virtual_dom_blocking(virtual_dom, desktop_config)
})
```
When an existing Tokio runtime is detected, it instead directly calls:
```rust
launch_virtual_dom_blocking(virtual_dom, desktop_config);
```
Because the blocking desktop event loop is entered during a poll of the outer Tokio main task, that task never returns to the Tokio scheduler and its cooperative budget is never reset.
**Workaround**
Wrapping the Dioxus launch in `tokio::task::unconstrained` prevents the freeze:
```rust
tokio::task::unconstrained(async move {
LaunchBuilder::desktop().launch(App);
})
.await;
```
It may be necessary for the existing-runtime launch path to provide the same unconstrained behavior as the internally-created-runtime path.
**Screenshots**
Not applicable. The desktop window becomes unresponsive.
**Environment:**
- Dioxus version: 0.7.9
- dioxus-desktop version: 0.7.9
- Tokio version: 1.52.3
- reqwest version: 0.12.28
- OS info: Windows
- App platform: desktop
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the existing-runtime and internally-created-runtime paths in dioxus-desktop/src/launch.rs, then run the minimal Tokio channel reproduction from the issue. Compare how each path enters launch_virtual_dom_blocking and verify the desktop event loop yields instead of repeatedly polling the same pending task; the window should remain responsive and the example should complete.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100