Hi, I'm trying to develop wasi-http programs but I've got a behavior that isn't really intuitive for me.
For example, for the following code snippet:
wit_bindgen::generate!({ generate_all });
use exports::wasi::http::incoming_handler::Guest;
use wasi::http::types::*;
struct HttpServer;
impl Guest for HttpServer {
fn handle(_request: IncomingRequest, response_out: ResponseOutparam) {
let response = OutgoingResponse::new(Fields::new());
let response_body = response.body().unwrap();
ResponseOutparam::set(response_out, Ok(response));
{
let write = response_body.write().unwrap();
write.write(b"[").unwrap();
write.write(b"{\"id\":\"0\"}").unwrap();
// write.write(b"]").unwrap(); // notice that this line is commented out
drop(write);
}
OutgoingBody::finish(response_body, None).expect("failed to finish response body");
}
}
export!(HttpServer);
With wasmtime 23.0.1, if I uncomment the line in the code snippet, the code would fail with write exceeded budget
. I wonder why writing a JSON would cause the available space to change?
The wasi io interfaces are somewhat difficult to use correctly, requiring careful reading of their documentation. See: https://github.com/WebAssembly/WASI/blob/main/wasip2/io/streams.wit#L141-L142
Precondition: check-write gave permit of Ok(n) and contents has a length of less than or equal to n. Otherwise, this function will trap.
In this particular case you could probably use blocking-write-and-flush
instead, which will behave more like a "normal" write
in other systems.
Yes, I understand that part. The problem is, if I check-write initially, it will give a very large number; but after printing the json object, it will become 0, and changing to blocking-write-and-flush
would just hang.
To be clear: if you use blocking-write-and-flush
, you need to use only that (not e.g. a mix of check-write
, write
, and blocking-write-and-flush
, which would probably result in weird behavior indeed). Also, you'll need to limit your buffer size to 4096 bytes per the documentation.
If you want to stick with check-write
/write
, you'll need to also use subscribe
, which returns a pollable
you can call ready
on (or pass to wasi:io/poll#poll
along with pollable
s) in order to wait for the stream to be ready for more data.
Joel Dice said:
To be clear: if you use
blocking-write-and-flush
, you need to use only that (not e.g. a mix ofcheck-write
,write
, andblocking-write-and-flush
, which would probably result in weird behavior indeed). Also, you'll need to limit your buffer size to 4096 bytes per the documentation.
Could you please elaborate? Because according to the document, the blocking-write-and-flush
is just a wrapper around check-write
subscribe
write
and flush
.
I guess my point is that if you're using blocking-write-and-flush
, then there's no need to call check-write
, subscribe
, write
, or flush
yourself; at best it would be redundant, and at worst it might confuse blocking-write-and-flush
depending on the ordering of calls.
To me, it would be akin to switching a POSIX socket between blocking and non-blocking operation, which is tricky to do correctly. It's much easier to pick one or the other and stick with it.
I was expecting that the blocking-write-and-flush
could be replaced by the pseudo code mentioned in the document, i.e. using check-write
subscribe
write
and flush
, so I wasn't sure where the mix
came from.
However my point in the example is that I'm not quite sure why writing thrice is different from writing once if there were enough space to write to.
Oh sure -- if you're looking to use check-write
, subscribe
, write
, and flush
instead of blocking-write-and-flush
, that should be fine. All I was cautioning against is mixing the two strategies. If you never had any intention of doing that, then great.
I'm guessing the writing thrice issue boils down to an implementation detail in wasmtime-wasi
.
I see. Thank you for your suggestion.
I think I should do more tests and dig a bit deeper in the implementation.
the implementation in wasmtime-wasi is sorta the crux of that whole crate (it took us months to get everything just right) but it is designed to be reused by other crates, e.g. wasmtime-wasi-http
so, if you look at the way wasmtime-wasi-http uses those wasmtime-wasi HostInputStream / HostOutputStream types, thats hopefully something that could work for your use case as well - if you need help understanding how it works, or if for some reason it doesnt, please let us know
I think I now understand the behavior better. Thanks a lot for your responses.
Till Schneidereit has marked this topic as resolved.
Last updated: Dec 23 2024 at 14:03 UTC