Skip to content

Incremental compile OOM when rsx has a loop over a component #3903

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

Closed
TapGhoul opened this issue Mar 24, 2025 · 4 comments
Closed

Incremental compile OOM when rsx has a loop over a component #3903

TapGhoul opened this issue Mar 24, 2025 · 4 comments
Labels
bug Something isn't working

Comments

@TapGhoul
Copy link

TapGhoul commented Mar 24, 2025

Problem

When compiling code with a loop, a server function, and some other bits and bobs, the compiler will continuously eat memory and eventually OOM. This occurs on my machine with 86GB of RAM free - it fully OOMs.

There's a few edits that prevent duplicating this - removing the onclick handler, and removing the loop around the div {} both prevent this occurring, but in the larger app the only way to consistently prevent this was to remove the loop(?)
I do 50 here, but a loop of 0..1 is enough to cause a problem.

Once you do get a hang, the issue can crop up in places that it did not previously occur - you need to cargo clean between each set of tests to ensure that you have a clean slate, otherwise you will get false positives in when things break.

You cannot reproduce this purely with clean builds - this only occurs during incremental rebuilds.

Steps To Reproduce

Note, for all compiles, cargo build --profile server-dev --verbose --features dioxus/server suffices to reproduce - but this can be replicated with the dioxus CLI too. Just you can't as easily kill it and prevent the OOM killer stomping all over your machine with the dioxus CLI, as it sometimes fails to properly kill the processes when cancelling.

Steps to reproduce the behavior:

  • Start with a basic bare-bones dioxus project, and copy the below code into src/main.rs
  • Compile - successful
  • Comment out the line use_effect(move || loaded.set("Yes")); and rebuild - should be successful
  • Uncomment out that line and rebuild - hang and eventual OOM

Expected behavior

Dioxus should build just fine

Screenshots

Image

Environment:

  • Dioxus version: 0.6.3
  • Rust version: 1.85.1 stable
  • OS info: Arch Linux
  • App platform: Web fullstack

Questionnaire

I'm interested in fixing this myself but don't know where to start. I don't know how much time I will have but if I do have time I'm happy to go for it.

Code:
use dioxus::prelude::*;

fn main() {
    dioxus::launch(App);
}

#[server]
pub async fn notify_update(_v: Option<u32>) -> Result<u32, ServerFnError> {
    Ok(0)
}

#[server]
pub async fn trigger_update() -> Result<(), ServerFnError> {
    Ok(())
}

#[component]
fn App() -> Element {
    let initial_state =
        use_server_future(move || async move { notify_update(None).await.unwrap() })?;

    let mut state = use_signal(|| initial_state.unwrap());

    use_future(move || async move {
        loop {
            let old_state = *state.read();
            let new_state = notify_update(Some(old_state)).await.unwrap();
            state.set(new_state);
        }
    });

    let mut loaded = use_signal(|| "No");
    use_effect(move || loaded.set("Yes"));

    rsx! {
        button {
            onclick: async |_| { trigger_update().await.unwrap(); }
        }

        div {
            "{loaded}"
        }
        for _ in 0..50 {
                div {}
        }
    }
}

Full example project: breakshit.zip

@TapGhoul TapGhoul added the bug Something isn't working label Mar 24, 2025
@TapGhoul
Copy link
Author

TapGhoul commented Mar 30, 2025

Further research: on expanding, within the loop, it seems that within the match statement within the loop - the one which expands to

#[cfg(
    debug_assertions
)] let __template_read = match __template_read.as_ref().map(|__template_read| __template_read.as_ref()) {
    Some(Some(__template_read)) => &__template_read,
    _ => __original_template(),
};

in debug mode, it is specifically the .map() call which causes problems. It can be replaced with nothing, and completely dropping the value and instead always calling __original_template() works - it's only if you have __template_read.as_ref().map(|_| ()).

Specifically:

Broken:

#[cfg(
    debug_assertions
)] let __template_read = match __template_read.as_ref().map(|__template_read| ()) {
    Some(Some(__template_read)) => &__template_read,
    _ => __original_template(),
};
let __template_read = __original_template();

Works:

#[cfg(
    debug_assertions
)] let __template_read = match __template_read.as_ref() {
    Some(Some(__template_read)) => &__template_read,
    _ => __original_template(),
};
let __template_read = __original_template();

I wonder if this is a rustc bug.

