Stream: wasi

Topic: Synchronous wasi runtime?


view this post on Zulip Karel Hrkal (kajacx) (Jul 22 2023 at 08:13):

Hello, I have an example of asynchronous wasi components working in this example.

However, I am not using the "asynchrony" in the improted host function, so it just adds a bunch on needless overhead for me. Is is possible to run wasi components synchronously instead?

I tried removing all the "async" code, but it then throws this error:
thread 'main' panicked at 'cannot use func_wrap_async without enabling async support in the config'

I think the problem is this line that enables async support regardless of what was in the original bindgen file.

view this post on Zulip Ramon Klass (Jul 22 2023 at 10:50):

I think I read in the code yesterday that right now only the async host for wasi is existing, so for not-yet-implemented reasons if you use wasi you need async until someone adds sync

view this post on Zulip Pat Hickey (Jul 24 2023 at 03:13):

correct, the wasmtime-wasi::preview2 crate requires async at this time. when https://github.com/bytecodealliance/wasmtime/pull/6556 lands (hopefully tomorrow - its all set to go, but something unrelated to the PR is broken in CI right now) this will no longer be the case.

When we landed the WASI Preview 2 support in wasmtime-wasi we knew the streams/poll/scheduler implementation was going to get torn out and rewritten: see task 4 in #6370. This is that rewrite. Most...

view this post on Zulip Pat Hickey (Jul 24 2023 at 03:14):

wasmtime-wasi::preview2 will still use tokio under the hood, so tokio will be a transitive dep of your implementation, but the wasmtime interfaces themselves wont use async, so you will be able to call wasmtime's sync apis instead of the async ones (e.g. instantiate vs instantiate_async).

view this post on Zulip Karel Hrkal (kajacx) (Jul 27 2023 at 13:20):

instantiate_async is not a big deal, but making the exported methods asynchronous is just horrible to use. I tried to use wasmtime from git to see the new PR in action, but I get an error when loading a component:

thread 'main' panicked at 'create component: failed to parse WebAssembly module

Caused by:
    record type must have at least one field (at offset 0xa5)', src\main.rs:142:71

Full code here. I have latest cargo component installed with cargo install --git https://github.com/bytecodealliance/cargo-component -f.

view this post on Zulip Ramon Klass (Jul 27 2023 at 13:23):

it's the same the other way round too btw, if the rest of your program is async, having one library that is sync is causing similar pain points all around

view this post on Zulip Karel Hrkal (kajacx) (Jul 27 2023 at 14:09):

That's why it should be a user choice, either:

Wasi should then export two sets of traits for imported wasi behaviour (for example IO streams) - one synchronous, and one asynchronous. The context builder should then take the asynchronous trait in asynch mode and vise versa.

view this post on Zulip Lann Martin (Jul 27 2023 at 14:18):

making the exported methods asynchronous is just horrible to use

Could you expand on what you mean by this?

view this post on Zulip Ramon Klass (Jul 27 2023 at 14:45):

I think their point was that not having the choice is not a good user experience if the upstream choice does not fit in your software, fwiw everything else in kajacx's repo is synchronous code so right now they would need a sync to async bridge which is not trivial

honestly, just wait a week or two, the sync interface is almost ready, if you don't want to help finishing it please wait

view this post on Zulip Pat Hickey (Jul 27 2023 at 15:46):

its already landed in main

view this post on Zulip Pat Hickey (Jul 27 2023 at 15:47):

test-programs now shows the component wasi-tests running in both synchronous and tokio

view this post on Zulip Karel Hrkal (kajacx) (Jul 27 2023 at 15:55):

Pat Hickey said:

its already landed in main

I tried to use a git dependency, but it threw the "record type must have at least one field" error.

Lann Martin said:

making the exported methods asynchronous is just horrible to use

Could you expand on what you mean by this?

