@@ -1507,8 +1507,9 @@ out into the `CopyEnd` class that is derived below:
1507
1507
``` python
1508
1508
class CopyState (Enum ):
1509
1509
IDLE = 1
1510
- COPYING = 2
1511
- DONE = 3
1510
+ SYNC_COPYING = 2
1511
+ ASYNC_COPYING = 3
1512
+ DONE = 4
1512
1513
1513
1514
class CopyEnd (Waitable ):
1514
1515
state: CopyState
@@ -1517,14 +1518,19 @@ class CopyEnd(Waitable):
1517
1518
Waitable.__init__ (self )
1518
1519
self .state = CopyState.IDLE
1519
1520
1521
+ def copying (self ):
1522
+ return self .state == CopyState.SYNC_COPYING or self .state == CopyState.ASYNC_COPYING
1523
+
1520
1524
def drop (self ):
1521
- trap_if(self .state == CopyState. COPYING )
1525
+ trap_if(self .copying() )
1522
1526
Waitable.drop(self )
1523
1527
```
1524
1528
As shown in ` drop ` , attempting to drop a readable or writable end while a copy
1525
1529
is in progress traps. This means that client code must take care to wait for
1526
1530
these operations to finish (potentially cancelling them via
1527
- ` stream.cancel-{read,write} ` ) before dropping.
1531
+ ` stream.cancel-{read,write} ` ) before dropping. The ` SYNC_COPY ` vs. ` ASYNC_COPY `
1532
+ distinction is tracked in the state to determine whether the copy operation can
1533
+ be cancelled.
1528
1534
1529
1535
Given the above, we can define the concrete ` {Readable,Writable}StreamEnd `
1530
1536
classes which are almost entirely symmetric, with the only difference being
@@ -4083,7 +4089,6 @@ until this point into a single `i32` payload for core wasm.
4083
4089
``` python
4084
4090
def stream_event (result , reclaim_buffer ):
4085
4091
reclaim_buffer()
4086
- assert (e.state == CopyState.COPYING )
4087
4092
if result == CopyResult.DROPPED :
4088
4093
e.state = CopyState.DONE
4089
4094
else :
@@ -4099,7 +4104,6 @@ until this point into a single `i32` payload for core wasm.
4099
4104
def on_copy_done (result ):
4100
4105
e.set_pending_event(partial(stream_event, result, reclaim_buffer = lambda :()))
4101
4106
4102
- e.state = CopyState.COPYING
4103
4107
e.copy(thread.task.inst, buffer, on_copy, on_copy_done)
4104
4108
```
4105
4109
@@ -4111,8 +4115,10 @@ synchronously and return `BLOCKED` if not:
4111
4115
``` python
4112
4116
if not e.has_pending_event():
4113
4117
if opts.sync:
4118
+ e.state = CopyState.SYNC_COPYING
4114
4119
thread.suspend_until(e.has_pending_event)
4115
4120
else :
4121
+ e.state = CopyState.ASYNC_COPYING
4116
4122
return [BLOCKED ]
4117
4123
code,index,payload = e.get_pending_event()
4118
4124
assert (code == event_code and index == i and payload != BLOCKED )
@@ -4177,7 +4183,6 @@ of elements copied is not packed in the high 28 bits; they're always zero.
4177
4183
``` python
4178
4184
def future_event (result ):
4179
4185
assert ((buffer.remain() == 0 ) == (result == CopyResult.COMPLETED ))
4180
- assert (e.state == CopyState.COPYING )
4181
4186
if result == CopyResult.DROPPED or result == CopyResult.COMPLETED :
4182
4187
e.state = CopyState.DONE
4183
4188
else :
@@ -4188,7 +4193,6 @@ of elements copied is not packed in the high 28 bits; they're always zero.
4188
4193
assert (result != CopyResult.DROPPED or event_code == EventCode.FUTURE_WRITE )
4189
4194
e.set_pending_event(partial(future_event, result))
4190
4195
4191
- e.state = CopyState.COPYING
4192
4196
e.copy(thread.task.inst, buffer, on_copy_done)
4193
4197
```
4194
4198
@@ -4197,8 +4201,10 @@ and returning either the progress made or `BLOCKED`.
4197
4201
``` python
4198
4202
if not e.has_pending_event():
4199
4203
if opts.sync:
4204
+ e.state = CopyState.SYNC_COPYING
4200
4205
thread.suspend_until(e.has_pending_event)
4201
4206
else :
4207
+ e.state = CopyState.ASYNC_COPYING
4202
4208
return [BLOCKED ]
4203
4209
code,index,payload = e.get_pending_event()
4204
4210
assert (code == event_code and index == i)
@@ -4240,7 +4246,7 @@ def cancel_copy(EndT, event_code, stream_or_future_t, sync, thread, i):
4240
4246
e = thread.task.inst.table.get(i)
4241
4247
trap_if(not isinstance (e, EndT))
4242
4248
trap_if(e.shared.t != stream_or_future_t.t)
4243
- trap_if(e.state != CopyState.COPYING )
4249
+ trap_if(e.state != CopyState.ASYNC_COPYING )
4244
4250
if not e.has_pending_event():
4245
4251
e.shared.cancel()
4246
4252
if not e.has_pending_event():
@@ -4249,9 +4255,12 @@ def cancel_copy(EndT, event_code, stream_or_future_t, sync, thread, i):
4249
4255
else :
4250
4256
return [BLOCKED ]
4251
4257
code,index,payload = e.get_pending_event()
4252
- assert (e.state != CopyState. COPYING and code == event_code and index == i)
4258
+ assert (not e.copying() and code == event_code and index == i)
4253
4259
return [payload]
4254
4260
```
4261
+ Cancellation traps if there is not currently an async copy in progress (sync
4262
+ copies do not expect or check for cancellation and thus cannot be cancelled).
4263
+
4255
4264
The * first* check for ` e.has_pending_event() ` catches the case where the copy has
4256
4265
already racily finished, in which case we must * not* call ` cancel() ` . Calling
4257
4266
` cancel() ` may, but is not required to, recursively call one of the ` on_* `
0 commit comments