@@ -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,68 @@ 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 manner, ` 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 a subsequent non-zero-length
433+ ` stream.{read,write} ` call will succeed without blocking. This lack of
434+ guarantee is due to practical externalities and because readiness may simply
435+ not be possible 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 and whether ` select() ` has indicated that this file descriptor
442+ is ready to write.
443+ * When ` select() ` is called to wait for a stream-backed file descriptor to be
444+ writable:
445+ * ` select() ` starts a zero-length write if there is not already a pending
446+ write in progress and then [ waits] ( #waiting ) on the stream (along with the
447+ other ` select() ` arguments).
448+ * If the pending write completes, ` select() ` updates the file descriptor and
449+ returns that the file descriptor is ready.
450+ * When ` write() ` is called for an ` O_NONBLOCKING ` file descriptor:
451+ * If there is already a pending ` stream.write ` for this file descriptor,
452+ ` write() ` immediately returns ` EWOULDBLOCK ` .
453+ * Otherwise:
454+ * ` write() ` calls ` stream.write ` , forwarding the caller's buffer.
455+ * If ` stream.write ` returns that it successfully copied some bytes without
456+ blocking, ` write() ` returns success.
457+ * Otherwise, to avoid blocking:
458+ * ` write() ` calls [ ` stream.cancel-write ` ] to regain ownership of the
459+ caller's buffer.
460+ * If ` select() ` has * not* indicated that this file descriptor is ready,
461+ ` write() ` starts a zero-length write and returns ` EWOULDBLOCK ` .
462+ * Otherwise, to avoid the potential infinite loop:
463+ * ` write() ` copies the contents of the caller's buffer into an
464+ internal buffer, starts a new ` stream.write ` to complete in the
465+ background using the internal buffer, and then returns success.
466+ * The above logic implicitly waits for this background ` stream.write `
467+ to complete before the file descriptor is considered ready again.
468+
469+ The fallback path for when the zero-length write does not accurately signal
470+ readiness resembles the buffering normally performed by the kernel for a
471+ ` write ` syscall and reflects the fact that streams do not perform internal
472+ buffering between the readable and writable ends.
473+
411474### Waiting
412475
413476When a component asynchronously lowers an import, it is explicitly requesting
@@ -1134,6 +1197,12 @@ comes after:
11341197[ FS or GS Segment Base Address ] : https://docs.kernel.org/arch/x86/x86_64/fsgs.html
11351198[ Cooperative ] : https://en.wikipedia.org/wiki/Cooperative_multitasking
11361199[ Multithreading ] : https://en.wikipedia.org/wiki/Multithreading_(computer_architecture)
1200+ [ Overlapped I/O ] : https://en.wikipedia.org/wiki/Overlapped_I/O
1201+ [ `io_uring` ] : https://en.wikipedia.org/wiki/Io_uring
1202+ [ `epoll` ] : https://en.wikipedia.org/wiki/Epoll
1203+
1204+ [ `select` ] : https://pubs.opengroup.org/onlinepubs/007908799/xsh/select.html
1205+ [ `O_NONBLOCK` ] : https://pubs.opengroup.org/onlinepubs/7908799/xsh/open.html
11371206
11381207[ AST Explainer ] : Explainer.md
11391208[ Lift and Lower Definitions ] : Explainer.md#canonical-definitions
@@ -1152,6 +1221,7 @@ comes after:
11521221[ `thread.spawn*` ] : Explainer.md#-threadspawn_ref
11531222[ `{stream,future}.new` ] : Explainer.md#-streamnew-and-futurenew
11541223[ `{stream,future}.{read,write}` ] : Explainer.md#-streamread-and-streamwrite
1224+ [ `stream.cancel-write` ] : Explainer.md#-streamcancel-read-streamcancel-write-futurecancel-read-and-futurecancel-write
11551225[ ESM-integration ] : Explainer.md#ESM-integration
11561226
11571227[ Canonical ABI Explainer ] : CanonicalABI.md
@@ -1190,6 +1260,7 @@ comes after:
11901260[ shared-everything-threads ] : https://github.com/webAssembly/shared-everything-threads
11911261[ memory64 ] : https://github.com/webAssembly/memory64
11921262[ wasm-gc ] : https://github.com/WebAssembly/gc/blob/main/proposals/gc/MVP.md
1263+ [ wasi-libc ] : https://github.com/WebAssembly/wasi-libc
11931264
11941265[ WASI Preview 3 ] : https://github.com/WebAssembly/WASI/tree/main/wasip2#looking-forward-to-preview-3
11951266[ `wasi:http/handler.handle` ] : https://github.com/WebAssembly/wasi-http/blob/main/wit-0.3.0-draft/handler.wit
0 commit comments