Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Infinite loop when wasm-bindgen Closures are used to alter signal state #1951

Closed
3 tasks
s1gtrap opened this issue Feb 17, 2024 · 4 comments
Closed
3 tasks
Assignees
Labels
bug Something isn't working core relating to the core implementation of the virtualdom

Comments

@s1gtrap
Copy link

s1gtrap commented Feb 17, 2024

Problem

poll_tasks of virtual_dom.rs is entering an infinite loop when wasm-bindgen Closures are used. Initially discovered when I tried to propagate changes to a signal from a wasm_bindgen::closure::Closure. Specifically to be used with the Ace editor, but any other EventListener registered with add_event_listener_with_callback seems to cause the same issue.

Not sure how useful this would be, but after finding the culprit with the profiler (99% of runtime is spent in poll_tasks), I added some debug prints in trying to figure out what was happening. In my experience, the loop on line 431 is entered, and despite receiving TaskNotified continually and calling handle_task_wakeup, the halting condition is never met as self.dirty_scopes is never populated and remains empty.

Relevant discussion: https://discord.com/channels/899851952891002890/1207014268994592818

Steps To Reproduce

Steps to reproduce the behavior:

I came up with this minimal example:

#![allow(non_snake_case)]

use dioxus::prelude::*;
use wasm_bindgen::prelude::*;

#[component]
pub fn Editor(state: Signal<()>) -> Element {
    let window = web_sys::window().unwrap();
    let document = window.document().unwrap();
    let mut editor = use_signal(|| None);
    use_effect({
        move || {
            log::info!("enter 1st effect");
            if editor.read().is_none() {
                let elem = document.get_element_by_id("editor").unwrap();
                let closure = Closure::new({
                    move || {
                        log::info!("enter event handler");
                        *state.write() = ();
                        log::info!("exit event handler");
                    }
                });
                elem.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref())
                    .unwrap();
                closure.forget();
                *editor.write() = Some(elem);
            }
            log::warn!("exit 1st effect");
        }
    });
    use_effect(move || {
        log::info!("enter 2nd effect");
        if let Some(editor) = &*editor.read() {
            editor.set_inner_html(&format!("{:?}", (&state))); // THIS
        }
        log::warn!("exit 2nd effect");
    });
    rsx! {
        div {
            id: "editor",
            class: "h-full bg-slate-100",
            ""
        }
    }
}

fn app() -> Element {
    let state = use_signal(|| ());
    rsx! {
        div {
            div {
                class: "h-screen",
                Editor {
                    state,
                }
            }
        }
    }
}

fn main() {
    dioxus_logger::init(log::LevelFilter::Trace).expect("failed to init logger");
    console_error_panic_hook::set_once();
    tracing_wasm::set_as_global_default_with_config(
        tracing_wasm::WASMLayerConfigBuilder::default()
            .set_max_level(tracing::Level::TRACE)
            .build(),
    );
    launch(app);
}

which consists of two effects, one to initialize the web_sys::Element with get_element_by_id and the other to register events after. The loop can be incurred by clicking the element, and after a couple seconds your browser will complain/warn you about a script not responding. Interestingly if you comment the line marked with // THIS the loop doesn't occur, presumably because the 2nd effect no longer depends on the state signal which seems to trigger the endless loop.

Expected behavior

To not cause the page to stop responding.

Screenshots

If applicable, add screenshots to help explain your problem.

Environment:

  • Dioxus version: master
  • Rust version: 1.78.0, nightly
  • OS info: MacOS
  • App platform: web`

Questionnaire

  • I'm interested in fixing this myself but don't know where to start
  • I would like to fix and I have a solution
  • I don't have time to fix this right now, but maybe later
@ealmloff
Copy link
Member

I cannot reproduce this issue with the latest git version of dioxus. It might have been fixed by #1925 or another recent update

@jkelleyrtp jkelleyrtp added this to the 0.5.0: Signals milestone Feb 23, 2024
@s1gtrap
Copy link
Author

s1gtrap commented Feb 24, 2024

I cannot reproduce this issue with the latest git version of dioxus. It might have been fixed by #1925 or another recent update

Nope, problem remains.. I'm really not sure what else to do, as both Firefox and Chrome run into this issue on both Linux and macOS (although I can't get a sensible performance profile with Chrome as it seems to hang on "Processing profile..." indefinitely..)

I uploaded the above example (slightly modified) here: https://github.com/s1gtrap/min-ace

It depends on a forked dioxus repo instead (https://github.com/s1gtrap/dioxus) but the branch being checked out is equivalent to latest main (e0b0afc0) except for some debug prints in poll_tasks that help demonstrate the infinite loop.

If you pull min-ace and run it with dx serve, you ought to be greeted by:

Screenshot 2024-02-24 at 15 48 08

Immediately clicking the cyan button increases the count as intended, but if you press the orange beforehand it enters the poll_tasks loop and the cyan button stops working.

@ealmloff
Copy link
Member

I can reproduce the issue in that repo. With the information from #1982, it looks like it is not related to signal subscriptions

@ealmloff ealmloff self-assigned this Feb 26, 2024
@ealmloff ealmloff added bug Something isn't working core relating to the core implementation of the virtualdom labels Feb 27, 2024
@jkelleyrtp
Copy link
Member

This has been fixed! It was an issue with flush lock which we've revamped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working core relating to the core implementation of the virtualdom
Projects
None yet
Development

No branches or pull requests

3 participants