diff --git a/Cargo.lock b/Cargo.lock index 1a40eac30f..ced4f882d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3641,6 +3641,7 @@ dependencies = [ "serde", "slab", "slotmap", + "sysinfo", "tokio", "tracing", "tracing-fluent-assertions", @@ -8253,6 +8254,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -13436,6 +13446,20 @@ dependencies = [ "walkdir", ] +[[package]] +name = "sysinfo" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -15114,8 +15138,8 @@ dependencies = [ "webview2-com-sys", "windows 0.58.0", "windows-core 0.58.0", - "windows-implement", - "windows-interface", + "windows-implement 0.58.0", + "windows-interface 0.58.0", ] [[package]] @@ -15345,6 +15369,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.58.0" @@ -15364,19 +15398,42 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", "windows-strings 0.1.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "windows-implement" version = "0.58.0" @@ -15388,6 +15445,17 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "windows-interface" version = "0.58.0" @@ -15405,7 +15473,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-strings 0.1.0", "windows-targets 0.52.6", ] @@ -15416,11 +15484,20 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bafa604f2104cf5ae2cc2db1dee84b7e6a5d11b05f737b60def0ffdc398cbc0a" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-strings 0.2.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.2.0" @@ -15436,7 +15513,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-targets 0.52.6", ] diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 0271e61f70..94b428e6ee 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -34,6 +34,7 @@ reqwest = { workspace = true } tracing-subscriber = { workspace = true, default-features = true } tracing-fluent-assertions = "0.3.0" pretty_assertions = "1.3.0" +sysinfo = "0.33.1" [dev-dependencies.web-sys] version = "0.3.56" diff --git a/packages/core/tests/memory_leak.rs b/packages/core/tests/memory_leak.rs new file mode 100644 index 0000000000..c3311921f4 --- /dev/null +++ b/packages/core/tests/memory_leak.rs @@ -0,0 +1,92 @@ +#![allow(non_snake_case)] +use dioxus::prelude::dioxus_core::NoOpMutations; +use dioxus::prelude::*; +use sysinfo::{ProcessRefreshKind, RefreshKind, System}; + +// Regression test for https://github.com/DioxusLabs/dioxus/issues/3421 +#[tokio::test] +async fn test_for_memory_leaks() { + fn app() -> Element { + let mut count = use_signal(|| 0); + + use_hook(|| { + spawn(async move { + loop { + tokio::time::sleep(std::time::Duration::from_nanos(1)).await; + let val = *count.peek_unchecked(); + if val == 70 { + count.set(0); + } else { + count.set(val + 1); + } + } + }) + }); + + rsx! { + for el in 0..*count.read() { + div { + key: "{el}", + div { + onclick: move |_| { println!("click"); }, + } + AcceptsEventHandlerAndReadOnlySignal { + event_handler: move |_| { println!("click"); }, + signal: el, + } + } + } + } + } + + // Event handlers and ReadOnlySignals have extra logic on component boundaries that has caused memory leaks + // in the past + #[component] + fn AcceptsEventHandlerAndReadOnlySignal( + event_handler: EventHandler, + signal: ReadOnlySignal, + ) -> Element { + rsx! { + div { + onclick: event_handler, + "{signal}" + } + } + } + + // create the vdom, the real_dom, and the binding layer between them + let mut vdom = VirtualDom::new(app); + + vdom.rebuild(&mut NoOpMutations); + + let pid = sysinfo::get_current_pid().expect("failed to get PID"); + + let refresh = + RefreshKind::nothing().with_processes(ProcessRefreshKind::nothing().with_memory()); + let mut system = System::new_with_specifics(refresh); + + let mut get_memory_usage = || { + system.refresh_specifics(refresh); + let this_process = system.process(pid).expect("failed to get process"); + this_process.memory() + }; + + let initial_memory_usage = get_memory_usage(); + + // we need to run the vdom in a async runtime + for i in 0..=10000 { + // wait for the vdom to update + vdom.wait_for_work().await; + + // get the mutations from the vdom + vdom.render_immediate(&mut NoOpMutations); + + if i % 1000 == 0 { + let new_memory_usage = get_memory_usage(); + println!("iteration: {} memory usage: {}", i, new_memory_usage); + + // Memory usage might increase as arenas fill up, but it shouldn't double from the initial render + assert!(new_memory_usage < initial_memory_usage * 2); + } + } +} diff --git a/packages/html/src/events/mod.rs b/packages/html/src/events/mod.rs index 4cecad3a1a..de290d3467 100644 --- a/packages/html/src/events/mod.rs +++ b/packages/html/src/events/mod.rs @@ -26,10 +26,16 @@ macro_rules! impl_event { )? #[inline] pub fn $name<__Marker>(mut _f: impl ::dioxus_core::prelude::SuperInto<::dioxus_core::prelude::EventHandler<::dioxus_core::Event<$data>>, __Marker>) -> ::dioxus_core::Attribute { - let event_handler = _f.super_into(); + // super into will make a closure that is owned by the current owner (either the child component or the parent component). + // We can't change that behavior in a minor version because it would cause issues with Components that accept event handlers. + // Instead we run super into with an owner that is moved into the listener closure so it will be dropped when the closure is dropped. + let owner = <::generational_box::UnsyncStorage as ::generational_box::AnyStorage>::owner(); + let event_handler = ::dioxus_core::prelude::with_owner(owner.clone(), || _f.super_into()); ::dioxus_core::Attribute::new( impl_event!(@name $name $($js_name)?), ::dioxus_core::AttributeValue::listener(move |e: ::dioxus_core::Event| { + // Force the owner to be moved into the event handler + _ = &owner; event_handler.call(e.map(|e| e.into())); }), None,