Stream: wasi

Topic: Streaming data between components with preview2


view this post on Zulip Christof Petig (Nov 29 2023 at 20:36):

The recommended way (https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#streams) to pass data from one component to an other is by a combination of io:poll and read.
Sadly this means that the interface is asymmetric, as the sender has to create a non-standard "futex like" pollable with the host and pass it to the reader - and there is significant communication overhead (reader component calls poll, writer calls "wake", reader awakes and invokes the read of the writer component) per transmitted date.

So I sat down and designed a consumer-side resource which the reader passes to the subscribe function and the writer calls the "ready(data)" method on whenever there is new data available. This looks neat on paper, but you introduce a cyclic component dependency as the callback interface is imported by the sender from the reader and the data-source interface is imported by the reader from the sender.
This cyclic dependency isn't supported by the current component tooling. But even if it were, the Rust bindgen will additionally expect an imported resource type when passing arguments to an imported function.

I know this is trying to introduce optimizations from the preview3 async proposal into preview2, but can you think of a way to realize a similar symmetric inter-component callback mechanism with preview2?

view this post on Zulip Pat Hickey (Nov 29 2023 at 20:38):

first off, that WitInWasi doc is sufficiently out of date that i think we should just remove it and maybe someone can take on the project of rewriting it

view this post on Zulip Pat Hickey (Nov 29 2023 at 20:39):

its unfortunate that streams arent very nice to use between components in preview 2, but its just the restrictions of the component model type system

view this post on Zulip Pat Hickey (Nov 29 2023 at 20:39):

elsewhere i discussed "onion composition" between components that need to export io, or interfaces depending on io... let me find the link

view this post on Zulip Pat Hickey (Nov 29 2023 at 20:40):

https://bytecodealliance.zulipchat.com/#narrow/stream/219900-wasi/topic/.60wasi-http.60.20middleware.20is.20infeasible.3F/near/402083142

view this post on Zulip Christof Petig (Nov 29 2023 at 20:44):

Oh, I feared that the "middleware" thread discussed this problem, I was hoping for a nice solution with bidirectional resource method calls and no extensive glue code between the components.

view this post on Zulip Pat Hickey (Nov 29 2023 at 20:45):

unfortunately, for anything that involves transferring control flow between components (i.e. anything that needs to measure readiness using pollables) theres just not a good way to do it in preview 2

view this post on Zulip Christof Petig (Nov 29 2023 at 20:48):

By the way, did I miss this component-to-component-activation pollable in the current WASI propsals, or did simply no-one run into this situation before.

view this post on Zulip Pat Hickey (Nov 29 2023 at 20:49):

when i say pollable i mean wasi/io.poll.{pollable}

view this post on Zulip Pat Hickey (Nov 29 2023 at 20:50):

we've been aware of this situation for a while (i remember we had a long discussion about it in may of this year) but we're just sorta stuck getting something that doesnt solve it shipped before we can go into solving it

view this post on Zulip Christof Petig (Nov 29 2023 at 20:51):

E.g. resource { constructor(); get-pollable: func() -> pollable; wake: func(); }to create an io compatible pollable to pass to from the writer the reader

view this post on Zulip Pat Hickey (Nov 29 2023 at 20:51):

so basically, in preview 2, component composition needs to either be entirely synchronous stuff (e.g. the classic example we use that is a markdown renderer with render: func(string) -> string

view this post on Zulip Pat Hickey (Nov 29 2023 at 20:51):

or it needs to be onion

view this post on Zulip Pat Hickey (Nov 29 2023 at 20:51):

right, so you cant export a pollable from that function without exporting the wasi:io package entirely

view this post on Zulip Pat Hickey (Nov 29 2023 at 20:52):

a pollable needs to come from the exact same place that implements wasi:io/poll.poll

view this post on Zulip Pat Hickey (Nov 29 2023 at 20:52):

and thats basically the "magic" function that takes control flow away until progress is made

view this post on Zulip Pat Hickey (Nov 29 2023 at 20:52):

so that function must be the "parent" of your component, basically.

view this post on Zulip Pat Hickey (Nov 29 2023 at 20:53):

in the sense that it provides all the exports of io and every other package that depends on io to your component.

view this post on Zulip Christof Petig (Nov 29 2023 at 20:56):

(Thinking about an onion way to express communication middleware for publisher/subscriber, including wrapping io)

view this post on Zulip Christof Petig (Nov 29 2023 at 20:57):

Thank you for the clarification, I can't wait to see future and stream support in preview3 ...

view this post on Zulip Christof Petig (Nov 29 2023 at 21:01):

Creating a pollable/waker pair could be added to io/poll with small additional effort, couldn't it?

view this post on Zulip Joel Dice (Nov 29 2023 at 21:36):

How would such a thing be used? If a component is blocked waiting for the pollable, there's no way the waker could be triggered since there's currently no notion of threads or reentrancy in the component model.

view this post on Zulip Christof Petig (Nov 29 2023 at 21:47):

Good point, I now realize that I was modeling the components running in separate (cooperative) stacks, which strangely is what my applications require. This can only work if poll switches between contexts, which I took for granted.

view this post on Zulip Joel Dice (Nov 29 2023 at 21:53):

Yes, and that's essentially what the service chaining approach I described earlier requires:

Second, you can always do service chaining the old-fashioned way by having the middleware component do an outbound HTTP request to the service component (which the host might optimize if it notices the service can be invoked without using the network at all). I.e. you don't need component composition to do service chaining, and you probably won't want to use it for that until Preview 3.

When you have two components running in separate fibers, it's easy enough to connect their concurrent I/O operations using e.g. a buffered, in-memory channel.

view this post on Zulip Christof Petig (Nov 29 2023 at 21:54):

Ok, I see, these are the preview3 fibers. I guess I aim far beyond preview2.

view this post on Zulip Joel Dice (Nov 29 2023 at 21:55):

Yeah, I think a lot of us are going to rig up a poor man's version of component model async before the real thing arrives :)

view this post on Zulip Christof Petig (Nov 30 2023 at 20:41):

Update: I undid the callback optimizations (very similar to slide 11 of the CM async proposal) which require criss-cross linking and went back to exchanging a pollable. A prototype app worked well.

Thank you for the explanations and support!

PS: I genuinely need multiple stacks because the communicating C++|Rust SOA Applications each bring their own main loop.

view this post on Zulip Christof Petig (Nov 30 2023 at 20:42):

PPS: I guess I will be an early adopter of preview3 prototypes.

view this post on Zulip Ryan Levick (rylev) (Dec 01 2023 at 17:37):

You may be interested in the prototype I posted in the middleware topic which uses the runtime to do a bit of async cooperation between two components.


Last updated: Dec 23 2024 at 12:05 UTC