Macro expansion - the relevant section is the template match within the 0..50's map() call
#[component]
fn App() -> Element {
    let initial_state =
        use_server_future(move || async move { notify_update(None).await.unwrap() })?;

    let mut state = use_signal(|| initial_state.unwrap());

    use_future(move || async move {
        loop {
            let old_state = *state.read();
            let new_state = notify_update(Some(old_state)).await.unwrap();
            state.set(new_state);
        }
    });

    let mut loaded = use_signal(|| "No");
    use_effect(move || loaded.set("Yes"));

    dioxus_core::Element::Ok({
        #[cfg(debug_assertions)]
        fn __original_template() -> &'static dioxus_core::internal::HotReloadedTemplate {
            static __ORIGINAL_TEMPLATE: ::std::sync::OnceLock<dioxus_core::internal::HotReloadedTemplate> = ::std::sync::OnceLock::new();
            if __ORIGINAL_TEMPLATE.get().is_none() { _ = __ORIGINAL_TEMPLATE.set(dioxus_core::internal::HotReloadedTemplate::new(None, vec![dioxus_core::internal::HotReloadDynamicNode::Dynamic(0usize), dioxus_core::internal::HotReloadDynamicNode::Dynamic(1usize)], vec![dioxus_core::internal::HotReloadDynamicAttribute::Dynamic(0usize)], vec![], __TEMPLATE_ROOTS)); }
            __ORIGINAL_TEMPLATE.get().unwrap()
        }
        #[cfg(
            debug_assertions
        )] let __template_read = {
            static __NORMALIZED_FILE: &'static str = {
                const PATH: &str = dioxus_core::const_format::str_replace!(file ! (), "\\\\" , "/" );
                dioxus_core::const_format::str_replace!(PATH , '\\' , "/" )
            };
            static __TEMPLATE: GlobalSignal<Option<dioxus_core::internal::HotReloadedTemplate>> = GlobalSignal::with_location(|| None::<dioxus_core::internal::HotReloadedTemplate>, __NORMALIZED_FILE, line!(), column!(), 0usize);
            dioxus_core::Runtime::current().ok().map(|_| __TEMPLATE.read())
        };
        #[cfg(
            debug_assertions
        )] let __template_read = match __template_read.as_ref().map(|__template_read| __template_read.as_ref()) {
            Some(Some(__template_read)) => &__template_read,
            _ => __original_template(),
        };
        #[cfg(
            debug_assertions
        )] let mut __dynamic_literal_pool = dioxus_core::internal::DynamicLiteralPool::new(vec![format!("{0:}", loaded).to_string()]);
        let __dynamic_nodes: [dioxus_core::DynamicNode; 2usize] = [dioxus_core::DynamicNode::Text(dioxus_core::VText::new(::std::format!("{loaded}"
        ))), {
            let ___nodes = (0..50).into_iter().map(|_| {
                dioxus_core::Element::Ok({
                    #[cfg(debug_assertions)]
                    fn __original_template() -> &'static dioxus_core::internal::HotReloadedTemplate {
                        static __ORIGINAL_TEMPLATE: ::std::sync::OnceLock<dioxus_core::internal::HotReloadedTemplate> = ::std::sync::OnceLock::new();
                        if __ORIGINAL_TEMPLATE.get().is_none() { _ = __ORIGINAL_TEMPLATE.set(dioxus_core::internal::HotReloadedTemplate::new(None, vec![], vec![], vec![], __TEMPLATE_ROOTS)); }
                        __ORIGINAL_TEMPLATE.get().unwrap()
                    }
                    #[cfg(
                        debug_assertions
                    )] let __template_read = {
                        static __NORMALIZED_FILE: &'static str = {
                            const PATH: &str = dioxus_core::const_format::str_replace!(file ! (), "\\\\" , "/" );
                            dioxus_core::const_format::str_replace!(PATH , '\\' , "/" )
                        };
                        static __TEMPLATE: GlobalSignal<Option<dioxus_core::internal::HotReloadedTemplate>> = GlobalSignal::with_location(|| None::<dioxus_core::internal::HotReloadedTemplate>, __NORMALIZED_FILE, line!(), column!(), 1usize);
                        dioxus_core::Runtime::current().ok().map(|_| __TEMPLATE.read())
                    };
                    #[cfg(
                        debug_assertions
                    )] let __template_read = match __template_read.as_ref().map(|__template_read| __template_read.as_ref()) {
                        Some(Some(__template_read)) => &__template_read,
                        _ => __original_template(),
                    };
                    #[cfg(
                        debug_assertions
                    )] let mut __dynamic_literal_pool = dioxus_core::internal::DynamicLiteralPool::new(vec![]);
                    let __dynamic_nodes: [dioxus_core::DynamicNode; 0usize] = [];
                    let __dynamic_attributes: [Box<[dioxus_core::Attribute]>; 0usize] = [];   #[doc(
                        hidden
                    )]
                    static __TEMPLATE_ROOTS: &[dioxus_core::TemplateNode] = &[{ dioxus_core::TemplateNode::Element { tag: dioxus_elements::elements::div::TAG_NAME, namespace: dioxus_elements::div::NAME_SPACE, attrs: &[], children: &[] } }];
                    #[cfg(
                        debug_assertions
                    )] {
                        let mut __dynamic_value_pool = dioxus_core::internal::DynamicValuePool::new(Vec::from(__dynamic_nodes), Vec::from(__dynamic_attributes), __dynamic_literal_pool);
                        __dynamic_value_pool.render_with(__template_read)
                    }
                    #[cfg(not(debug_assertions))] {
                        #[doc(hidden)]
                        static ___TEMPLATE: dioxus_core::Template = dioxus_core::Template { roots: __TEMPLATE_ROOTS, node_paths: &[], attr_paths: &[] };
                        #[allow(
                            clippy::let_and_return
                        )] let __vnodes = dioxus_core::VNode::new(None, ___TEMPLATE, Box::new(__dynamic_nodes), Box::new(__dynamic_attributes));
                        __vnodes
                    }
                })
            }).into_dyn_node();
            ___nodes
        }];
        let __dynamic_attributes: [Box<[dioxus_core::Attribute]>; 1usize] = [Box::new([{
            dioxus_elements::events::onclick(async |_| { trigger_update().await.unwrap(); }
            )
        }])];   #[doc(hidden)]
        static __TEMPLATE_ROOTS: &[dioxus_core::TemplateNode] = &[{ dioxus_core::TemplateNode::Element { tag: dioxus_elements::elements::button::TAG_NAME, namespace: dioxus_elements::button::NAME_SPACE, attrs: &[dioxus_core::TemplateAttribute::Dynamic { id: 0usize }], children: &[] } }, { dioxus_core::TemplateNode::Element { tag: dioxus_elements::elements::div::TAG_NAME, namespace: dioxus_elements::div::NAME_SPACE, attrs: &[], children: &[dioxus_core::TemplateNode::Dynamic { id: 0usize }] } }, dioxus_core::TemplateNode::Dynamic { id: 1usize }];
        #[cfg(
            debug_assertions
        )] {
            let mut __dynamic_value_pool = dioxus_core::internal::DynamicValuePool::new(Vec::from(__dynamic_nodes), Vec::from(__dynamic_attributes), __dynamic_literal_pool);
            __dynamic_value_pool.render_with(__template_read)
        }
        #[cfg(not(debug_assertions))] {
            #[doc(hidden)]
            static ___TEMPLATE: dioxus_core::Template = dioxus_core::Template { roots: __TEMPLATE_ROOTS, node_paths: &[&[1u8, 0u8], &[2u8]], attr_paths: &[&[0u8]] };
            #[allow(
                clippy::let_and_return
            )] let __vnodes = dioxus_core::VNode::new(None, ___TEMPLATE, Box::new(__dynamic_nodes), Box::new(__dynamic_attributes));
            __vnodes
        }
    })
}

@TapGhoul
Copy link
Author

Quick update here: this is likely a rustc bug. See rust-lang/rust#139142

@TapGhoul
Copy link
Author

TapGhoul commented Mar 31, 2025

The fix has been merged into master: rust-lang/rust@b17948a
This can either be closed now, or closed when this is available in an upcoming rust release. Either way, good news! This should be in the next nightly, I'll give it a test tomorrow.

Related rollup: rust-lang/rust#139169

@TapGhoul
Copy link
Author

TapGhoul commented Apr 1, 2025

Confirmed - works on latest nightly (as of a6c604d1b 2025-03-26), so rust 1.88 should be where this works nicely on stable. For reference, we are at 1.85.1, so likely in around 5-6 weeks.

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

No branches or pull requests

2 participants