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

Panic from batched update to disposed runtime after action dispatch #2225

Closed
alilee opened this issue Jan 25, 2024 · 8 comments
Closed

Panic from batched update to disposed runtime after action dispatch #2225

alilee opened this issue Jan 25, 2024 · 8 comments
Labels
documentation Improvements or additions to documentation support

Comments

@alilee
Copy link

alilee commented Jan 25, 2024

Describe the bug
Panic during initial server-side rendering when dispatching to server action.

Leptos Dependencies
on tag v0.6.0-beta:

leptos = { path = "../../leptos", features = ["nightly"] }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_meta = { path = "../../meta", features = ["nightly"] }
leptos_router = { path = "../../router", features = ["nightly"] }
server_fn = { path = "../../server_fn", features = ["serde-lite", "rkyv", "multipart" ]}

To Reproduce
Screenshot 2024-01-25 at 11 55 30 am

Note error:

thread '<unnamed>' panicked at /.../leptos/leptos_reactive/src/runtime.rs:1407:6:
tried to run a batched update in a runtime that has been disposed: RuntimeDisposed(RuntimeId(1v1))

Expected behavior
The panic doesn't seem to stop the value() call from resolving on the next (client-side) round-trip which is good. Ideally I'd like it to resolve on the first call though. Also would like to understand if the panic is indicative of undefined behaviour or something we'd fix. Thanks!

@alilee
Copy link
Author

alilee commented Jan 25, 2024

#[server]
async fn sv_fn() -> Result<String, ServerFnError> {
    Ok("[email protected]".to_string())
}

#[component]
pub fn App() -> impl IntoView {
    let action = create_server_action::<SvFn>();
    // action.dispatch(SvFn {});

    // uncommenting the dispatch yields:
    // thread '<unnamed>' panicked at /.../leptos/leptos_reactive/src/runtime.rs:1407:6:
    //    tried to run a batched update in a runtime that has been disposed: RuntimeDisposed(RuntimeId(1v1))

    let res = action.value();

    view! {
        <Suspense>
            <p> "Hello " { move || res() } </p>
        </Suspense>
    }
}

@gbj
Copy link
Collaborator

gbj commented Jan 25, 2024

Dispatching an action during rendering is almost always a sign you should be using a resource instead.

The fact that you're reading the action under Suspense also suggests that this should be a resource: actions and Suspense do not interact in any way.

@gbj gbj closed this as completed Jan 25, 2024
@gbj gbj reopened this Jan 25, 2024
@gbj gbj added the support label Jan 25, 2024
@gbj
Copy link
Collaborator

gbj commented Jan 25, 2024

Didn't mean to click the "Close" button, sorry.

@alilee
Copy link
Author

alilee commented Jan 25, 2024

Ok reading the docs again... thanks very much for the clue. I'm not sure anyone should read too much into my Suspense, because I don't know what I'm doing with that either!

What's driving my thinking here is that I assume the beautiful ActionForm construct should be used everywhere, so in every case I reach for Action<DispatchMutation, Result<Option<LatestServerState>, E>> . My early .dispatch() is an attempt at seeding the initial Some(LatestServerState::...). I will experiment further.

Are you ok with the panic? If so, very happy to close this.

@gbj
Copy link
Collaborator

gbj commented Jan 25, 2024

Yes ActionForm is great. If you need some initial server state, that should be loaded in a resource, which can then be invalidated by dispatching the action (so that the resource refetches).

let action = create_server_action::<SvFn>();
let data = create_resource(move || action.version().get(), |_| /* load the data */);

view! {
        <Suspense>
            <p> "Hello " { move || data() } </p>
        </Suspense>
}

action.input() can be used for things like optimistic updates while waiting for the action to actually complete.

Re: the panic — Your HTTP response is over on the server while the async work you dispatch is still running. There's nothing waiting for it because Suspense just waits for resources, not actions. As a result, when the action finishes and goes looking for the reactive runtime it finds that it's already been disposed.

It's probably possible to have a non-panicking version here, and provide a better warning instead, so I'll leave the issue open just as a note to self.

@gbj gbj added the documentation Improvements or additions to documentation label Jan 25, 2024
@alilee
Copy link
Author

alilee commented Jan 25, 2024

I can see how the server could potentially elide the two reactive objects, but would this result in two round-trips from the client? (ActionForm submit/action-dispatch, then resource fetcher-call). I feel like one call is almost within reach.

@gbj
Copy link
Collaborator

gbj commented Jan 26, 2024

Sure if you want to avoid the round trip you could use the resource only to load the initial state, and then have a derived signal otherwise. Something like this:

let action = create_server_action::<SvFn>();
let value = action.value();
let data = create_resource(|| (), |_| /* load the data */);
let latest = move || value.get().unwrap_or_else(move || data.get());

view! {
        <Suspense>
            <p> "Hello " { move || latest() } </p>
        </Suspense>
}

@alilee
Copy link
Author

alilee commented Jan 26, 2024

Ok - very smart, thank you. Getting my head around the more sophisticated behaviour of the individual tools acting in concert is my next gear-shift.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation support
Projects
None yet
Development

No branches or pull requests

2 participants