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

[0.7] Storing a new item in the arena while holding a shared reference to an arena-allocated item fails #2968

Open
winteler opened this issue Sep 12, 2024 · 2 comments
Labels
bug Something isn't working
Milestone

Comments

@winteler
Copy link

Hello,

I've tried to migrate to 0.7 and I think I've stumbled upon a bug when using a generic component to read resources. The code builds without problem but a Uncaught RuntimeError: unreachable executed occurs when rendering the Suspense.

Leptos Dependencies

leptos = { version = "0.7.0-beta5", features = ["nightly"] }
leptos_axum = { version = "0.7.0-beta5", optional = true }
leptos_meta = { version = "0.7.0-beta5" }
leptos_router = { version = "0.7.0-beta5", features = ["nightly"] }

To Reproduce
Here is a minimal example to reproduce the issue. The bug depends on the children of , in this example it only occurs when let signal = RwSignal::new(0); is present in . In my code, I also got issues when an is in the children.

use leptos::prelude::*;
use leptos_meta::{provide_meta_context, MetaTags, Stylesheet, Title};
use leptos_router::{
    components::{Route, Router, Routes},
    StaticSegment,
};

pub fn shell(options: LeptosOptions) -> impl IntoView {
    view! {
        <!DOCTYPE html>
        <html lang="en">
            <head>
                <meta charset="utf-8"/>
                <meta name="viewport" content="width=device-width, initial-scale=1"/>
                <AutoReload options=options.clone() />
                <HydrationScripts options/>
                <MetaTags/>
            </head>
            <body>
                <App/>
            </body>
        </html>
    }
}

#[server]
pub async fn string_data() -> Result<String, ServerFnError> {
    Ok(String::from("hello world"))
}

#[component]
pub fn App() -> impl IntoView {
    // Provides context that manages stylesheets, titles, meta tags, etc.
    provide_meta_context();

    view! {
        // injects a stylesheet into the document <head>
        // id=leptos means cargo-leptos will hot-reload this stylesheet
        <Stylesheet id="leptos" href="/pkg/reproduce-leptos-0-7-beta5.css"/>

        // sets the document title
        <Title text="Welcome to Leptos"/>

        // content for this welcome page
        <Router>
            <main>
                <Routes fallback=|| "Page not found.".into_view()>
                    <Route path=StaticSegment("") view=HomePage/>
                    <Route path=StaticSegment("test") view=Test/>
                </Routes>
            </main>
        </Router>
    }
}

/// Renders the home page of your application.
#[component]
fn HomePage() -> impl IntoView {
    view! {
        <h1>"Welcome to Leptos!"</h1>
        <a href="/test">"Test page"</a>
    }
}

/// Renders the test page.
#[component]
fn Test() -> impl IntoView {
    // Creates a reactive value to update the button
    let count = RwSignal::new(0);
    let on_click = move |_| *count.write() += 1;
    let resource = Resource::new(
        move || (),
        move |_| string_data()
    );

    view! {
        <h1>"Welcome to Leptos!"</h1>
        <button on:click=on_click>"Click Me: " {count}</button>
        <SuspenseUnpack resource=resource let:value>
            <ValueDisplay value=value.clone()/>
        </SuspenseUnpack>
    }
}

/// Renders the resource value
#[component]
fn ValueDisplay(
    value: String,
) -> impl IntoView {
    let signal = RwSignal::new(0);
    view! {
        <div>{value}</div>
    }
}

#[component]
pub fn SuspenseUnpack<
    T: Clone + Send + Sync + 'static,
    V: IntoView + 'static,
    F: Fn(&T) -> V + Clone + Send + Sync + 'static,
>(
    resource: Resource<Result<T, ServerFnError>>,
    children: F,
) -> impl IntoView {
    let children = StoredValue::new(children);

    view! {
        <Suspense>
        {
            move || Suspend::new(async move {
                match &resource.await {
                    Ok(value) => Either::Left(children.with_value(|children| children(value))),
                    Err(_e) => Either::Right(view! { <div>"Error"</div> }),
                }
            })
        }
        </Suspense>
    }
}

Let me know if I can help in any way and thanks a lot for your work :)

@gbj
Copy link
Collaborator

gbj commented Sep 13, 2024

The issue is creation a new StoredValue of some kind (RwSignal in this case) while holding the lock on another.

Min reproduction:

#[component]
fn HomePage() -> impl IntoView {
    let stored = StoredValue::new("outer");
    stored.with_value(|outer| {
        let inner = StoredValue::new("inner");
        inner.get_value()
    })
}

In the case of your example this can be fixed pretty trivially:

                    Ok(value) => Either::Left(children.get_value()(value)),

These kinds of nested changes should be allowed from a DX perspective, it's just the new arena implementation which accidentally assumes they don't. Allowing them to work as intended probably involves adding an extra layer of indirection here by adding an additional Arc<Mutex<_>> around a StoredValue so that we can clone the StoredValue out of the arena, then work with it, rather than just taking a lock on the whole arena.

Given that I may want to refactor the way that signals etc. use StoredValue so that they are not paying that same cost.

Thanks for pointing this out.

@gbj gbj added the bug Something isn't working label Sep 13, 2024
@gbj gbj added this to the 0.7 milestone Sep 13, 2024
@winteler
Copy link
Author

Thanks a lot for the quick reply, the workaround indeed fixed the issue 👍

@gbj gbj changed the title [0.7] Runtime error when reading resource in generic component [0.7] Storing a new item in the arena while holding a shared reference to an arena-allocated item fails Sep 17, 2024
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