Skip to content

Commit 0b951b7

Browse files
lukewagnerdicej
andauthored
Clarify {stream,future}.cancel-{read,write} (#593)
* Trap on call to {stream,future}.cancel-{read,write} after a prior cancellation blocked * Add comma Co-authored-by: Joel Dice <joel.dice@fermyon.com> --------- Co-authored-by: Joel Dice <joel.dice@fermyon.com>
1 parent f136418 commit 0b951b7

File tree

3 files changed

+30
-10
lines changed

3 files changed

+30
-10
lines changed

design/mvp/CanonicalABI.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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

15301531
class 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
```
15551561
As 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

15621568
The 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
42874294
have completed without blocking). There is also a trap if there is not
42884295
currently 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

42914299
The *first* check for `e.has_pending_event()` catches the case where the copy has
42924300
already racily finished, in which case we must *not* call `cancel()`. Calling

design/mvp/canonical-abi/definitions.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -886,7 +886,8 @@ class CopyState(Enum):
886886
IDLE = 1
887887
SYNC_COPYING = 2
888888
ASYNC_COPYING = 3
889-
DONE = 4
889+
CANCELLING_COPY = 4
890+
DONE = 5
890891

891892
class CopyEnd(Waitable):
892893
state: CopyState
@@ -898,7 +899,12 @@ def __init__(self, shared):
898899
self.shared = shared
899900

900901
def copying(self):
901-
return self.state == CopyState.SYNC_COPYING or self.state == CopyState.ASYNC_COPYING
902+
match self.state:
903+
case CopyState.IDLE | CopyState.DONE:
904+
return False
905+
case CopyState.SYNC_COPYING | CopyState.ASYNC_COPYING | CopyState.CANCELLING_COPY:
906+
return True
907+
assert(False)
902908

903909
def drop(self):
904910
trap_if(self.copying())
@@ -2431,6 +2437,7 @@ def cancel_copy(EndT, event_code, stream_or_future_t, async_, thread, i):
24312437
trap_if(not isinstance(e, EndT))
24322438
trap_if(e.shared.t != stream_or_future_t.t)
24332439
trap_if(e.state != CopyState.ASYNC_COPYING)
2440+
e.state = CopyState.CANCELLING_COPY
24342441
if not e.has_pending_event():
24352442
e.shared.cancel()
24362443
if not e.has_pending_event():

design/mvp/canonical-abi/run_tests.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2031,6 +2031,11 @@ def core_func(thread, args):
20312031
host_source.block_cancel()
20322032
[ret] = canon_stream_cancel_read(StreamType(U8Type()), True, thread, rsi)
20332033
assert(ret == definitions.BLOCKED)
2034+
try:
2035+
canon_stream_cancel_read(StreamType(U8Type()), True, thread, rsi)
2036+
assert(False)
2037+
except Trap:
2038+
pass
20342039
host_source.write([7,8])
20352040
host_source.unblock_cancel()
20362041
[seti] = canon_waitable_set_new(thread)

0 commit comments

Comments
 (0)