Skip to content

wasi/http: headers phase is unbounded and first-byte-timeout diverges from wasmtime in placement and variant #227

Description

@lannbot

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingp2Minor bugs; desirable lower-priority featuresspec-divergenceBehavior diverges from the pinned CM reference; fix or adjudicate as a named divergence

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions