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.
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
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.
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).
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
.
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
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.
making the exported methods asynchronous is just horrible to use
Could you expand on what you mean by this?
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
its already landed in main
test-programs now shows the component wasi-tests running in both synchronous and tokio
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?
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.
a sync version of HostOutputStream and HostPollable would be a signficant redesign of wasmtime-wasi
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
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
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
alex (who was the original author of tokio) concurred
this will only get harder as we evolve to support preview 3 streams/futures as well
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.
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.
if you have a radically different set of needs than that, then you probably need a different wasi implementation.
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.
wasi implementation == wasi runtime
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
Are there any other runtimes in Rust that support the component model?
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"?
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
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
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
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
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.
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.
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.
bevy-tasks can't be used with futures that use tokio. Those only work on the tokio runtime.
And bevy-tasks uses the light weight async-executor runtime.
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.
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.
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.
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
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.
Which part(s) of wasi do you want to expose to a per-frame update process?
printing to stdout, random generator and clocks . Random and clocks is already synchronous, and writing to stdout can easily be made synchronous.
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.
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)?
not that im aware of
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)
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.
Ok, partialy my bad, you have to use command::sync::add_to_linker(&mut linker)?;
, which was mentioned earlier, now it works, thanks.
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