-
Notifications
You must be signed in to change notification settings - Fork 82
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
How to implement non-blocking POSIX send
in terms of stream.write
?
#441
Comments
Great question. I believe @lukewagner's plan was to allow zero-length However, this could lead to a livelock-type situation if both the read and write ends are repeatedly probing for readiness using zero-length calls, but failing to line up since both of them are canceling any call that returns |
Yes, relaxing the current length restrictions on To avoid the livelock scenario @dicej mentions, the general rule needs to be that, if you are using a zero-length read/write to probe readiness, once you hear back that "the other side is ready", you must follow that up with a non-zero-length read/write that you allow to complete (either by calling If you apply these rules to a vanilla scenario where there is a stream between two components that are both applying this non-blocking I/O technique, then you'd get the following sequence, which I think is what you'd expect:
Thus, you do get "blocking", but only in guest-to-guest cases where, one way or another, you need to synchronously block and switch between A and B for streaming to work at all. In host-guest streaming scenarios, such blocking could be avoided by the host as an impl detail. I hope that helps, let me know if that sounds like it'd work or there are any problems. |
Thanks for the detailed explanation (as usual 🤠) Unless I'm mistaken, this protocol seems susceptible to infinite blocking in the presence of false wake-ups. If one side of the stream reports that its 0-sized read/write is done, it must be very, very sure it actually has some data to read/write, otherwise the other side of the stream is toast. In the worst case new data never appears and the other side will never wake up again. Ultimately, I'll need more hands-on experience with all of this to fully understand all the possible interactions. |
Yeah, that is a hazard. I'm not sure how likely it is to occur, but I guess I can see it happening hypothetically if code performs a non-blocking |
As an optimization, the CM could additionally offer
This reduces the number of operations on the happy path (where data is immediately ready) from 2 to 1. Taking it even further; with these methods in place, there may not even be a need to expose 0-sized reads/writes to the guest. |
Good idea! As a small tweak to consider, we could also add
I think we still need 0-length reads/writes as the way for
To avoid spurious Since buffering shouldn't happen in host-to-wasm, wasm-to-host, wasm-to-wasm synchronous or wasm-to-wasm asynchronous-completion-based scenarios and since a traditional OS piping between two processes also copies into an intermediate buffer (the pipe's kernel memory), this seems equivalent-or-better than the status quo and so a pretty good default behavior. We could offer an advanced option (say, set by WDYT? |
Fine by me 👍
With the
Awesome! This seems like a simple but effective way to ensure both sides of the stream make progress, even if they're both readiness-based. Earlier you mentioned:
With the asymmetric rendezvous mechanism in place, I presume this restriction is now lifted for the Also, would it make sense to enforce this protocol for the writer? For example trap the writer if it attempts to do two
Could you elaborate on the
Aside from the potential double buffering (see previous point): Agree! 👌 |
Ah, maybe we're thinking of different behavior for
Yep!
Hypothetically we could, but I worry this would have false negatives (where maybe there were two (or three or ...) in a row for some ad hoc reason, but a bounded amount, so no livelock).
My thinking here is that an optimizing host would never do the wasm equivalent of a |
Am I correct to say that: when
stream.write
returnsBLOCKED
, it continues to have access to the provided memory buffer until the write either finishes or is canceled?If so, does this mean wasi-libc has to perform an intermediate copy into a private socket-level buffer first for every
send
/recv
on non-blocking sockets? (in order to emulate the "readiness"-based POSIX async model)The text was updated successfully, but these errors were encountered: