Skip to content

Commit edeee6e

Browse files
authored
Clarify zero-length read documentation for components (#12039)
Update preexisting documentation with WebAssembly/component-model#565 and also reformat `poll_produce` docs to have sections and be a bit less than a wall-of-text.
1 parent 3f285db commit edeee6e

File tree

1 file changed

+118
-49
lines changed

1 file changed

+118
-49
lines changed

crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs

Lines changed: 118 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -263,12 +263,16 @@ impl<'a, T, B> Destination<'a, T, B> {
263263
/// This will return `Some(_)` if the reader is a guest; it will return
264264
/// `None` if the reader is the host.
265265
///
266-
/// Note that, if this returns `None(0)`, the producer must still attempt to
267-
/// produce at least one item if the value of `finish` passed to
268-
/// `StreamProducer::poll_produce` is false. In that case, the reader is
269-
/// effectively asking when the producer will be able to produce items
270-
/// without blocking (or reach a terminal state such as end-of-stream),
271-
/// meaning the next non-zero read must complete without blocking.
266+
/// Note that this can return `Some(0)`. This means that the guest is
267+
/// attempting to perform a zero-length read which typically means that it's
268+
/// trying to wait for this stream to be ready-to-read but is not actually
269+
/// ready to receive the items yet. The host in this case is allowed to
270+
/// either block waiting for readiness or immediately complete the
271+
/// operation. The guest is expected to handle both cases. Some more
272+
/// discussion about this case can be found in the discussion of ["Stream
273+
/// Readiness" in the component-model repo][docs].
274+
///
275+
/// [docs]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Concurrency.md#stream-readiness
272276
pub fn remaining(&self, mut store: impl AsContextMut) -> Option<usize> {
273277
let transmit = store
274278
.as_context_mut()
@@ -437,6 +441,21 @@ pub trait StreamProducer<D>: Send + 'static {
437441
///
438442
/// This will be called whenever the reader starts a read.
439443
///
444+
/// # Arguments
445+
///
446+
/// * `self` - a `Pin`'d version of self to perform Rust-level
447+
/// future-related operations on.
448+
/// * `cx` - a Rust-related [`Context`] which is passed to other
449+
/// future-related operations or used to acquire a waker.
450+
/// * `store` - the Wasmtime store that this operation is happening within.
451+
/// Used, for example, to consult the state `D` associated with the store.
452+
/// * `destination` - the location that items are to be written to.
453+
/// * `finish` - a flag indicating whether the host should strive to
454+
/// immediately complete/cancel any pending operation. See below for more
455+
/// details.
456+
///
457+
/// # Behavior
458+
///
440459
/// If the implementation is able to produce one or more items immediately,
441460
/// it should write them to `destination` and return either
442461
/// `Poll::Ready(Ok(StreamResult::Completed))` if it expects to produce more
@@ -449,59 +468,109 @@ pub trait StreamProducer<D>: Send + 'static {
449468
/// anything to `destination`. Later, it should alert the waker when either
450469
/// the items arrive, the stream has ended, or an error occurs.
451470
///
452-
/// If the implementation is unable to produce any items immediately, but
453-
/// expects to do so later, and `finish` is _true_, it should, if possible,
454-
/// return `Poll::Ready(Ok(StreamResult::Cancelled))` immediately without
455-
/// writing anything to `destination`. However, that might not be possible
456-
/// if an earlier call to `poll_produce` kicked off an asynchronous
457-
/// operation which needs to be completed (and possibly interrupted)
458-
/// gracefully, in which case the implementation may return `Poll::Pending`
459-
/// and later alert the waker as described above. In other words, when
460-
/// `finish` is true, the implementation should prioritize returning a
461-
/// result to the reader (even if no items can be produced) rather than wait
462-
/// indefinitely for at least one item to arrive.
463-
///
464-
/// In all of the above cases, the implementation may alternatively choose
465-
/// to return `Err(_)` to indicate an unrecoverable error. This will cause
466-
/// the guest (if any) to trap and render the component instance (if any)
467-
/// unusable. The implementation should report errors that _are_
468-
/// recoverable by other means (e.g. by writing to a `future`) and return
469-
/// `Poll::Ready(Ok(StreamResult::Dropped))`.
470-
///
471-
/// Note that the implementation should never return `Poll::Pending` after
472-
/// writing one or more items to `destination`; if it does, the caller will
473-
/// trap as if `Err(_)` was returned. Conversely, it should only return
474-
/// `Poll::Ready(Ok(StreamResult::Cancelled))` without writing any items to
475-
/// `destination` if called with `finish` set to true. If it does so when
476-
/// `finish` is false, the caller will trap. Additionally, it should only
477-
/// return `Poll::Ready(Ok(StreamResult::Completed))` after writing at least
478-
/// one item to `destination` if it has capacity to accept that item;
479-
/// otherwise, the caller will trap.
480-
///
481471
/// If more items are written to `destination` than the reader has immediate
482472
/// capacity to accept, they will be retained in memory by the caller and
483473
/// used to satisfy future reads, in which case `poll_produce` will only be
484474
/// called again once all those items have been delivered.
485475
///
486-
/// If this function is called with zero capacity
487-
/// (i.e. `Destination::remaining` returns `Some(0)`), the implementation
488-
/// should either:
476+
/// # Zero-length reads
477+
///
478+
/// This function may be called with a zero-length capacity buffer
479+
/// (i.e. `Destination::remaining` returns `Some(0)`). This indicates that
480+
/// the guest wants to wait to see if an item is ready without actually
481+
/// reading the item. For example think of a UNIX `poll` function run on a
482+
/// TCP stream, seeing if it's readable without actually reading it.
483+
///
484+
/// In this situation the host is allowed to either return immediately or
485+
/// wait for readiness. Note that waiting for readiness is not always
486+
/// possible. For example it's impossible to test if a Rust-native `Future`
487+
/// is ready without actually reading the item. Stream-specific
488+
/// optimizations, such as testing if a TCP stream is readable, may be
489+
/// possible however.
490+
///
491+
/// For a zero-length read, the host is allowed to:
489492
///
490493
/// - Return `Poll::Ready(Ok(StreamResult::Completed))` without writing
491-
/// anything if it expects to be able to produce items immediately
492-
/// (i.e. without first returning `Poll::Pending`) the next time
493-
/// `poll_produce` is called with non-zero capacity _or_ if that cannot be
494-
/// reliably determined.
494+
/// anything if it expects to be able to produce items immediately (i.e.
495+
/// without first returning `Poll::Pending`) the next time `poll_produce`
496+
/// is called with non-zero capacity. This is the best-case scenario of
497+
/// fulfilling the guest's desire -- items aren't read/buffered but the
498+
/// host is saying it's ready when the guest is.
499+
///
500+
/// - Return `Poll::Ready(Ok(StreamResult::Completed))` without actually
501+
/// testing for readiness. The guest doesn't know this yet, but the guest
502+
/// will realize that zero-length reads won't work on this stream when a
503+
/// subsequent nonzero read attempt is made which returns `Poll::Pending`
504+
/// here.
495505
///
496-
/// - Return `Poll::Pending` if the next call to `poll_produce` with
497-
/// non-zero capacity is likely to also return `Poll::Pending`.
506+
/// - Return `Poll::Pending` if the host has performed necessary async work
507+
/// to wait for this stream to be readable without actually reading
508+
/// anything. This is also a best-case scenario where the host is letting
509+
/// the guest know that nothing is ready yet. Later the zero-length read
510+
/// will complete and then the guest will attempt a nonzero-length read to
511+
/// actually read some bytes.
498512
///
499513
/// - Return `Poll::Ready(Ok(StreamResult::Completed))` after calling
500-
/// `Destination::set_buffer` with one more more items. Note, however, that
501-
/// this creates the hazard that the items will never be received by the
502-
/// guest if it decides not to do another non-zero-length read before
503-
/// closing the stream. Moreover, if `Self::Item` is e.g. a `Resource<_>`,
504-
/// they may end up leaking in that scenario.
514+
/// `Destination::set_buffer` with one more more items. Note, however,
515+
/// that this creates the hazard that the items will never be received by
516+
/// the guest if it decides not to do another non-zero-length read before
517+
/// closing the stream. Moreover, if `Self::Item` is e.g. a
518+
/// `Resource<_>`, they may end up leaking in that scenario. It is not
519+
/// recommended to do this and it's better to return
520+
/// `StreamResult::Completed` without buffering anything instead.
521+
///
522+
/// For more discussion on zero-length reads see the [documentation in the
523+
/// component-model repo itself][docs].
524+
///
525+
/// [docs]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Concurrency.md#stream-readiness
526+
///
527+
/// # Return
528+
///
529+
/// This function can return a number of possible cases from this function:
530+
///
531+
/// * `Poll::Pending` - this operation cannot complete at this time. The
532+
/// Rust-level `Future::poll` contract applies here where a waker should
533+
/// be stored from the `cx` argument and be arranged to receive a
534+
/// notification when this implementation can make progress. For example
535+
/// if you call `Future::poll` on a sub-future, that's enough. If items
536+
/// were written to `destination` then a trap in the guest will be raised.
537+
///
538+
/// Note that implementations should strive to avoid this return value
539+
/// when `finish` is `true`. In such a situation the guest is attempting
540+
/// to, for example, cancel a previous operation. By returning
541+
/// `Poll::Pending` the guest will be blocked during the cancellation
542+
/// request. If `finish` is `true` then `StreamResult::Cancelled` is
543+
/// favored to indicate that no items were read. If a short read happened,
544+
/// however, it's ok to return `StreamResult::Completed` indicating some
545+
/// items were read.
546+
///
547+
/// * `Poll::Ok(StreamResult::Completed)` - items, if applicable, were
548+
/// written to the `destination`.
549+
///
550+
/// * `Poll::Ok(StreamResult::Cancelled)` - used when `finish` is `true` and
551+
/// the implementation was able to successfully cancel any async work that
552+
/// a previous read kicked off, if any. The host should not buffer values
553+
/// received after returning `Cancelled` because the guest will not be
554+
/// aware of these values and the guest could close the stream after
555+
/// cancelling a read. Hosts should only return `Cancelled` when there are
556+
/// no more async operations in flight for a previous read.
557+
///
558+
/// If items were written to `destination` then a trap in the guest will
559+
/// be raised. If `finish` is `false` then this return value will raise a
560+
/// trap in the guest.
561+
///
562+
/// * `Poll::Ok(StreamResult::Dropped)` - end-of-stream marker, indicating
563+
/// that this producer should not be polled again. Note that items may
564+
/// still be written to `destination`.
565+
///
566+
/// # Errors
567+
///
568+
/// The implementation may alternatively choose to return `Err(_)` to
569+
/// indicate an unrecoverable error. This will cause the guest (if any) to
570+
/// trap and render the component instance (if any) unusable. The
571+
/// implementation should report errors that _are_ recoverable by other
572+
/// means (e.g. by writing to a `future`) and return
573+
/// `Poll::Ready(Ok(StreamResult::Dropped))`.
505574
fn poll_produce<'a>(
506575
self: Pin<&mut Self>,
507576
cx: &mut Context<'_>,

0 commit comments

Comments
 (0)