You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[v0.3] Expand documentation of "result of transmission" futures #777
I know I have spent a fair bit of time discussing, forgetting, and trying to remember precisely what these futures are for and how best to plumb them together, and I'm sure I'm not the only one. Let's try to get that information written down in this issue and distilled into a bit more doc commentary.
I'll start with my best understanding, to be corrected:
The future returned from {request,response}.new represents the final result of abstractly "transmitting" the message body and trailers. When "transmitting" involves actual I/O this future represents the host's best understanding of the result of "flushing" the message.
The future passed to {request,response}.consume-body is just the other end of this signal. In practice most guests will only use this to "proxy" one message into another of the same kind, e.g. request->request or response->response (notrequest -> response). Another example would be a component that implements wasi-http backed by wasi-sockets; in this case the future would be resolved with the result of the last send for the message.
Conceptually this is useful for requests because handle can return immediately after the response headers have been received where in the case of full-duplex request/response streaming the request body may still be in flight. In fact it seems to be allowable (though certainly rare) for the response to be completed before the request has finished streaming. Without this future there would be no way to reliably receive error information from failed transmission.
One example use case for this feature would be request retry middleware where a request body is buffered and held until this future resolves. It's a little harder for me to see compelling use cases on the response side, though you could always just log errors.
Questions:
When there is no further "transmission", such as when the request is being handled by a terminal HTTP service, what should be done with this future? Resolve it immediately? Resolve it after "processing" the request body?
What kinds of errors might consumers expect to see on these futures? Clearly in the I/O-backed case you could get e.g. "connection reset". Would content-length enforcement signal an error here?
If handle results in an error what should happen to this future?
More generally, is there any guarantee attached to successful resolution of the future? Or is it just a best-effort channel for signaling failures?
Agreed that more prose is necessary here and thanks so much for digging in! What you're saying sounds right. Digging into the questions:
When there is no further "transmission", such as when the request is being handled by a terminal HTTP service, what should be done with this future? Resolve it immediately? Resolve it after "processing" the request body?
I think the contract should be that the future is only resolved when the body is closed (to communicate the final success or failure of the transmission). That leaves open the question: if a request-consumer eagerly performs a future.write (if they know there's no chance they'll generate an error) and/or a response-producer eagerly performs a future.read (just to have it "ready to go"): are these reads/writes allowed to complete before the stream<u8> is closed?
Now, in a future where we had a stream<u8 then result<trailers, error-code>> reply-with result<_, error-code>> in WIT (I've been playing with the idea of replacing the commas in stream<T,U,V> with more readable keywords...) that was stream.split into a stream<u8>, readable future<result<trailers, error-code>> and writable future<result<_, error-code>>, then the answer would be "no": the reads/writes could be started, but there is an internal state machine that will cause them to block until the stream<u8> is closed and the result<trailers, error-code> is successfully delivered. I believe this behavior is implementable in 0.3.0 even in component-to-component cases because the common wasi:http/types implementation of {request,response}.new and {request,response}.consume-body can inject itself between the readable/writable ends to delay the rendezvous. So unless this ends up being hard to implement, then perhaps we should specify ordering constraints on the completion of reads/writes of the futures that client code (of wasi:http/types) can depend on. These ordering guarantees might be nice for understanding these futures too.
What kinds of errors might consumers expect to see on these futures? Clearly in the I/O-backed case you could get e.g. "connection reset". Would content-length enforcement signal an error here?
I believe wasi:http/types should enforce content-length such that callers of {request,response}.new and {request,response}.consume-body can simply assume it has been checked (and they'll get an error if the condition was violated). (Allowing wasi:http/types to enforce content-length is what prompted the whole immutable-once-attached semantics of fields.)
If handle results in an error what should happen to this future?
I can't think of any invariants relating handle's result and the request body futures. Especially due to the concurrency of various operations, it seems like they're all independent. E.g., streaming a request could fail concurrently with a response's headers arriving, such that handle would race returning ok(response) with error(error-code) from the request future.
More generally, is there any guarantee attached to successful resolution of the future? Or is it just a best-effort channel for signaling failures?
Mostly the latter, although I suppose we could assign a "that's the very end of the HTTP call, there are no more failures possible for this call" significance to a response body's futures.
Activity
lann commentedon Oct 3, 2025
I'll start with my best understanding, to be corrected:
The future returned from
{request,response}.newrepresents the final result of abstractly "transmitting" the message body and trailers. When "transmitting" involves actual I/O this future represents the host's best understanding of the result of "flushing" the message.The future passed to
{request,response}.consume-bodyis just the other end of this signal. In practice most guests will only use this to "proxy" one message into another of the same kind, e.g.request->requestorresponse->response(notrequest->response). Another example would be a component that implements wasi-http backed by wasi-sockets; in this case the future would be resolved with the result of the lastsendfor the message.Conceptually this is useful for requests because
handlecan return immediately after the response headers have been received where in the case of full-duplex request/response streaming the request body may still be in flight. In fact it seems to be allowable (though certainly rare) for the response to be completed before the request has finished streaming. Without this future there would be no way to reliably receive error information from failed transmission.One example use case for this feature would be request retry middleware where a request body is buffered and held until this future resolves. It's a little harder for me to see compelling use cases on the response side, though you could always just log errors.
Questions:
content-lengthenforcement signal an error here?handleresults in an error what should happen to this future?lukewagner commentedon Oct 9, 2025
Agreed that more prose is necessary here and thanks so much for digging in! What you're saying sounds right. Digging into the questions:
I think the contract should be that the future is only resolved when the body is closed (to communicate the final success or failure of the transmission). That leaves open the question: if a request-consumer eagerly performs a
future.write(if they know there's no chance they'll generate an error) and/or a response-producer eagerly performs afuture.read(just to have it "ready to go"): are these reads/writes allowed to complete before thestream<u8>is closed?Now, in a future where we had a
stream<u8 then result<trailers, error-code>> reply-with result<_, error-code>>in WIT (I've been playing with the idea of replacing the commas instream<T,U,V>with more readable keywords...) that wasstream.splitinto astream<u8>, readablefuture<result<trailers, error-code>>and writablefuture<result<_, error-code>>, then the answer would be "no": the reads/writes could be started, but there is an internal state machine that will cause them to block until thestream<u8>is closed and theresult<trailers, error-code>is successfully delivered. I believe this behavior is implementable in 0.3.0 even in component-to-component cases because the commonwasi:http/typesimplementation of{request,response}.newand{request,response}.consume-bodycan inject itself between the readable/writable ends to delay the rendezvous. So unless this ends up being hard to implement, then perhaps we should specify ordering constraints on the completion of reads/writes of the futures that client code (ofwasi:http/types) can depend on. These ordering guarantees might be nice for understanding these futures too.I believe
wasi:http/typesshould enforce content-length such that callers of{request,response}.newand{request,response}.consume-bodycan simply assume it has been checked (and they'll get an error if the condition was violated). (Allowingwasi:http/typesto enforce content-length is what prompted the whole immutable-once-attached semantics offields.)I can't think of any invariants relating
handle'sresultand therequestbody futures. Especially due to the concurrency of various operations, it seems like they're all independent. E.g., streaming a request could fail concurrently with a response's headers arriving, such thathandlewould race returningok(response)witherror(error-code)from therequestfuture.Mostly the latter, although I suppose we could assign a "that's the very end of the HTTP call, there are no more failures possible for this call" significance to a
responsebody's futures.