Stream: wasmtime

Topic: ✔ HTTP server behavior


view this post on Zulip Zihang Ye (Aug 02 2024 at 06:10):

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?

view this post on Zulip Lann Martin (Aug 02 2024 at 13:05):

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.

WebAssembly System Interface. Contribute to WebAssembly/WASI development by creating an account on GitHub.

view this post on Zulip Zihang Ye (Aug 02 2024 at 13:38):

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.

view this post on Zulip Joel Dice (Aug 02 2024 at 14:19):

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 pollables) in order to wait for the stream to be ready for more data.

view this post on Zulip Zihang Ye (Aug 02 2024 at 15:33):

Joel Dice said:

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.

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/O Types proposal for WASI. Contribute to WebAssembly/wasi-io development by creating an account on GitHub.

view this post on Zulip Joel Dice (Aug 02 2024 at 15:40):

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.

view this post on Zulip Joel Dice (Aug 02 2024 at 15:41):

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.

view this post on Zulip Zihang Ye (Aug 02 2024 at 15:44):

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.

view this post on Zulip Joel Dice (Aug 02 2024 at 15:47):

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.

view this post on Zulip Zihang Ye (Aug 02 2024 at 15:50):

I see. Thank you for your suggestion.

I think I should do more tests and dig a bit deeper in the implementation.

view this post on Zulip Pat Hickey (Aug 03 2024 at 02:31):

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

view this post on Zulip Pat Hickey (Aug 03 2024 at 02:32):

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

view this post on Zulip Zihang Ye (Aug 05 2024 at 06:40):

I think I now understand the behavior better. Thanks a lot for your responses.

view this post on Zulip Notification Bot (Sep 09 2024 at 12:18):

Till Schneidereit has marked this topic as resolved.


Last updated: Nov 22 2024 at 17:03 UTC