@@ -364,8 +364,8 @@ class BufferGuestImpl(Buffer):
364
364
length: int
365
365
366
366
def __init__ (self , t , cx , ptr , length ):
367
- trap_if(length == 0 or length > Buffer.MAX_LENGTH )
368
- if t:
367
+ trap_if(length > Buffer.MAX_LENGTH )
368
+ if t and length > 0 :
369
369
trap_if(ptr != align_to(ptr, alignment(t)))
370
370
trap_if(ptr + length * elem_size(t) > len (cx.opts.memory))
371
371
self .cx = cx
@@ -1104,9 +1104,12 @@ class ReadableStreamGuestImpl(ReadableStream):
1104
1104
self .reset_pending()
1105
1105
1106
1106
def reset_pending (self ):
1107
- self .pending_buffer = None
1108
- self .pending_on_partial_copy = None
1109
- self .pending_on_copy_done = None
1107
+ self .set_pending(None , None , None )
1108
+
1109
+ def set_pending (self , buffer , on_partial_copy , on_copy_done ):
1110
+ self .pending_buffer = buffer
1111
+ self .pending_on_partial_copy = on_partial_copy
1112
+ self .pending_on_copy_done = on_copy_done
1110
1113
```
1111
1114
The ` impl ` field records the component instance that created this stream and is
1112
1115
used by ` lower_stream ` below.
@@ -1168,20 +1171,44 @@ but in the opposite direction. Both are implemented by a single underlying
1168
1171
if self .closed_:
1169
1172
return ' done'
1170
1173
elif not self .pending_buffer:
1171
- self .pending_buffer = buffer
1172
- self .pending_on_partial_copy = on_partial_copy
1173
- self .pending_on_copy_done = on_copy_done
1174
+ self .set_pending(buffer, on_partial_copy, on_copy_done)
1174
1175
return ' blocked'
1175
1176
else :
1176
- ncopy = min (src.remain(), dst.remain())
1177
- assert (ncopy > 0 )
1178
- dst.write(src.read(ncopy))
1179
1177
if self .pending_buffer.remain() > 0 :
1180
- self .pending_on_partial_copy(self .reset_pending)
1178
+ if buffer.remain() > 0 :
1179
+ dst.write(src.read(min (src.remain(), dst.remain())))
1180
+ if self .pending_buffer.remain() > 0 :
1181
+ self .pending_on_partial_copy(self .reset_pending)
1182
+ else :
1183
+ self .reset_and_notify_pending()
1184
+ return ' done'
1181
1185
else :
1182
- self .reset_and_notify_pending()
1183
- return ' done'
1184
- ```
1186
+ if buffer.remain() > 0 or buffer is dst:
1187
+ self .reset_and_notify_pending()
1188
+ self .set_pending(buffer, on_partial_copy, on_copy_done)
1189
+ return ' blocked'
1190
+ else :
1191
+ return ' done'
1192
+ ```
1193
+ The meaning of a ` read ` or ` write ` when the length is ` 0 ` is that the caller is
1194
+ querying the "readiness" of the other side. When a ` 0 ` -length read/write
1195
+ rendezvous with a non-` 0 ` -length read/write, only the ` 0 ` -length read/write
1196
+ completes; the non-` 0 ` -length read/write is kept pending (and ready for a
1197
+ subsequent rendezvous).
1198
+
1199
+ In the corner case where a ` 0 ` -length read * and* write rendezvous, only the
1200
+ * writer* is notified of readiness. To avoid livelock, the Canonical ABI
1201
+ requires that a writer * must* (eventually) follow a completed ` 0 ` -length write
1202
+ with a non-` 0 ` -length write that is allowed to block (allowing the reader end
1203
+ to run and rendezvous with its own non-` 0 ` -length read). To implement a
1204
+ traditional ` O_NONBLOCK ` ` write() ` or ` sendmsg() ` API, a writer can use a
1205
+ buffering scheme in which, after ` select() ` (or a similar API) signals a file
1206
+ descriptor is ready to write, the next ` O_NONBLOCK ` ` write() ` /` sendmsg() ` on
1207
+ that file descriptor copies to an internal buffer and suceeds, issuing an
1208
+ ` async ` ` stream.write ` in the background and waiting for completion before
1209
+ signalling readiness again. Note that buffering only occurs when streaming
1210
+ between two components using non-blocking I/O; if either side is the host or a
1211
+ component using blocking or completion-based I/O, no buffering is necessary.
1185
1212
1186
1213
Given the above, we can define the ` {Readable,Writable}StreamEnd ` classes that
1187
1214
are actually stored in the ` waitables ` table. The classes are almost entirely
0 commit comments