@@ -18,6 +18,7 @@ summary of the motivation and animated sketch of the design in action.
1818 * [ Context-Local Storage] ( #context-local-storage )
1919 * [ Structured concurrency] ( #structured-concurrency )
2020 * [ Streams and Futures] ( #streams-and-futures )
21+ * [ Stream Readiness] ( #stream-readiness )
2122 * [ Waiting] ( #waiting )
2223 * [ Backpressure] ( #backpressure )
2324 * [ Returning] ( #returning )
@@ -408,6 +409,66 @@ successfully read, conveys the completion of a second event.
408409The [ Stream State] and [ Future State] sections describe the runtime state
409410maintained for streams and futures by the Canonical ABI.
410411
412+ ### Stream Readiness
413+
414+ When passed a non-zero-length buffer, the ` stream.read ` and ` stream.write `
415+ built-ins are "completion-based" (in the style of, e.g., [ Overlapped I/O] or
416+ [ ` io_uring ` ] ) in that they complete only once one or more values have been
417+ copied to or from the memory buffer passed in at the start of the operation.
418+ In a Component Model context, completion-based I/O avoids intermediate copies
419+ and enables a greater degree of concurrency in a number of cases and thus
420+ language producer toolchains should attempt to pass non-zero-length buffers
421+ whenever possible.
422+
423+ Given completion-based ` stream.{read,write} ` built-ins, "readiness-based" APIs
424+ (in the style of, e.g., [ ` select ` ] or [ ` epoll ` ] used in combination with
425+ [ ` O_NONBLOCK ` ] ) can be implemented by passing an intermediate non-zero-length
426+ memory buffer to ` stream.{read,write} ` and signalling "readiness" once the
427+ operation completes. However, this approach incurs extra copying overhead. To
428+ avoid this overhead in a best-effort mannner, ` stream.{read,write} ` allow the
429+ buffer length to be zero in which case "completion" of the operation is allowed
430+ (but not required) to wait to complete until the other end is "ready". As the
431+ "but not required" caveat suggests, after a zero-length ` stream.{read,write} `
432+ completes, there is no guarantee that if the next call passes a non-zero-length
433+ buffer that the operation won't block. This lack of guarantee is due to
434+ practical host externalities and because readiness may simply not be possible
435+ to implement given certain underlying host APIs.
436+
437+ As an example, to implement ` select() ` and non-blocking ` write() ` in
438+ [ wasi-libc] , the following implementation strategy could be used (a symmetric
439+ scheme is also possible for ` read() ` ):
440+ * The libc-internal file descriptor table tracks whether there is currently a
441+ pending write or whether a previous zero-length write has completed, both
442+ initially false.
443+ * When ` select() ` ing a non-blocking file descriptor for writing, a zero-length
444+ write is started if there is no write already pending and then the stream is
445+ [ waited] ( #waiting ) upon along with all the other ` select() ` arguments.
446+ * If a pending write completes, ` select() ` updates the file descriptor to
447+ record this fact and then indicates to the ` select() ` caller that this
448+ file descriptor is "ready".
449+ * When ` write() ` is called in non-blocking mode:
450+ * If there is already a pending ` stream.write ` for this file descriptor,
451+ ` write() ` immediately returns ` EWOULDBLOCK ` .
452+ * Otherwise, ` stream.write ` is called, forwarding the buffer that was passed
453+ to ` write() ` .
454+ * If ` stream.write ` returns that it successfully copied some bytes without
455+ blocking, then ` write() ` returns success. This is the fast path that we
456+ hope hits most of the time.
457+ * Otherwise, if ` stream.write ` blocks:
458+ * ` stream.cancel-write ` is called to halt the async ` stream.write ` and
459+ regain ownership of the ` write() ` -caller's buffer.
460+ * The contents of the ` write() ` -caller's buffer are copied to a
461+ wasi-libc-internal buffer and a new ` stream.write ` is issued for this
462+ buffer (which will likely return that it blocked).
463+ * ` write() ` will then synchronously return success to the caller,
464+ with the above logic implicitly waiting for it to complete before
465+ signalling readiness again.
466+
467+ The fallback path for when the zero-length write does not indicate actual
468+ readiness resembles the buffering normally performed by the kernel and
469+ reflects the fact that streams do not perform internal buffering between
470+ the readable and writable ends.
471+
411472### Waiting
412473
413474When a component asynchronously lowers an import, it is explicitly requesting
@@ -1134,6 +1195,12 @@ comes after:
11341195[ FS or GS Segment Base Address ] : https://docs.kernel.org/arch/x86/x86_64/fsgs.html
11351196[ Cooperative ] : https://en.wikipedia.org/wiki/Cooperative_multitasking
11361197[ Multithreading ] : https://en.wikipedia.org/wiki/Multithreading_(computer_architecture)
1198+ [ Overlapped I/O ] : https://en.wikipedia.org/wiki/Overlapped_I/O
1199+ [ `io_uring` ] : https://en.wikipedia.org/wiki/Io_uring
1200+ [ `epoll` ] : https://en.wikipedia.org/wiki/Epoll
1201+
1202+ [ `select` ] : https://pubs.opengroup.org/onlinepubs/007908799/xsh/select.html
1203+ [ `O_NONBLOCK` ] : https://pubs.opengroup.org/onlinepubs/7908799/xsh/open.html
11371204
11381205[ AST Explainer ] : Explainer.md
11391206[ Lift and Lower Definitions ] : Explainer.md#canonical-definitions
@@ -1152,6 +1219,7 @@ comes after:
11521219[ `thread.spawn*` ] : Explainer.md#-threadspawn_ref
11531220[ `{stream,future}.new` ] : Explainer.md#-streamnew-and-futurenew
11541221[ `{stream,future}.{read,write}` ] : Explainer.md#-streamread-and-streamwrite
1222+ [ `stream.cancel-read` ] : Explainer.md#-streamcancel-read-streamcancel-write-futurecancel-read-and-futurecancel-write
11551223[ ESM-integration ] : Explainer.md#ESM-integration
11561224
11571225[ Canonical ABI Explainer ] : CanonicalABI.md
@@ -1190,6 +1258,7 @@ comes after:
11901258[ shared-everything-threads ] : https://github.com/webAssembly/shared-everything-threads
11911259[ memory64 ] : https://github.com/webAssembly/memory64
11921260[ wasm-gc ] : https://github.com/WebAssembly/gc/blob/main/proposals/gc/MVP.md
1261+ [ wasi-libc ] : https://github.com/WebAssembly/wasi-libc
11931262
11941263[ WASI Preview 3 ] : https://github.com/WebAssembly/WASI/tree/main/wasip2#looking-forward-to-preview-3
11951264[ `wasi:http/handler.handle` ] : https://github.com/WebAssembly/wasi-http/blob/main/wit-0.3.0-draft/handler.wit
0 commit comments