@@ -1262,15 +1262,13 @@ returned to the wasm code. Lastly, `COMPLETED` indicates that at least one
1262
1262
value has been copied and neither ` DROPPED ` nor ` CANCELLED ` apply.
1263
1263
1264
1264
As with functions and buffers, native host code can be on either side of a
1265
- stream. Thus, streams are defined in terms of an abstract interface that can be
1265
+ stream. Thus, streams are defined in terms of abstract interfaces that can be
1266
1266
implemented and consumed by wasm or host code (with all {wasm,host} pairings
1267
1267
being possible and well-defined). Since a ` stream ` in a function parameter or
1268
1268
result type always represents the transfer of the * readable* end of a stream,
1269
- the abstract stream interface is ` ReadableStream ` and allows a (wasm or host)
1270
- client to asynchronously read multiple values from a (wasm or host) producer.
1271
- (The absence of a dual ` WritableStream ` abstract interface reflects the fact
1272
- that there is no Component Model type for passing the writable end of a
1273
- stream.)
1269
+ only the ` ReadableStream ` interface can be implemented by either wasm or the
1270
+ host; the ` WritableStream ` interface is always written to by wasm via a
1271
+ writable stream end created by ` stream.new ` .
1274
1272
``` python
1275
1273
ReclaimBuffer = Callable[[], None ]
1276
1274
OnCopy = Callable[[ReclaimBuffer], None ]
@@ -1281,40 +1279,53 @@ class ReadableStream:
1281
1279
read: Callable[[ComponentInstance, WritableBuffer, OnCopy, OnCopyDone], None ]
1282
1280
cancel: Callable[[], None ]
1283
1281
drop: Callable[[], None ]
1282
+
1283
+ class WritableStream :
1284
+ t: ValType
1285
+ write: Callable[[ComponentInstance, ReadableBuffer, OnCopy, OnCopyDone], None ]
1286
+ cancel: Callable[[], None ]
1287
+ drop: Callable[[], None ]
1284
1288
```
1285
- The key operation is ` read ` which works as follows:
1289
+ The key operations in these interfaces are ` read ` and ` write ` which work as
1290
+ follows:
1286
1291
* ` read ` never blocks and returns its values by either synchronously or
1287
1292
asynchronously writing to the given ` WritableBuffer ` and then calling the
1288
1293
given ` OnCopy* ` callbacks to notify the caller of progress.
1289
- * ` OnCopyDone ` is called to indicate that the ` read ` is finished copying and
1290
- that the caller has regained ownership of the buffer.
1291
- * ` OnCopy ` is called to indicate a write has been made into the buffer.
1292
- However, there may be further writes made in the future, so the caller has
1294
+ * Symmetrically, ` write ` never blocks and takes the value to be written
1295
+ from the given ` ReadableBuffer ` , calling the given ` OnCopy* ` callbacks to
1296
+ notify the caller of progress.
1297
+ * ` OnCopyDone ` is called to indicate that the ` read ` or ` write ` is finished
1298
+ copying and that the caller has regained ownership of the buffer.
1299
+ * ` OnCopy ` is called to indicate a copy has been made to or from the buffer.
1300
+ However, there may be further copies made in the future, so the caller has
1293
1301
* not* regained ownership of the buffer.
1294
- * The ` ReclaimBuffer ` callback passed to ` OnCopy ` allows the caller of ` read ` to
1295
- immediately regain ownership of the buffer once the first copy has completed.
1302
+ * The ` ReclaimBuffer ` callback passed to ` OnCopy ` allows the caller of ` read ` or
1303
+ ` write ` to immediately regain ownership of the buffer once the first copy has
1304
+ completed.
1296
1305
* ` cancel ` is non-blocking, but does ** not** guarantee that ownership of
1297
1306
the buffer has been returned; ` cancel ` only lets the caller * request* that
1298
- ` read ` call one of the ` OnCopy* ` callbacks ASAP (which may or may not happen
1307
+ one of the ` OnCopy* ` callbacks be called ASAP (which may or may not happen
1299
1308
during ` cancel ` ).
1300
- * The client may not call ` read ` or ` drop ` while there is still an unfinished
1301
- ` read ` of the same ` ReadableStream ` .
1309
+ * The client may not call ` read ` , ` write ` or ` drop ` while there is a previous
1310
+ ` read ` or ` write ` in progress .
1302
1311
1303
1312
The ` OnCopy* ` callbacks are a spec-internal detail used to specify the allowed
1304
1313
concurrent behaviors of ` stream.{read,write} ` and not exposed directly to core
1305
1314
wasm code. Specifically, the point of the ` OnCopy* ` callbacks is to specify that
1306
- * multiple* writes are allowed into the same ` WritableBuffer ` up until the point
1307
- where either the buffer is full or the calling core wasm code receives the
1308
- ` STREAM_READ ` progress event (in which case ` ReclaimBuffer ` is called). This
1309
- reduces the number of task-switches required by the spec, particularly when
1310
- streaming between two components.
1311
-
1312
- The ` SharedStreamImpl ` class implements ` ReadableStream ` for streams created by
1313
- wasm (via ` stream.new ` ) and tracks the common state shared by both the readable
1314
- and writable ends of streams (defined below). Introducing the class in chunks,
1315
- starting with the fields and initialization:
1316
- ``` python
1317
- class SharedStreamImpl (ReadableStream ):
1315
+ * multiple* reads or writes are allowed into the same ` Buffer ` up until the point
1316
+ where either the buffer is full or the calling core wasm code receives a
1317
+ ` STREAM_READ ` or ` STREAM_WRITE ` progress event (in which case ` ReclaimBuffer ` is
1318
+ called). This reduces the number of task-switches required by the spec,
1319
+ particularly when streaming between two components.
1320
+
1321
+ The ` SharedStreamImpl ` class implements both ` ReadableStream ` and
1322
+ ` WritableStream ` for streams created by wasm (via ` stream.new ` ) and tracks the
1323
+ common state shared by both the readable and writable ends of streams (defined
1324
+ below).
1325
+
1326
+ Introducing ` SharedStreamImpl ` in chunks, starting with the fields and initialization:
1327
+ ``` python
1328
+ class SharedStreamImpl (ReadableStream , WritableStream ):
1318
1329
dropped: bool
1319
1330
pending_inst: Optional[ComponentInstance]
1320
1331
pending_buffer: Optional[Buffer]
@@ -1355,9 +1366,9 @@ callback:
1355
1366
if self .pending_buffer:
1356
1367
self .reset_and_notify_pending(CopyResult.DROPPED )
1357
1368
```
1358
- While the abstract ` ReadableStream ` interface * allows * ` cancel ` to return
1359
- without having returned ownership of the buffer (which, in general, is
1360
- necessary for [ various] [ OIO ] [ host] [ io_uring ] APIs), when * wasm* is
1369
+ While the abstract ` ReadableStream ` and ` WritableStream ` interfaces * allow *
1370
+ ` cancel ` to return without having returned ownership of the buffer (which, in
1371
+ general, is necessary for [ various] [ OIO ] [ host] [ io_uring ] APIs), when * wasm* is
1361
1372
implementing the stream, ` cancel ` always returns ownership of the buffer
1362
1373
immediately.
1363
1374
@@ -1367,16 +1378,16 @@ Note that `cancel` and `drop` notify in opposite directions:
1367
1378
* ` drop ` * must not* be called on a readable or writable end with an operation
1368
1379
pending, and thus ` drop ` notifies the opposite end.
1369
1380
1370
- The ` read ` method implements the ` ReadableStream.read ` interface described
1371
- above and is called by either ` stream.read ` or the host, depending on who is
1372
- passed the readable end of the stream. If the reader is first to rendezvous,
1373
- then all the parameters are stored in the ` pending_* ` fields, requiring the
1374
- reader to wait for the writer to rendezvous. If the writer was first to
1375
- rendezvous, then there is already a pending ` ReadableBuffer ` to read from, and
1376
- so the reader copies as much as it can (which may be less than a full buffer's
1377
- worth) and eagerly completes the copy without blocking. In the final special
1378
- case where both the reader and pending writer have zero-length buffers, the
1379
- writer is notified, but the reader remains blocked:
1381
+ The ` read ` method implements ` ReadableStream.read ` and is called by either
1382
+ ` stream.read ` or the host, depending on who is passed the readable end of the
1383
+ stream. If the reader is first to rendezvous, then all the parameters are
1384
+ stored in the ` pending_* ` fields, requiring the reader to wait for the writer
1385
+ to rendezvous. If the writer was first to rendezvous, then there is already a
1386
+ pending ` ReadableBuffer ` to read from, and so the reader copies as much as it
1387
+ can (which may be less than a full buffer's worth) and eagerly completes the
1388
+ copy without blocking. In the final special case where both the reader and
1389
+ pending writer have zero-length buffers, the writer is notified, but the reader
1390
+ remains blocked:
1380
1391
``` python
1381
1392
def read (self , inst , dst_buffer , on_copy , on_copy_done ):
1382
1393
if self .dropped:
@@ -1403,13 +1414,13 @@ and lowering can alias the same memory, interleavings can be complex and must
1403
1414
be handled carefully. Future improvements to the Canonical ABI ([ lazy lowering] )
1404
1415
can greatly simplify this interleaving and be more practical to implement.
1405
1416
1406
- The ` write ` method is symmetric to ` read ` (being given a ` ReadableBuffer `
1407
- instead of a ` WritableBuffer ` ) and is called by the ` stream.write ` built-in.
1408
- (noting that the host cannot be passed the writable end of a stream but may
1409
- instead * implement * the ` ReadableStream ` interface and pass the readable end
1410
- into a component). The steps for ` write ` are the same as ` read ` except for
1411
- when a zero-length ` write ` rendezvous with a zero-length ` read ` , in which case
1412
- the ` write ` eagerly completes, leaving the ` read ` pending:
1417
+ The ` write ` method implements ` WritableStream.write ` and is called by the
1418
+ ` stream.write ` built-in (noting that the host cannot be passed the writable end
1419
+ of a stream but may instead * implement * the ` ReadableStream ` interface and pass
1420
+ the readable end into a component). The steps for ` write ` are the same as
1421
+ ` read ` except for when a zero-length ` write ` rendezvous with a zero-length
1422
+ ` read ` , in which case the ` write ` eagerly completes, leaving the ` read `
1423
+ pending:
1413
1424
``` python
1414
1425
def write (self , inst , src_buffer , on_copy , on_copy_done ):
1415
1426
if self .dropped:
@@ -1456,7 +1467,7 @@ entirely symmetric, with the only difference being whether the polymorphic
1456
1467
` copy ` method (used below) calls ` read ` or ` write ` :
1457
1468
``` python
1458
1469
class StreamEnd (Waitable ):
1459
- shared: ReadableStream
1470
+ shared: ReadableStream| WritableStream
1460
1471
copying: bool
1461
1472
done: bool
1462
1473
@@ -1493,36 +1504,37 @@ since the async read or write cannot be cancelled without blocking and `drop`
1493
1504
This means that client code must take care to wait for these operations to
1494
1505
finish before dropping.
1495
1506
1496
- The ` {Readable,Writable}StreamEnd.copy ` method is called polymorphically by the
1497
- shared definition of ` stream.{read,write} ` below. While the static type of
1498
- ` StreamEnd.shared ` is ` ReadableStream ` , a ` WritableStreamEnd ` always points to
1499
- a ` SharedStreamImpl ` object which is why ` WritableStreamEnd.copy ` can
1500
- unconditionally call ` stream.write ` .
1501
-
1502
1507
1503
1508
#### Future State
1504
1509
1505
1510
Futures are similar to streams, except that instead of passing 0..N values,
1506
1511
exactly one value is passed from the writer end to the reader end unless the
1507
1512
reader end is explicitly dropped first.
1508
1513
1509
- Like streams, futures are defined in terms of an abstract ` ReadableFuture `
1510
- interface that can be implemented by the host or wasm :
1514
+ Futures are defined in terms of abstract ` ReadableFuture ` and ` WritableFuture `
1515
+ interfaces :
1511
1516
``` python
1512
1517
class ReadableFuture :
1513
1518
t: ValType
1514
1519
read: Callable[[ComponentInstance, WritableBuffer, OnCopyDone], None ]
1515
1520
cancel: Callable[[], None ]
1516
1521
drop: Callable[[], None ]
1522
+
1523
+ class WritableFuture :
1524
+ t: ValType
1525
+ write: Callable[[ComponentInstance, ReadableBuffer, OnCopyDone], None ]
1526
+ cancel: Callable[[], None ]
1527
+ drop: Callable[[], None ]
1517
1528
```
1518
- The ` ReadableFuture ` interface works like ` ReadableStream ` except that there is
1519
- no ` OnCopy ` callback passed to ` read ` to report partial progress (since at most
1520
- 1 value is copied) and the given ` WritableBuffer ` must have ` remain() == 1 ` .
1529
+ These interfaces work like ` ReadableStream ` and ` WritableStream ` except that
1530
+ there is no ` OnCopy ` callback passed to ` read ` or ` write ` to report partial
1531
+ progress (since at most 1 value is copied) and the given ` Buffer ` must have
1532
+ ` remain() == 1 ` .
1521
1533
1522
1534
Introducing ` SharedFutureImpl ` in chunks, the first part is exactly
1523
1535
symmetric to ` SharedStreamImpl ` in how initialization and cancellation work:
1524
1536
``` python
1525
- class SharedFutureImpl (ReadableFuture ):
1537
+ class SharedFutureImpl (ReadableFuture , WritableFuture ):
1526
1538
dropped: bool
1527
1539
pending_inst: Optional[ComponentInstance]
1528
1540
pending_buffer: Optional[Buffer]
@@ -1597,7 +1609,7 @@ Lastly, the `{Readable,Writable}FutureEnd` classes are mostly symmetric with
1597
1609
value or been notified of the reader dropping their end:
1598
1610
``` python
1599
1611
class FutureEnd (Waitable ):
1600
- shared: ReadableFuture
1612
+ shared: ReadableFuture| WritableFuture
1601
1613
copying: bool
1602
1614
done: bool
1603
1615
0 commit comments