cataggar opened issue #13396:
Summary
The upstream
wasi-testsuitefixturewasm32-wasip3/sockets-tcp-connect(tests/rust/wasm32-wasip3/src/bin/sockets-tcp-connect.rs'stest_explicit_bind_addrinuse) panics on wasmtime44.0.1(the first stable release with-Sp3support). The fixture creates two sockets, binds the first tolocalhost:0, then asks the kernel for the assigned port and tries to bind a second socket to the same address, expectingErrorCode::AddressInUse. wasmtime returns something other thanErr(AddressInUse)and thematches!assertion panics.Reproduction
# In a wasi-testsuite checkout at d8c30a7 with wasmtime 44.0.1 on PATH cd tests/rust/wasm32-wasip3 && make build && cd - wasmtime -Wcomponent-model-async -Sp3,inherit-network \ tests/rust/testsuite/wasm32-wasip3/sockets-tcp-connect.wasmOutput (trimmed):
thread '<unnamed>' (1) panicked at src/bin/sockets-tcp-connect.rs:154:5: assertion failed: matches!(result, Err(ErrorCode::AddressInUse)) note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Error: failed to run main module `…/sockets-tcp-connect.wasm` … 12: 0xbbf1 - sockets_tcp_connect…!sockets_tcp_connect…::test_explicit_bind_addrinuse::{closure#0} … 1: wasm trap: wasm `unreachable` instruction executedThe asserting code (
tests/wasi-testsuitetests/rust/wasm32-wasip3/src/bin/sockets-tcp-connect.rs):async fn test_explicit_bind_addrinuse(family: IpAddressFamily) { let listener = { let bind_address = IpSocketAddress::localhost(family, 0); let listener = TcpSocket::create(family).unwrap(); listener.bind(bind_address).unwrap(); // bind to ephemeral port listener }; let listener_address = listener.get_local_address().unwrap(); // ask the kernel for the port let client = TcpSocket::create(family).unwrap(); let result = client.bind(listener_address); // second bind to the same port assert!(matches!(result, Err(ErrorCode::AddressInUse))); // panics on wasmtime 44.0.1 }Cross-runtime parity
The same fixture passes on the WAMR-Zig WASIp3 host (40 / 40 wasm32-wasip3 fixtures pass, this one included), which suggests the upstream WIT contract is for
bindto returnErr(AddressInUse)when a second socket tries to bind to a port still held by another socket. wasmtime 44 either lets the second bind succeed (perhaps becauseSO_REUSEADDRis set on the second socket) or maps the kernelEADDRINUSEto a differentErrorCodevariant.Why this matters
This is one of four
wasm32-wasip3fixtures that fail on a stock wasmtime 44.0.1 install but pass on the WAMR-Zig host; tracked in cataggar/wamr#583 C1 (the cross-runtime parity gate). Filing here so the parity gate can mark it as a documented wasmtime-side delta until a wasmtime fix lands.Environment
- wasmtime
44.0.1 (f302ebd6b 2026-04-30)— fromcurl https://wasmtime.dev/install.sh | bash -s -- --version v44.0.1- wasi-testsuite
wasi-testsuite-rust-runners-1.0.0(40c1f7d)tests/rust/testsuite/wasm32-wasip3/sockets-tcp-connect.wasm- Linux 6.x aarch64
dicej assigned dicej to issue #13396.
dicej commented on issue #13396:
Thanks for reporting this, @cataggar.
BTW, I couldn't find a
d8c30a7commit in https://github.com/WebAssembly/wasi-testsuite, nor a Makefile undertests/rust/wasm32-wasip3in that repo, but I was able to reproduce the issue using(cd tests/rust/wasm32-wasip3 && cargo build --target wasm32-wasip2) && wasmtime -Wcomponent-model-async -Sp3,inherit-network tests/rust/wasm32-wasip3/target/wasm32-wasip2/debug/sockets-tcp-connect.wasm.After a bit of debugging, I traced this back to these lines which, as you suspected, set
SO_REUSEADDRon the socket. Commenting out those lines makes the test pass.@badeend @rvolosatovs as the last ones to touch that code in e.g. https://github.com/bytecodealliance/wasmtime/pull/12597, can you shed some light on this? The comment says that it's to bypass the "
TIME_WAITstate of a recently closed socket on the same local address", but seems to have the side effect of bypassing still-open sockets on the same local address, which presumably wasn't the intent.
badeend commented on issue #13396:
AFAIK, the wasi-testsuite is (partially) adapted from the wasmtime tests. Wasmtime contains spiritually the same test which "successfully" fails on all platforms:
wasmtime returns something other than Err(AddressInUse)
Are you able to share what it actually returns? Even though the wasi-testsuite & wasmtime tests are structured slightly differently, I don't see any obvious reasons for the output to be different.
dicej commented on issue #13396:
Are you able to share what it actually returns? Even though the wasi-testsuite & wasmtime tests are structured slightly differently, I don't see any obvious reasons for the output to be different.
It returns
Ok(())for me.
badeend commented on issue #13396:
Spotted the discrepancy. The
listenersocket in the wasi-testsuite test is not actually listening.On Linux the
listenis needed in order to trigger the desired error code. From the man pages:SO_REUSEADDR: (...) For AF_INET sockets this means that a socket may bind, except when there is an active listening socket bound to the address.
In other words; Yes, Linux allows multiple sockets the same address, but.. at most one _active_ socket. As soon as you actually want to use it (i.e. listen on it), the latter later calls fail. Take the following example sequence:
sockA.bind(someAddr); sockB.bind(someAddr); sockA.listen(); sockB.listen();Windows fails immediately on the second bind (
sockB.bind). On Linux this is deferred until the second listen (sockB.listen).As far as I'm aware this is a Linux-specific peculiarity and there's not much we can do about it, other than updating the wasi-testsuite test to be more platform agnostic:
async fn test_explicit_bind_addrinuse(family: IpAddressFamily) { let listener = { let bind_address = IpSocketAddress::localhost(family, 0); let listener = TcpSocket::create(family).unwrap(); listener.bind(bind_address).unwrap(); + listener.listen().unwrap(); listener }; let listener_address = listener.get_local_address().unwrap(); let client = TcpSocket::create(family).unwrap(); let result = client.bind(listener_address); assert!(matches!(result, Err(ErrorCode::AddressInUse))); }
The same fixture passes on the WAMR-Zig WASIp3 host
@cataggar I'm not familiar with that runtime, but if it passes this (broken-on-linux) test then I'm surpised it doesn't fail on
test_reuseaddr, which tests the opposite side of the coin.
Last updated: Jun 01 2026 at 09:49 UTC