@@ -1525,7 +1525,8 @@ class CopyState(Enum):
15251525 IDLE = 1
15261526 SYNC_COPYING = 2
15271527 ASYNC_COPYING = 3
1528- DONE = 4
1528+ CANCELLING_COPY = 4
1529+ DONE = 5
15291530
15301531class CopyEnd (Waitable ):
15311532 state: CopyState
@@ -1537,7 +1538,12 @@ class CopyEnd(Waitable):
15371538 self .shared = shared
15381539
15391540 def copying (self ):
1540- return self .state == CopyState.SYNC_COPYING or self .state == CopyState.ASYNC_COPYING
1541+ match self .state:
1542+ case CopyState.IDLE | CopyState.DONE :
1543+ return False
1544+ case CopyState.SYNC_COPYING | CopyState.ASYNC_COPYING | CopyState.CANCELLING_COPY :
1545+ return True
1546+ assert (False )
15411547
15421548 def drop (self ):
15431549 trap_if(self .copying())
@@ -1553,11 +1559,11 @@ class WritableStreamEnd(CopyEnd):
15531559 self .shared.write(inst, src, on_copy, on_copy_done)
15541560```
15551561As shown in ` drop ` , attempting to drop a readable or writable end while a copy
1556- is in progress traps. This means that client code must take care to wait for
1557- these operations to finish (potentially cancelling them via
1558- ` stream.cancel-{read,write} ` ) before dropping. The ` SYNC_COPY ` vs. ` ASYNC_COPY `
1559- distinction is tracked in the state to determine whether the copy operation can
1560- be cancelled.
1562+ is in progress or in the process of being cancelled traps. This means that
1563+ client code must take care to wait for these operations to finish (potentially
1564+ cancelling them via ` stream.cancel-{read,write} ` ) before dropping. The
1565+ ` SYNC_COPY ` vs. ` ASYNC_COPY ` distinction is tracked in the state to determine
1566+ whether the copy operation can be cancelled.
15611567
15621568The polymorphic ` copy ` method dispatches to either ` ReadableStream.read ` or
15631569` WritableStream.write ` and allows the implementations of ` stream.{read,write} `
@@ -4270,6 +4276,7 @@ def cancel_copy(EndT, event_code, stream_or_future_t, async_, thread, i):
42704276 trap_if(not isinstance (e, EndT))
42714277 trap_if(e.shared.t != stream_or_future_t.t)
42724278 trap_if(e.state != CopyState.ASYNC_COPYING )
4279+ e.state = CopyState.CANCELLING_COPY
42734280 if not e.has_pending_event():
42744281 e.shared.cancel()
42754282 if not e.has_pending_event():
@@ -4286,7 +4293,8 @@ unconditionally traps if it transitively attempts to make a synchronous call to
42864293` cancel-read ` or ` cancel-write ` (regardless of whether the cancellation would
42874294have completed without blocking). There is also a trap if there is not
42884295currently an async copy in progress (sync copies do not expect or check for
4289- cancellation and thus cannot be cancelled).
4296+ cancellation and thus cannot be cancelled, and repeatedly cancelling the same
4297+ async copy after the first call blocked is not allowed).
42904298
42914299The * first* check for ` e.has_pending_event() ` catches the case where the copy has
42924300already racily finished, in which case we must * not* call ` cancel() ` . Calling
0 commit comments