Skip to content

Commit 8694ef4

Browse files
committed
Deliver stream/future dropped events even when not reading/writing
1 parent 35e9769 commit 8694ef4

5 files changed

Lines changed: 652 additions & 8 deletions

File tree

design/mvp/CanonicalABI.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,7 +1265,6 @@ class Waitable:
12651265
wset.elems.append(self)
12661266

12671267
def drop(self):
1268-
assert(not self.has_pending_event())
12691268
assert(not self.has_sync_waiter)
12701269
self.join(None)
12711270
```
@@ -1646,7 +1645,7 @@ buffer of length `1`, the second half of case `3` and cases `4` and `5` do not
16461645
apply to futures. Enumerating the cases in the order that they are handled in
16471646
the code below:
16481647
1. The other end was racily dropped before this end could be notified, in which
1649-
case the call immediately completes, reporting `DROPPED` and nothing copied.
1648+
case `End.drop` already left a pending event and so there's nothing to do.
16501649
2. The other end has not currently provided a buffer, in which case this end
16511650
must block until the other end shows up with a buffer.
16521651
3. Both this and the other end have provided buffers that can copy at least 1
@@ -1668,7 +1667,7 @@ the code below:
16681667
assert(self.buffer is None)
16691668
self.state = End.State.COPYING
16701669
if self.other is None:
1671-
self.notify(End.Result.DROPPED, progress = 0)
1670+
assert(self.has_pending_event())
16721671
elif self.other.buffer is None:
16731672
self.buffer = buffer
16741673
elif buffer.remain() > 0 and self.other.buffer.remain() > 0:
@@ -1721,14 +1720,15 @@ cancellation. In the future, guest components may be given the same capability.
17211720

17221721
The `End.drop` method is called by `{stream,future}.drop-{readable,writable}` to
17231722
update the `other` end's state and possibly set a pending notification for the
1724-
other end, if doing so wouldn't clobber an already-pending notification.
1723+
other end, if the other end isn't already `DONE` and doing so wouldn't clobber
1724+
an already-pending notification.
17251725
```python
17261726
def drop(self):
17271727
assert(not self.copying_or_cancelling())
17281728
if self.other is not None:
17291729
assert(self is self.other.other)
17301730
self.other.other = None
1731-
if self.other.copying_or_cancelling() and not self.other.has_pending_event():
1731+
if self.other.state != End.State.DONE and not self.other.has_pending_event():
17321732
self.other.notify(End.Result.DROPPED)
17331733
self.other = None
17341734
Waitable.drop(self)

design/mvp/canonical-abi/definitions.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -742,7 +742,6 @@ def join(self, wset):
742742
wset.elems.append(self)
743743

744744
def drop(self):
745-
assert(not self.has_pending_event())
746745
assert(not self.has_sync_waiter)
747746
self.join(None)
748747

@@ -945,7 +944,7 @@ def copy(self, buffer: Buffer, is_read: bool):
945944
assert(self.buffer is None)
946945
self.state = End.State.COPYING
947946
if self.other is None:
948-
self.notify(End.Result.DROPPED, progress = 0)
947+
assert(self.has_pending_event())
949948
elif self.other.buffer is None:
950949
self.buffer = buffer
951950
elif buffer.remain() > 0 and self.other.buffer.remain() > 0:
@@ -980,7 +979,7 @@ def drop(self):
980979
if self.other is not None:
981980
assert(self is self.other.other)
982981
self.other.other = None
983-
if self.other.copying_or_cancelling() and not self.other.has_pending_event():
982+
if self.other.state != End.State.DONE and not self.other.has_pending_event():
984983
self.other.notify(End.Result.DROPPED)
985984
self.other = None
986985
Waitable.drop(self)

design/mvp/canonical-abi/run_tests.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1673,6 +1673,14 @@ def core_func(args):
16731673
lift_and_run(opts, inst, ft, core_func, on_start, on_resolve)
16741674
assert(host_writer.readable_end is return_value)
16751675

1676+
# A stream whose writable end has already been dropped can still be lowered
1677+
# into and lifted out of the component; whoever finally reads it sees DROPPED.
1678+
host_writer = HostWriter(U8Type(), [])
1679+
lift_and_run(opts, inst, ft, core_func, on_start, on_resolve)
1680+
assert(host_writer.readable_end is return_value)
1681+
host_reader = HostReader(return_value)
1682+
assert(host_reader.dropped and host_reader.take() == [])
1683+
16761684

