DioxusLabs / dioxus

Fullstack GUI library for web, desktop, mobile, and more.
https://dioxuslabs.com
Apache License 2.0
19.3k stars 735 forks source link

EventHandlers on elements are owned by parent, causing long-running memory usage #2227

Closed jkelleyrtp closed 2 months ago

jkelleyrtp commented 2 months ago

Problem

In https://github.com/DioxusLabs/dioxus/discussions/2195 we received a report that global signals might be causing large memory usage. This shouldn't be the case but it's worth investigating and writing tests just in case.

Questionnaire

marc2332 commented 2 months ago

Small repro:

#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus::prelude::dioxus_core::NoOpMutations;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    fn app() -> Element {
        let mut count = use_signal(|| 0);

        use_hook(|| spawn(async move {
            loop {
                tokio::time::sleep(std::time::Duration::from_millis(2)).await;
                let val = *count.peek_unchecked();
                if val == 50 {
                    count.set(0);
                } else {
                    count.set(val + 1);
                }
            }
        }));

        rsx! {
            div { 
                color: "red",
                for el in 0..*count.read() {
                    rect {
                        key: "{el}",
                        onclick: move |_| { },
                        label {
                            "{el}"
                        }
                    }
                }
            }
        }
    }

    // create the vdom, the real_dom, and the binding layer between them
    let mut vdom = VirtualDom::new(app);

    vdom.rebuild(&mut NoOpMutations);

    // we need to run the vdom in a async runtime
    tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()?
        .block_on(async {
            loop {
                // wait for the vdom to update
                vdom.wait_for_work().await;

                // get the mutations from the vdom
                 vdom.render_immediate(&mut NoOpMutations);
            }
        })
}
marc2332 commented 2 months ago

Although the leaking is now much smaller, it still seems to leak.

New repro:

#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus::prelude::dioxus_core::NoOpMutations;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    fn app() -> Element {
        let mut count = use_signal(|| 0);

        use_hook(|| spawn(async move {
            loop {
                tokio::time::sleep(std::time::Duration::from_micros(50)).await;
                let val = *count.peek_unchecked();
                if val == 500 {
                    count.set(0);
                } else {
                    count.set(val + 1);
                }
            }
        }));

        rsx! {
            div { 
                color: "red",
                for el in 0..*count.read() {
                    rect {
                        key: "{el}",
                        onclick: move |_| { },
                        label {
                            "{el}"
                        }
                    }
                }
            }
        }
    }

    // create the vdom, the real_dom, and the binding layer between them
    let mut vdom = VirtualDom::new(app);

    vdom.rebuild(&mut NoOpMutations);

    // we need to run the vdom in a async runtime
    tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()?
        .block_on(async {
            loop {
                // wait for the vdom to update
                vdom.wait_for_work().await;

                // get the mutations from the vdom
                 vdom.render_immediate(&mut NoOpMutations);
            }
        })
}