@@ -18,6 +18,7 @@ summary of the motivation and animated sketch of the design in action.
18
18
* [ Context-Local Storage] ( #context-local-storage )
19
19
* [ Structured concurrency] ( #structured-concurrency )
20
20
* [ Streams and Futures] ( #streams-and-futures )
21
+ * [ Stream Readiness] ( #stream-readiness )
21
22
* [ Waiting] ( #waiting )
22
23
* [ Backpressure] ( #backpressure )
23
24
* [ Returning] ( #returning )
@@ -408,6 +409,68 @@ successfully read, conveys the completion of a second event.
408
409
The [ Stream State] and [ Future State] sections describe the runtime state
409
410
maintained for streams and futures by the Canonical ABI.
410
411
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
+
411
474
### Waiting
412
475
413
476
When a component asynchronously lowers an import, it is explicitly requesting
@@ -1134,6 +1197,12 @@ comes after:
1134
1197
[ FS or GS Segment Base Address ] : https://docs.kernel.org/arch/x86/x86_64/fsgs.html
1135
1198
[ Cooperative ] : https://en.wikipedia.org/wiki/Cooperative_multitasking
1136
1199
[ 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
1137
1206
1138
1207
[ AST Explainer ] : Explainer.md
1139
1208
[ Lift and Lower Definitions ] : Explainer.md#canonical-definitions
@@ -1152,6 +1221,7 @@ comes after:
1152
1221
[ `thread.spawn*` ] : Explainer.md#-threadspawn_ref
1153
1222
[ `{stream,future}.new` ] : Explainer.md#-streamnew-and-futurenew
1154
1223
[ `{stream,future}.{read,write}` ] : Explainer.md#-streamread-and-streamwrite
1224
+ [ `stream.cancel-write` ] : Explainer.md#-streamcancel-read-streamcancel-write-futurecancel-read-and-futurecancel-write
1155
1225
[ ESM-integration ] : Explainer.md#ESM-integration
1156
1226
1157
1227
[ Canonical ABI Explainer ] : CanonicalABI.md
@@ -1190,6 +1260,7 @@ comes after:
1190
1260
[ shared-everything-threads ] : https://github.com/webAssembly/shared-everything-threads
1191
1261
[ memory64 ] : https://github.com/webAssembly/memory64
1192
1262
[ wasm-gc ] : https://github.com/WebAssembly/gc/blob/main/proposals/gc/MVP.md
1263
+ [ wasi-libc ] : https://github.com/WebAssembly/wasi-libc
1193
1264
1194
1265
[ WASI Preview 3 ] : https://github.com/WebAssembly/WASI/tree/main/wasip2#looking-forward-to-preview-3
1195
1266
[ `wasi:http/handler.handle` ] : https://github.com/WebAssembly/wasi-http/blob/main/wit-0.3.0-draft/handler.wit
0 commit comments