@@ -354,7 +354,7 @@ values *into* the buffer's memory. Buffers are represented by the following 3
354
354
abstract Python classes:
355
355
``` python
356
356
class Buffer :
357
- MAX_LENGTH = 2 ** 30 - 1
357
+ MAX_LENGTH = 2 ** 28 - 1
358
358
t: ValType
359
359
remain: Callable[[], int ]
360
360
@@ -1056,7 +1056,7 @@ stream.)
1056
1056
``` python
1057
1057
RevokeBuffer = Callable[[], None ]
1058
1058
OnPartialCopy = Callable[[RevokeBuffer], None ]
1059
- OnCopyDone = Callable[[], None ]
1059
+ OnCopyDone = Callable[[Literal[ ' completed ' , ' cancelled ' ] ], None ]
1060
1060
1061
1061
class ReadableStream :
1062
1062
t: ValType
@@ -1069,7 +1069,8 @@ The key operation is `read` which works as follows:
1069
1069
* ` read ` is non-blocking, returning ` 'blocked' ` if it would have blocked.
1070
1070
* The ` On* ` callbacks are only called * after* ` read ` returns ` 'blocked' ` .
1071
1071
* ` OnCopyDone ` is called to indicate that the caller has regained ownership of
1072
- the buffer.
1072
+ the buffer and whether this was due to the read/write completing or
1073
+ being cancelled.
1073
1074
* ` OnPartialCopy ` is called to indicate a partial write has been made to the
1074
1075
buffer, but there may be further writes made in the future, so the caller
1075
1076
has * not* regained ownership of the buffer.
@@ -1119,21 +1120,21 @@ If set, the `pending_*` fields record the `Buffer` and `On*` callbacks of a
1119
1120
` read ` . Closing the readable or writable end of a stream or cancelling a ` read `
1120
1121
or ` write ` notifies any pending ` read ` or ` write ` via its ` OnCopyDone `
1121
1122
callback, which lets the other side know that ownership of the ` Buffer ` has
1122
- been returned:
1123
+ been returned and why :
1123
1124
``` python
1124
- def reset_and_notify_pending (self ):
1125
+ def reset_and_notify_pending (self , why ):
1125
1126
pending_on_copy_done = self .pending_on_copy_done
1126
1127
self .reset_pending()
1127
- pending_on_copy_done()
1128
+ pending_on_copy_done(why )
1128
1129
1129
1130
def cancel (self ):
1130
- self .reset_and_notify_pending()
1131
+ self .reset_and_notify_pending(' cancelled ' )
1131
1132
1132
1133
def close (self ):
1133
1134
if not self .closed_:
1134
1135
self .closed_ = True
1135
1136
if self .pending_buffer:
1136
- self .reset_and_notify_pending()
1137
+ self .reset_and_notify_pending(' completed ' )
1137
1138
1138
1139
def closed (self ):
1139
1140
return self .closed_
@@ -1179,7 +1180,7 @@ but in the opposite direction. Both are implemented by a single underlying
1179
1180
if self .pending_buffer.remain() > 0 :
1180
1181
self .pending_on_partial_copy(self .reset_pending)
1181
1182
else :
1182
- self .reset_and_notify_pending()
1183
+ self .reset_and_notify_pending(' completed ' )
1183
1184
return ' done'
1184
1185
```
1185
1186
Currently, there is a trap when both the ` read ` and ` write ` come from the same
@@ -1243,10 +1244,10 @@ and closing once a value has been read-from or written-to the given buffer:
1243
1244
class FutureEnd (StreamEnd ):
1244
1245
def close_after_copy (self , copy_op , inst , buffer , on_copy_done ):
1245
1246
assert (buffer.remain() == 1 )
1246
- def on_copy_done_wrapper ():
1247
+ def on_copy_done_wrapper (why ):
1247
1248
if buffer.remain() == 0 :
1248
1249
self .stream.close()
1249
- on_copy_done()
1250
+ on_copy_done(why )
1250
1251
ret = copy_op(inst, buffer, on_partial_copy = None , on_copy_done = on_copy_done_wrapper)
1251
1252
if ret == ' done' and buffer.remain() == 0 :
1252
1253
self .stream.close()
@@ -3521,7 +3522,8 @@ multiple partial copies before having to context-switch back.
3521
3522
``` python
3522
3523
if opts.sync:
3523
3524
final_revoke_buffer = None
3524
- def on_partial_copy (revoke_buffer ):
3525
+ def on_partial_copy (revoke_buffer , why = ' completed' ):
3526
+ assert (why == ' completed' )
3525
3527
nonlocal final_revoke_buffer
3526
3528
final_revoke_buffer = revoke_buffer
3527
3529
if not async_copy.done():
@@ -3532,6 +3534,8 @@ multiple partial copies before having to context-switch back.
3532
3534
await task.wait_on(async_copy, sync = True )
3533
3535
final_revoke_buffer()
3534
3536
```
3537
+ (When non-cooperative threads are added, the assertion that synchronous copies
3538
+ can only be ` completed ` , and not ` cancelled ` , will no longer hold.)
3535
3539
3536
3540
In the asynchronous case, the ` on_* ` callbacks set a pending event on the
3537
3541
` Waitable ` which will be delivered to core wasm when core wasm calls
@@ -3542,36 +3546,46 @@ allowing multiple partial copies to complete in the interim, reducing overall
3542
3546
context-switching overhead.
3543
3547
``` python
3544
3548
else :
3545
- def copy_event (revoke_buffer ):
3549
+ def copy_event (why , revoke_buffer ):
3546
3550
revoke_buffer()
3547
3551
e.copying = False
3548
- return (event_code, i, pack_copy_result(task, buffer, e ))
3552
+ return (event_code, i, pack_copy_result(task, e, buffer, why ))
3549
3553
def on_partial_copy (revoke_buffer ):
3550
- e.set_event(partial(copy_event, revoke_buffer))
3551
- def on_copy_done ():
3552
- e.set_event(partial(copy_event, revoke_buffer = lambda :()))
3554
+ e.set_event(partial(copy_event, ' completed ' , revoke_buffer))
3555
+ def on_copy_done (why ):
3556
+ e.set_event(partial(copy_event, why, revoke_buffer = lambda :()))
3553
3557
if e.copy(task.inst, buffer, on_partial_copy, on_copy_done) != ' done' :
3554
3558
e.copying = True
3555
3559
return [BLOCKED ]
3556
- return [pack_copy_result(task, buffer, e )]
3560
+ return [pack_copy_result(task, e, buffer, ' completed ' )]
3557
3561
```
3558
3562
However the copy completes, the results are reported to the caller via
3559
3563
` pack_copy_result ` :
3560
3564
``` python
3561
- BLOCKED = 0x ffff_ffff
3562
- CLOSED = 0x 8000_0000
3565
+ BLOCKED = 0x ffff_ffff
3566
+ COMPLETED = 0x 0
3567
+ CLOSED = 0x 1
3568
+ CANCELLED = 0x 2
3563
3569
3564
- def pack_copy_result (task , buffer , e ):
3565
- if buffer.progress or not e.stream.closed():
3566
- assert (buffer.progress <= Buffer. MAX_LENGTH < BLOCKED )
3567
- assert ( not (buffer.progress & CLOSED ))
3568
- return buffer.progress
3570
+ def pack_copy_result (task , e , buffer , why ):
3571
+ if e.stream.closed():
3572
+ result = CLOSED
3573
+ elif why == ' cancelled ' :
3574
+ result = CANCELLED
3569
3575
else :
3570
- return CLOSED
3571
- ```
3572
- The order of tests here indicates that, if some progress was made and then the
3573
- stream was closed, only the progress is reported and the ` CLOSED ` status is
3574
- left to be discovered next time.
3576
+ assert (why == ' completed' )
3577
+ assert (not isinstance (e, FutureEnd))
3578
+ result = COMPLETED
3579
+ assert (buffer.progress <= Buffer.MAX_LENGTH < 2 ** 28 )
3580
+ packed = result | (buffer.progress << 4 )
3581
+ assert (packed != BLOCKED )
3582
+ return packed
3583
+ ```
3584
+ The ` result ` indicates whether the stream was closed by the other end, the
3585
+ copy was cancelled by this end (via ` {stream,future}.cancel-{read,write} ` ) or,
3586
+ otherwise, completed successfully. In all cases, any number of elements (from
3587
+ ` 0 ` to ` n ` ) may have * first* been copied into or out of the buffer passed to
3588
+ the ` read ` or ` write ` and so this number is packed into the ` i32 ` result.
3575
3589
3576
3590
3577
3591
### 🔀 ` canon {stream,future}.cancel-{read,write} `
0 commit comments