Pretty much what @Ramon Klass said. I am making a video game in Bevy, and I need to call the exported function on an instance every frame. I do not know if Bevy supports asynchronous update function (it probably doesn't), so I would need to start a new runtime and block on the future. Every frame. And for code that most likely doesn't actually await anything. That's why it's "horrible".

Ramon Klass said:

I think their point was that not having the choice is not a good user experience if the upstream choice does not fit in your software, fwiw everything else in kajacx's repo is synchronous code so right now they would need a sync to async bridge which is not trivial

honestly, just wait a week or two, the sync interface is almost ready, if you don't want to help finishing it please wait

I would love to help on it. Who do I contact?

view this post on Zulip Karel Hrkal (kajacx) (Jul 27 2023 at 16:01):

To re-iterate, what I want is an synchronous version of the OutputStream trait for example. Same signature, but not asynchronous, it would just do a blocking write of the bytes. then I should be able to specify a custom implementation of this synchronous trait in the WasiCtxBuilder, like in this example.

view this post on Zulip Pat Hickey (Jul 27 2023 at 17:58):

a sync version of HostOutputStream and HostPollable would be a signficant redesign of wasmtime-wasi

view this post on Zulip Pat Hickey (Jul 27 2023 at 17:59):

i dont expect its one we will be working on anytime soon. if you need such a thing, you probably need to fork wasmtime-wasi to make that design change, and maintain a fork

view this post on Zulip Pat Hickey (Jul 27 2023 at 18:01):

my expectation is it would be pretty challenging to implement wasi-http and other proposals with a synchronous stream interface. for one, i am not aware of any production-quality http implementations in synchronous rust

view this post on Zulip Pat Hickey (Jul 27 2023 at 18:03):

i did some investigating on whether we could implement wasmtime-wasi without a hard dependency on tokio, and my conclusion was that it was quite challenging, and would basically amount to reinventing half or more of tokio

view this post on Zulip Pat Hickey (Jul 27 2023 at 18:04):

alex (who was the original author of tokio) concurred

view this post on Zulip Pat Hickey (Jul 27 2023 at 18:05):

this will only get harder as we evolve to support preview 3 streams/futures as well

view this post on Zulip Karel Hrkal (kajacx) (Jul 27 2023 at 18:24):

Pat Hickey said:

my expectation is it would be pretty challenging to implement wasi-http and other proposals with a synchronous stream interface. for one, i am not aware of any production-quality http implementations in synchronous rust

Ok, I am aware that http is the biggest problem when it comes to "sync wasi", but think about what the alternative is ... every exported function in now async, because the guest could make a http request and await it ...

I think a much better alternative would be to give the users the option to turn off http support if they want an synchronous interface. In fact, this problem will actually become a lot better with the preview3 proposal, which I understand will add async fn support to wit bindgen.

In this new proposal, guest exported function can be marked as async in the wit protocol, that means you can call the http imported wasi functions, which are async as well. But if a guest exports a synchronous function, then they cannot call the async imported functions. And in the host, you will correctly get an async / sync function depending on what is in the wit file.

view this post on Zulip Pat Hickey (Jul 27 2023 at 18:29):

ok. i dont think we can achieve all of those goals in the current codebase with the current constraints on time and expertise and labor. we've found so far that production wasmtime users are all using tokio to hook wasmtime up to other systems, cooperatively schedule multiple stores by using epoch or fuel based async yield, and so on.

view this post on Zulip Pat Hickey (Jul 27 2023 at 18:29):

if you have a radically different set of needs than that, then you probably need a different wasi implementation.

view this post on Zulip Karel Hrkal (kajacx) (Jul 27 2023 at 18:53):

Pat Hickey said:

if you have a radically different set of needs than that, then you probably need a different wasi implementation.

Wasi implementation is not the problem, I could implement the wasi imports with synchronous methods and just return Err for http stuff and not use it in the guest, the problem is that wasmtime forces me to use async. What I would need then is a completely different wasm runtime, not just a custom wasi implementaion.

But I really like the wasmtime is doing with the component model, and so far it is working well with wit worlds and everything. Too bad that adding wasi support forces the exported methods to be asynchronous.

view this post on Zulip Ramon Klass (Jul 27 2023 at 18:54):

wasi implementation == wasi runtime

view this post on Zulip Ramon Klass (Jul 27 2023 at 18:56):

the component model will be implemented by many wasi runtimes in the future, wasmtime just happens to be the reference implementation where people try out new things, but the whole component stuff is indeed a standard, not something specific to wasmtime

view this post on Zulip Karel Hrkal (kajacx) (Jul 27 2023 at 18:58):

Are there any other runtimes in Rust that support the component model?

view this post on Zulip Karel Hrkal (kajacx) (Jul 28 2023 at 08:14):

Ramon Klass said:

the sync interface is almost ready

Ok, maybe I am misunderstanding something. By "sync interface", I assumed the traits that are now async would have a sync option. If that's not what it means, then what will this new "sync interface" look like?

And more importantly, will exported functions be synchronous when using this new "sync interface"?

view this post on Zulip Ramon Klass (Jul 28 2023 at 11:39):

I misunderstood your goals. As much as possible that can be sync is now available in sync and Pat clarified that they can't feasibly do any more than that at this early stage

view this post on Zulip Pat Hickey (Jul 28 2023 at 17:09):

by "sync interface" what i meant was, you can use wasmtime_wasi::preview2 from wasmtime without Config::async_support enabled, by using the command::sync::add_to_linker: https://github.com/bytecodealliance/wasmtime/blob/pch/resource_lifetimes/crates/wasi/src/preview2/command.rs#L73

view this post on Zulip Pat Hickey (Jul 28 2023 at 17:10):

the test suite for it, here, is equivalent to the preview2-components.rs suite except that it does not have Config::async_support enabled, and is executed from plain old sync rust #[test] instead of a #[tokio::test] https://github.com/bytecodealliance/wasmtime/blob/pch/resource_lifetimes/crates/test-programs/tests/wasi-preview2-components-sync.rs

view this post on Zulip Pat Hickey (Jul 28 2023 at 17:16):

this is just the interface to wasmtime that is sync - under the hood, the sync implementation will create its own tokio runtime, and then use that to execute the async implementation https://github.com/bytecodealliance/wasmtime/blob/pch/resource_lifetimes/crates/wasi/src/preview2/mod.rs#L151

view this post on Zulip Karel Hrkal (kajacx) (Jul 29 2023 at 08:21):

Pat Hickey said:

under the hood, the sync implementation will create its own tokio runtime, and then use that to execute the async implementation

Ok, that sound a little bit scary, but it will work, which is what's important. I am calling the exported function in the game engine's update loop (I am defining unit behavior using wasi components), so if a component will make a (from it's perspective) blocking http call, it will freeze the entire game for the duration of the request.

I guess that is not something that can be "solved", since that is an inherit (and not accidental) problem. The best solution in this case would be to add a custom http client impl that just returns an Err immediately. Another option would be for the guest to make the http call in another "thread", but that's using wasi threads, which out of scope for this thread.

view this post on Zulip Christof Petig (Jul 29 2023 at 09:12):

I am not an expert on bevy but looks like that https://docs.rs/bevy/latest/bevy/tasks/struct.AsyncComputeTaskPool.html offers ways to integrate Futures into bevy. Another option is block_on, e.g. https://www.appsloveworld.com/rust/11/how-to-make-async-system-function-in-the-bevy-game-engine?utm_content=cmp-true .
As a side note I would expect it to be more easy to expand bevy to support async than to remove executor dependency (tokio or any other) from an async designed runtime, especially given that non-async APIs are a very difficult when porting any program to a browser environment. And IIRC bevy supports browser targets already quite well.
https://docs.rs/bevy_eventwork/latest/bevy_eventwork/ also seems to combine async networking with bevy.

view this post on Zulip Christof Petig (Jul 29 2023 at 09:19):

Update: Internally bevy uses smol's executor https://github.com/bevyengine/bevy/discussions/9287#discussioncomment-6563573 - so I guess using async inside bevy shouldn't be hard, but still there might be some friction between wassmtime using tokio and bevy using smol.

Pretty much as it says on the tin. I don't see the common rust asynchronous runtimes in cargo.toml (tokio, rayon, futures), yet on the front page of the bevy website, Bevy advertises "Lock-Free Par...

view this post on Zulip bjorn3 (Jul 29 2023 at 11:01):

bevy-tasks can't be used with futures that use tokio. Those only work on the tokio runtime.

view this post on Zulip bjorn3 (Jul 29 2023 at 11:02):

And bevy-tasks uses the light weight async-executor runtime.

view this post on Zulip Karel Hrkal (kajacx) (Jul 29 2023 at 11:18):

Thanks for the replies. I was aware of AsyncComputeTaskPool, but that allows to spawn futures "in the background". And while that would be possible, it would be very strange if the game update tick would not wait for the individual unit's "update" methods to finish.

When it comes to runtime compatibility, there is async-compat, but I have not really looked into that yet. I don't mind tokio being a transitive dependency, the desktop download size is not that important.

When it comes to the web, async makes it more difficult, not less. For example, when using a jco-traspiled component, the imported functions cannot be asynchronous, at least as far as i can tell. But wasi imports are async, so just taking the rust async code and "shoving" that to a JS function that returns a promise simply doesn't work.

The current solution I use in wasm-bridge is that all component import trait imlementaions (wasi or custom world import) must be sync, even if the function signature says otherwise. To enable async, browsers would have to support asynchronous improted function in webassembly modules, which I am not sure if they do. Or async would have to be supported by wit-bindgen / the component model, which will be in preview3.

view this post on Zulip Christof Petig (Jul 29 2023 at 11:34):

I just know that the main java script 'thread' must never block and that emscripten (asyncify) goes to great lengths to make this work with its pthread emulation. As I had to do for my wasi-thread emulation in the browser :sad:
So even if the current support for async is incomplete with the component model tooling I think this is the correct direction to go in the long term.

view this post on Zulip Christof Petig (Jul 29 2023 at 11:42):

Async compat doesn't look actively developed (last functional commit in early 2021) and doesn't support creating an executor with portable code, so there will be some gaps, but here I also feel that executor abstraction is the right path forward.

view this post on Zulip Ramon Klass (Jul 29 2023 at 12:49):

oh if this is for Bevy you absolutely want to go the async route and never wait for anything, "the game is eating inputs" or "the framerate drops when you turn the camera too fast" are some of the fun things you just don't want to deal with

view this post on Zulip Karel Hrkal (kajacx) (Jul 29 2023 at 15:16):

Ramon Klass said:

oh if this is for Bevy you absolutely want to go the async route and never wait for anything, "the game is eating inputs" or "the framerate drops when you turn the camera too fast" are some of the fun things you just don't want to deal with

I will admit, I have no idea what you mean by "game is eating inputs". Also, I do not understand how making the "update" function synchronous would drop the framerate when the camera moves too fast.

It is true that the update function can take long if the guest code does a lengthy operation, especially IO like writing to a disk or (god forbid) makes HTTP requests. However, there are better ways to handle than making the entire "update" function async and not waiting for it to finish before updating the next unit (or advancing to the next frame for that matter).

For example, the guest code could make those lengthy operations in another "thread". Again, I have not tried wasi threads yet, so I do not know how it works. But the alternative you seem to suggest (if I understand correctly), is to leave the exported "update" function async, and just call all the "update" functions on all units and go to the next frame, without even waiting for the async function to finish.

That sound very bad, you could run into a situation where one update "invocation" is running on the same unit at the same tile as an "older" "invocation", which could create all kinds of bad (and completely unpredictable) behaviour. Worst thing is, the guest code is not using async at all! It is just implementing a "normal" update function, so who would expect it would be running concurenntly more than once at a time?

The only "real" solution is to wait for async to be properly working in the component model. Then, the http wasi imports can be correctly labeled as "async" in the wit protocol, and the guest can call them only from async code, so only async exported functions can call the http imported functions to begin with.

view this post on Zulip Lann Martin (Jul 29 2023 at 15:22):

Which part(s) of wasi do you want to expose to a per-frame update process?

view this post on Zulip Karel Hrkal (kajacx) (Jul 29 2023 at 16:51):

printing to stdout, random generator and clocks . Random and clocks is already synchronous, and writing to stdout can easily be made synchronous.

view this post on Zulip Lann Martin (Jul 30 2023 at 06:37):

I would ignore the wasmtime-wasi implementation and implement wasi directly. Random and clock should be straightforward, and even the stdout output-stream shouldn't be too bad if you can special-case everything it uses to that one purpose. Everything else can return errors or dummy impls.

view this post on Zulip BanOcean (Aug 02 2023 at 18:49):

Is there any roadmap page that shows implementation of the wasi proposals in the all well-know runtimes (something like this for wasm https://webassembly.org/roadmap)?

view this post on Zulip Pat Hickey (Aug 02 2023 at 21:21):

not that im aware of

view this post on Zulip Pat Hickey (Aug 02 2023 at 21:23):

thats a good idea, we could probably host such a chart on wasi.dev if its something you or others wanted to contribute. (my guess is that the current implementors are too busy heads down to work on it right now)

view this post on Zulip Karel Hrkal (kajacx) (Aug 03 2023 at 12:04):

Lann Martin said:

I would ignore the wasmtime-wasi implementation and implement wasi directly. Random and clock should be straightforward, and even the stdout output-stream shouldn't be too bad if you can special-case everything it uses to that one purpose. Everything else can return errors or dummy impls.

That's what I'm trying to do, but I can't. wasmtime forces me to use asynchronous interface, even when I want to use a synchronous one. You can have a look at this file to understand what I want.

To re-iterate, what I want is synchronous exported functions when using wasi. I am fine with implementing the wasi imports myself, but I am not fine with re-implementing the entirety of wasmtime, which someone effectively already suggested.

I tried using wasmtime from the git repo, which supposedly has the PR adding "sync wasi" support merged, but it still doesn't work.

view this post on Zulip Karel Hrkal (kajacx) (Aug 03 2023 at 12:19):

Ok, partialy my bad, you have to use command::sync::add_to_linker(&mut linker)?;, which was mentioned earlier, now it works, thanks.

view this post on Zulip Karel Hrkal (kajacx) (Aug 03 2023 at 13:57):

I will have to try it "for real" later, but this thread can be marked as solved, which I cannot do.


Last updated: Dec 23 2024 at 12:05 UTC