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

Fix WASI builds trying to use JS for spawn_local #2066

Merged
merged 1 commit into from
Dec 1, 2023

Conversation

itowlson
Copy link
Contributor

Fixes #2056.

This is a change to the original fix proposed in the issue. After talking to colleagues working on upstream WASI, the recommendation was to poll the future only once, on the basis that if the future were pending at that point, futures::executor::block_on would block forever, and it was better to panic with a specific message than to do that.

The caveat here is that spawn_local is a very low level function. My fix works for the case I ran into, which was create_resource. But if spawn_local is used elsewhere for a future that is truly long-running and non-blocking (such as a WASI Preview 2 outbound HTTP request) then this will fail. Hopefully in that case the message will at least point us to the fix that's needed. I don't think this is an issue as yet, but I don't know how spawn_local really gets used in SSR, so advice is welcome!

The risk on top of that caveat is that, as far as I know, the Leptos test suite doesn't currently build for WASI, and I haven't tried to tackle that as part of this PR. So this new case is extremely Works On My Machine. I am not sure how to move forward with testing on WASI I'm afraid.

@gbj
Copy link
Collaborator

gbj commented Nov 24, 2023

Wait, how does this work for the create_resource case? Doesn't it just panic if you have a resource that actually does any async work? If the Future is guaranteed to always already be Ready then there's no real point in using a resource, is there?

EDIT: I guess maybe if you're using it just to serialize data from the server which is synchronous? Or a server functions that is synchronous? Huh. It seems somewhat odd to me to panic at runtime if someone tries to use actual async, but I guess it's better than nothing.

@itowlson
Copy link
Contributor Author

@gbj Thanks for flagging this. I did some more testing and the news is not encouraging.

My original use case was async on the client but synchronous on the server. That worked fine (as did my earlier idea to use futures::executor::block.

However, when I tried a resource getter that was async on the server too - in Spin's case, outbound HTTP - it did indeed panic as you feared. And when I went back to futures::executor::block, that went into a busy-loop as my colleague had warned it would.

So... I don't actually know how to solve this. Following @diversable's comment in the issue, I will try running the Spin request handler inside a Tokio single-threaded runtime, which isn't a very lovely developer experience, but if it works then I can see if there is a way to make it less intrusive to app developers.

Thanks for your caution and for pointing me in the right direction to confirm the problem!

@diversable
Copy link
Contributor

Thanks for your caution and for pointing me in the right direction to confirm the problem!

Glad to see that you & the folks at Fermyon are working on adding this functionality - thanks, @itowlson !

@itowlson
Copy link
Contributor Author

Okay, with a bare Tokio runtime, and reusing the standard ssr implementation of spawn_local, it crashed with a message about needing to be in a LocalSet. When I tried adding a LocalSet, I got a "condvar wait not supported" somewhere deep inside WASI. I'll appeal to the Fermyon folks working on the Wasm internals for ideas, but in the meantime, @diversable, you mentioned you were working on an Axum WASI template - have you run across the spawn_local problem and if so how did you tackle it?

@diversable
Copy link
Contributor

diversable commented Nov 27, 2023

@diversable, you mentioned you were working on an Axum WASI template - have you run across the spawn_local problem and if so how did you tackle it?

I have indeed run across the spawn_local problem, and I'm still working on trying to sort it out myself..

fyi: I think full Axum support is going to have to wait until Hyper.rs merges their wasi support (I'm hesitant to provide end users with a dep on a forked hyper crate); in the meantime, I think you're on track with using the single-threaded Tokio rt, even though the dev exp. isn't smooth.

if you want to share your current implementation, I can take a look and get back to you with more ideas about how to proceed re: spawn_local issues... ?

I think your approach is going to get Leptos to wasi support quicker than mine :)

@itowlson
Copy link
Contributor Author

Thanks!

What I tried was this in the Spin app:

#[http_component]
async fn handle(req: IncomingRequest, resp_out: ResponseOutparam) {
    let rt = tokio::runtime::Builder::new_current_thread().enable_time().build().unwrap();
    let local = tokio::task::LocalSet::new();
    rt.block_on(local.run_until(
        async move {
            handle_what_could_go_wrong(req, resp_out).await
        })
    );
}

with the rest of it as in https://github.com/itowlson/spin-leptos-investigation/tree/counter-isomorphic (except for the http_component being moved to the Tokio wrapper). (I can push this at some point, just trying something else at the moment!)

Then in leptos_reactive::spawn_local (

pub fn spawn_local<F>(fut: F)
) I added:

        if #[cfg(all(target_arch = "wasm32", target_os = "wasi"))] {
            use crate::Runtime;

            let runtime = Runtime::current();
            tokio::task::spawn_local(async move {
                crate::TASK_RUNTIME.scope(Some(runtime), fut).await
            });
        }

@itowlson itowlson force-pushed the spawn-local-wasi-fix branch from 2fc4de6 to 859d599 Compare November 28, 2023 03:52
@itowlson
Copy link
Contributor Author

@gbj Well, I don't know if this is going to meet with your approval, but after the various dead ends discussed above, the only solution I have found is, I'm afraid, Spin-specific. It introduces a Spin SDK dependency into Leptos, which feels nasty outside of the integration crates; but it is optional and is brought in only via a spin feature gate. If there's a better way to manage this, do let me know!

After discussions with the Spin team, the longer goal is to move towards using a Tokio implementation inside the Spin SDK, instead of the hand-rolled executor. This would be beneficial for Spin too, of course, but from the Leptos point of view it would hopefully remove the need to special-case (and would remove that ugly dependency). I don't have a time frame for this, though, and can't make any promises.

In the meantime, I've tested this with resources that are both sync and async on the server, and it has worked in both cases. Given the lack of automated coverage, if there's any other testing you'd like me to do, let me know.

The apparently unrelated changes to the string concatenation operation in leptos_router is because of a weird quirk in smartstring that makes String + &String not work anywhere in an application that uses smartstring (bodil/smartstring#7). This affects Spin builds because the Spin router uses routefinder which uses smartstring.

@gbj
Copy link
Collaborator

gbj commented Nov 29, 2023

Cool! It looks like the String + &String thing is very limited, doesn't cause any breaking changes, and is very easy to work around, so fine by me. I am totally fine introducing a Spin-specific feature flag here, given that it's an optional dependency.

Thanks so much for your work on this.

@itowlson itowlson force-pushed the spawn-local-wasi-fix branch from 859d599 to 9bfc3dc Compare November 29, 2023 19:29
@itowlson
Copy link
Contributor Author

Thanks! I pushed an update to fix the CI error if you wouldn't mind re-approving to run the workflows.

@itowlson
Copy link
Contributor Author

I think the remaining CI error is a flake installing jq.

@gbj gbj merged commit 9bbd881 into leptos-rs:main Dec 1, 2023
60 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

create_resource panics when server is compiled to wasm32-wasi
3 participants