Skip to content

fix(wasm): wasi:sockets TCP cannot bind — finish-connect and accept return too many values - #611

Closed
xepozz wants to merge 2 commits into
wippyai:mainfrom
xepozz:fix/wasi-sockets-result-arity
Closed

fix(wasm): wasi:sockets TCP cannot bind — finish-connect and accept return too many values#611
xepozz wants to merge 2 commits into
wippyai:mainfrom
xepozz:fix/wasi-sockets-result-arity

Conversation

@xepozz

@xepozz xepozz commented Aug 30, 2026

Copy link
Copy Markdown

The wasi:sockets TCP host cannot be bound by any guest. Two methods return their payload as several bare values, which the canonical-ABI lowering has no way to accept, so every component that imports them fails to load.

The failure

A WASI Preview 2 component importing wasi:sockets/tcp fails at host binding:

[host] registration: register wasi:sockets/tcp@0.2.8#[method]tcp-socket.finish-connect
  (caused by: handler validation: result count mismatch: expected 1, got 3)

accept reports expected 1, got 4. Reproduced with a Rust wasm32-wasip2 component and, independently, with a Go-authored one — the guest cannot influence this.

Why it can never bind

CanonRegistry.processLower caps the lowered results at one (lower.Results = []wit.Type{result}), which is what the canonical ABI requires: a result that does not fit one flat value comes back through a retptr.

LowerWrapper.ValidateHandler then accepts exactly two arities — an exact match, or the (payload, error) pair it can fold through foldResultValue:

if numOut != expectedResults {
    if expectedResults == 1 && numOut == 2 && w.hasResultType() {
    } else if expectedResults == 0 && numOut == 0 {
    } else {
        return fmt.Errorf("result count mismatch: expected %d, got %d", expectedResults, numOut)
    }
}

The two methods match neither:

MethodTCPSocketFinishConnect(ctx, self uint32) (uint32, uint32, *NetworkError)          // 3
MethodTCPSocketAccept(ctx, self uint32)        (uint32, uint32, uint32, *NetworkError)  // 4

This is not a partial outage. NewTCPInputStreamResource is reached only from those two methods, so there is no other route to a socket's input and output streams: a guest can create a socket and start a connection, but can never read or write. UDP and DNS are unaffected.

The change

Both methods now return a record plus the error — the shape the address getters in the same file already use (MethodTCPSocketLocalAddress returns (*IPSocketAddress, *NetworkError)), so the fold applies and the payload lifts as a record:

type TCPStreams  struct { Input, Output uint32 }
type TCPAccepted struct { Socket, Input, Output uint32 }

Four call sites in tcp_test.go follow the new shape. No behaviour changes beyond the return shape, and no dependency is touched.

Test

boot/components/runtime/wasm/sockets_bind_test.go loads a real component that imports finish-connect and fails if any sockets method cannot bind. It is gated on WASM_SOCKETS_PROBE pointing at such a component, and skips otherwise, because the repository carries no component fixture — happy to add one if you would prefer the test to stand alone.

Against the component I built it fails on main with expected 1, got 3 and passes here.

Note on the WIT shape

ip-socket-address is implemented as a record (IPSocketAddress{Address string; Port uint16}) rather than the variant { ipv4, ipv6 } the published WASI interface declares, so a guest has to declare it the same way to bind. That is out of scope here, but worth knowing if the intent is to accept stock wasi:sockets guests.

Dmitriy Derepko added 2 commits August 30, 2026 20:12
The canonical ABI folds a host method into one result, and the binder accepts
either an exact arity match or a (payload, error) pair. finish-connect returned
three values and accept four, so neither could ever bind: a guest importing them
failed with 'result count mismatch: expected 1, got 3'. Those two methods are
the only source of the socket's input and output streams, so wasi:sockets TCP
was unusable end to end for every guest.

Both now return a record plus the error, the shape the address getters already
use.
Loads a Go-authored WASI Preview 2 component that imports finish-connect and
fails if any sockets method cannot bind. Before the record change this failed
with 'result count mismatch: expected 1, got 3'.
@wolfy-j

wolfy-j commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Superseded by #543. The canonical-ABI tuple fix and a committed stock Rust WASI sockets binding fixture are included there.

@wolfy-j wolfy-j closed this Aug 30, 2026
@xepozz
xepozz deleted the fix/wasi-sockets-result-arity branch August 31, 2026 04:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants