@@ -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