Found while comparing outbound wasi:http behavior against wasmtime-wasi-http 47.0.3 (src/p3/, the same wasi:http@0.3 surface).
1. The headers phase is unbounded — a hung server hangs the guest forever
client.send awaits the fetch with no timer of any kind (wasi/src/http.ts:1018-1022). The first-byte timer only starts after fetch resolves, i.e. after response headers arrived (consumeFetchBody, wasi/src/http.ts:839). A server that accepts the connection and never sends headers hangs the guest's send indefinitely, regardless of any request-options.
Compounding it: unset timeouts mean no timer at all, where wasmtime clamps all three phases to 600s defaults (p3/request.rs:311,319,328). So even a guest that sets nothing gets a 10-minute worst case on wasmtime and an infinite one here.
2. first-byte-timeout placement and error variant both diverge from wasmtime
- wasmtime: the timer wraps
sender.send_request() — it bounds time-to-response-headers and fails the handle call itself, with connection-read-timeout (p3/request.rs:431-433). The first body byte is then governed by between_bytes_timeout (interval reset right after headers, p3/request.rs:435-436).
- polyengine: headers unbounded (above); the timer bounds the first body read and errs the body stream with
HTTP-response-timeout (wasi/src/http.ts:839-847).
The WIT prose ("timeout for receiving the first byte of the Response body", types.wit request-options docs) reads closer to our placement — wasmtime arguably fires too early — but neither implementation covers headers + first body byte as a spec-literal reading would. Guests that match on the variant, or that expect handle/send itself to fail on a slow server, observe different behavior on the two hosts.
Suggested direction
Race the first-byte timer against the fetch promise itself so the clock starts at request transmission and covers the headers phase; keep it running until the first body read. That fixes the unbounded hang and makes the coverage a superset of both current interpretations. Separately decide:
- whether unset timeouts should inherit wasmtime's 600s defaults (parity) or stay infinite (spec-literal: unset = no timeout);
- whether the variant should stay
HTTP-response-timeout (closer to the event we detect) or move to connection-read-timeout (wasmtime parity). between-bytes already matches (connection-read-timeout, both sides).
Whatever the outcome, the recorded-divergences header (wasi/src/http.ts:40-60) should gain a line for the timeout model; today it records set-connect-timeout only.
Found while comparing outbound wasi:http behavior against wasmtime-wasi-http 47.0.3 (
src/p3/, the same wasi:http@0.3 surface).1. The headers phase is unbounded — a hung server hangs the guest forever
client.sendawaits the fetch with no timer of any kind (wasi/src/http.ts:1018-1022). The first-byte timer only starts after fetch resolves, i.e. after response headers arrived (consumeFetchBody, wasi/src/http.ts:839). A server that accepts the connection and never sends headers hangs the guest'ssendindefinitely, regardless of any request-options.Compounding it: unset timeouts mean no timer at all, where wasmtime clamps all three phases to 600s defaults (
p3/request.rs:311,319,328). So even a guest that sets nothing gets a 10-minute worst case on wasmtime and an infinite one here.2. first-byte-timeout placement and error variant both diverge from wasmtime
sender.send_request()— it bounds time-to-response-headers and fails thehandlecall itself, withconnection-read-timeout(p3/request.rs:431-433). The first body byte is then governed bybetween_bytes_timeout(interval reset right after headers,p3/request.rs:435-436).HTTP-response-timeout(wasi/src/http.ts:839-847).The WIT prose ("timeout for receiving the first byte of the Response body", types.wit request-options docs) reads closer to our placement — wasmtime arguably fires too early — but neither implementation covers headers + first body byte as a spec-literal reading would. Guests that match on the variant, or that expect
handle/senditself to fail on a slow server, observe different behavior on the two hosts.Suggested direction
Race the first-byte timer against the fetch promise itself so the clock starts at request transmission and covers the headers phase; keep it running until the first body read. That fixes the unbounded hang and makes the coverage a superset of both current interpretations. Separately decide:
HTTP-response-timeout(closer to the event we detect) or move toconnection-read-timeout(wasmtime parity). between-bytes already matches (connection-read-timeout, both sides).Whatever the outcome, the recorded-divergences header (wasi/src/http.ts:40-60) should gain a line for the timeout model; today it records
set-connect-timeoutonly.