DioxusLabs / DioxusLabs/dioxus

[desktop] UI freezes in an existing Tokio runtime after cooperative budget exhaustion

Open
#5,679 0 comments 0 reactions 0 assignees View on GitHub
bug
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.