16771685
def test_receive_own_stream():
16781686
store = Store()
@@ -2254,6 +2262,66 @@ def core_func(args):
22542262
lift_and_run(opts, inst, caller_ft, core_func, lambda:[], lambda _:())
22552263

22562264

2265+
def test_stream_drop_both_ends_while_idle():
2266+
store = Store()
2267+
inst = ComponentInstance(store)
2268+
mem = bytearray(24)
2269+
opts = mk_opts(memory=MemInst(mem, 'i32'), async_ = True)
2270+
stream_t = StreamType(U8Type())
2271+
future_t = FutureType(U8Type())
2272+
2273+
def core_func(args):
2274+
assert(len(args) == 0)
2275+
[] = canon_task_return([], opts, [])
2276+
[packed] = canon_stream_new(stream_t)
2277+
rsi,wsi = unpack_new_ends(packed)
2278+
[] = canon_stream_drop_writable(stream_t, wsi)
2279+
[] = canon_stream_drop_readable(stream_t, rsi)
2280+
2281+
# Dropping one end notifies the other end even when it has no read or write
2282+
# in flight: the DROPPED event is delivered (once) through the waitable set,
2283+
# after which the end is done and further reads trap.
2284+
retp = 8
2285+
[seti] = canon_waitable_set_new()
2286+
[packed] = canon_stream_new(stream_t)
2287+
rsi,wsi = unpack_new_ends(packed)
2288+
[] = canon_waitable_join(rsi, seti)
2289+
[] = canon_stream_drop_writable(stream_t, wsi)
2290+
[event] = canon_waitable_set_poll(MemInst(mem, 'i32'), seti, retp)
2291+
assert(event == EventCode.STREAM_READ)
2292+
assert(mem[retp+0] == rsi)
2293+
result,n = unpack_result(mem[retp+4])
2294+
assert(n == 0 and result == End.Result.DROPPED)
2295+
[event] = canon_waitable_set_poll(MemInst(mem, 'i32'), seti, retp)
2296+
assert(event == EventCode.NONE)
2297+
trapped = False
2298+
try:
2299+
canon_stream_read(stream_t, opts, rsi, 0, 4)
2300+
except Trap:
2301+
trapped = True
2302+
assert(trapped)
2303+
[] = canon_waitable_join(rsi, 0)
2304+
[] = canon_stream_drop_readable(stream_t, rsi)
2305+
2306+
# Same for the writable end of a future (using wait instead of poll); once
2307+
# notified, the writable end may be dropped without having written.
2308+
[packed] = canon_future_new(future_t)
2309+
rfi,wfi = unpack_new_ends(packed)
2310+
[] = canon_waitable_join(wfi, seti)
2311+
[] = canon_future_drop_readable(future_t, rfi)
2312+
[event] = canon_waitable_set_wait(MemInst(mem, 'i32'), seti, retp)
2313+
assert(event == EventCode.FUTURE_WRITE)
2314+
assert(mem[retp+0] == wfi)
2315+
assert(mem[retp+4] == End.Result.DROPPED)
2316+
[] = canon_waitable_join(wfi, 0)
2317+
[] = canon_future_drop_writable(future_t, wfi)
2318+
[] = canon_waitable_set_drop(seti)
2319+
return []
2320+
2321+
caller_ft = FuncType([], [], async_ = True)
2322+
lift_and_run(opts, inst, caller_ft, core_func, lambda:[], lambda _:())
2323+
2324+
22572325
def test_cancel_subtask():
22582326
store = Store()
22592327
ft = FuncType([U8Type()], [U8Type()], async_ = True)
@@ -2982,6 +3050,7 @@ def on_resolve(v):
29823050
test_cancel_copy()
29833051
test_futures()
29843052
test_future_drop_readable_with_pending_write()
3053+
test_stream_drop_both_ends_while_idle()
29853054
test_cancel_subtask()
29863055
test_self_copy(None)
29873056
test_self_copy(U8Type())

0 commit comments

Comments
 (0)