Repository navigation
Conversation
Build the real SocketsHttpHandler for WASI on top of wasi:sockets, add a WASI build of System.Net.WebSockets, and allow ClientWebSocket on WASI. HttpClientHandler on WASI follows the mobile pattern: it uses the wasi:http based WasiHttpHandler by default and SocketsHttpHandler when System.Net.Http.UseNativeHttpHandler is false. WasiApp.targets defaults UseNativeHttpHandler=true so the SDK passes the switch to the trimmer and the unused handler is removed. System.Net.Security has no WASI build, so the socket path doesn't configure TLS options, and ClientWebSocket only touches SslOptions when a certificate validation callback is set. Only http:// and ws:// work over SocketsHttpHandler. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Unix implements a zero-byte receive as a 1-byte MSG_PEEK, which WASI sockets don't support, so the receive failed with OperationNotSupported. SocketsHttpHandler relies on zero-byte reads for HTTP/2. On WASI, report that the receive would block; the queued zero-byte operation completes once the socket is readable. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CheckPollables re-registered a poll hook once per pollable it subscribed, so a connected socket (input + output pollables) doubled its hook entries every iteration until memory ran out, and hooks with no pollables were dropped. Re-register each live hook once. A connected socket also always subscribed to its output stream, which is almost always writable, so the event loop never blocked and spun with any idle connection. Only subscribe to directions with a waiting operation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Run the SocketsHttpHandler functional tests and the System.Net.WebSockets and System.Net.WebSockets.Client tests on WASI. The test projects use SocketsHttpHandler through HttpClientHandler (UseNativeHttpHandler=false), as on Android. Tests that need TLS or MD5 are gated on new PlatformDetection.IsTlsSupported and IsMD5Supported capability probes, so they light up if those become available. Tests that need APM, pipes, NetworkInformation, synchronous socket operations or blocking waits are skipped on WASI. Make shared test infrastructure work without TLS or threads: - Configuration.Certificates only fails fast when TLS is supported, so an unloadable test certificate doesn't terminate the test process. - LoopbackProxyServer uses async I/O and doesn't block in Dispose without multithreading. - DefaultConnectCallback creates an address-family-specific socket on WASI, which doesn't support dual-mode sockets. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Azure Pipelines: Successfully started running 4 pipeline(s). 12 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Tagging subscribers to this area: @karelz, @dotnet/ncl |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Guard SslOptions use in ClientWebSocket on WASI (CA1416), suppress CA1416 for the System.Net.Http wasi TFM where TLS paths throw PlatformNotSupportedException, and narrow CreateNativeHandler's return type (CA1859). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara |
There was a problem hiding this comment.
🟡 Changes recommended
Source inspection confirms zero-byte receive correctness issues and missing TLS guards in newly enabled tests.
5 open issues
What changed in this PR
Enables socket-based HTTP and WebSockets on WASI while retaining WasiHttpHandler as the default.
Changes:
- Adds WASI handler selection and WebSocket support.
- Adjusts zero-byte receives and event-loop polling.
- Enables networking tests with capability-based exclusions and asynchronous test infrastructure.
Validation here was limited to source inspection; no builds or tests were run.
| File | Description |
|---|---|
| src/mono/wasi/build/WasiApp.targets | Defaults to the native HTTP handler. |
| src/libraries/tests.proj | Enables WASI WebSocket test projects. |
| src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs | Preserves each live hook once. |
| src/libraries/System.Private.CoreLib/src/System/Threading/UnixHandleAsyncContext.Wasi.cs | Limits connected TCP subscriptions to pending operations. |
| src/libraries/System.Private.CoreLib/src/System/Threading/UnixHandleAsyncContext.cs | Exposes queue waiting state. |
| src/libraries/System.Net.WebSockets/tests/WebSocketStreamTests.cs | Separates threading-dependent stream tests. |
| src/libraries/System.Net.WebSockets/src/System.Net.WebSockets.csproj | Adds the WASI target. |
| src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj | Selects socket-based HTTP for WASI tests. |
| src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.Invoker.cs | Guards TLS-dependent invoker cases. |
| src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketTestBase.cs | Guards certificate callback configuration. |
| src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketOptionsTests.cs | Guards secure proxy cases. |
| src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs | Avoids unsupported WASI TLS configuration. |
| src/libraries/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj | Removes the WASI exclusion. |
| src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive/SendReceive.cs | Enables zero-byte disconnect coverage. |
| src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs | Adds WASI zero-byte receive handling. |
| src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj | Selects SocketsHttpHandler on WASI. |
| src/libraries/System.Net.Http/tests/FunctionalTests/SyncHttpHandlerTest.cs | Updates synchronous-send skip reasons. |
| src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2ExtendedConnect.cs | Guards TLS-dependent cases. |
| src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs | Enables WASI tests and adds capability guards. |
| src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Cancellation.cs | Uses cleartext when TLS is unavailable. |
| src/libraries/System.Net.Http/tests/FunctionalTests/MetricsTest.cs | Guards TLS-dependent metrics tests. |
| src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs | Adapts socket creation and TLS configuration. |
| src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/SystemProxyInfo.Wasi.cs | Removes the no-proxy implementation. |
| src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs | Reports support on WASI. |
| src/libraries/System.Net.Http/src/System/Net/Http/HttpProtocolException.cs | Includes HTTP/2 exception helpers on WASI. |
| src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.Wasi.InvokeNativeHandler.cs | Adds native-handler forwarding. |
| src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs | Removes WASI-specific branches from the shared implementation. |
| src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.AnyMobile.cs | Extends handler selection to WASI. |
| src/libraries/System.Net.Http/src/System/Net/Http/Headers/KnownHeaders.cs | Clarifies the Alt-Svc comment only. |
| src/libraries/System.Net.Http/src/System/Net/Http/GlobalHttpSettings.cs | Enables HTTP/2 settings on WASI. |
| src/libraries/System.Net.Http/src/System.Net.Http.csproj | Includes socket HTTP implementation and dependencies. |
| src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs | Adds TLS and MD5 capability probes. |
| src/libraries/Common/tests/System/Net/Http/TestHelper.cs | Guards TLS configuration. |
| src/libraries/Common/tests/System/Net/Http/LoopbackProxyServer.cs | Uses asynchronous I/O and avoids threadless blocking disposal. |
| src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Proxy.cs | Gates TLS and MD5 scenarios. |
| src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.MaxResponseHeadersLength.cs | Gates threading-dependent tests. |
| src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs | Adds capability and unsupported-operation guards. |
| src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Asynchrony.cs | Gates synchronization-context tests. |
| src/libraries/Common/tests/System/Net/Configuration.Certificates.cs | Limits fail-fast diagnostics to TLS-capable platforms. |
| src/coreclr/vm/wasm/wasi/callhelpers-pinvoke.cpp | Documents WebSocket compression consumers in comments. |
Review effort: Balanced
Completing a queued zero-byte receive on any poll event mistook output readiness for input, left receives on unconnected TCP sockets pending, and completed UDP receives before a datagram arrived. Poll the socket for input instead, and report the receive error when it can't be polled. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
/azp run runtime-coreclr outerloop |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
I'm staging several follow-ups to this to add back wasihttphandler tests, enable more features and fix the things uncovered in the process. |
| @@ -0,0 +1,109 @@ | |||
| // Licensed to the .NET Foundation under one or more agreements. | |||
There was a problem hiding this comment.
The file name confuses me. I would say that the WasiHttpHandler is "system" HTTP client, rather than "native". As in "system interface"
There was a problem hiding this comment.
this follows the mobile naming convention, native here is essentially "not managed/system" vs socketshttphandler which is the managed implementation on top of sockets
Enables `System.IO.Compression.Brotli` and Zstandard (`ZstandardStream` and related types) on CoreCLR WASI. Browser is unchanged and still excludes both. ## Why Both codecs were excluded on all of wasm because browser leaves them out for download size. WASI apps always relink the native runtime with a P/Invoke table generated from the app's trimmed assemblies, so a codec's native code is only linked into apps that use it. Measured on relinked, trimmed sample apps (Release, `wasm-tools strip -a`): | App | Host size | Delta | gzip -9 delta | |---|---|---|---| | GZip only | 5,521,734 | — | — | | GZip + BrotliStream | 6,355,085 | +833 KB | +316 KB | | GZip + ZstandardStream | 6,028,091 | +506 KB | +121 KB | The GZip-only host contains no `Brotli*` or `ZSTD_*` symbols. ## Changes - **Native/build:** brotli build, includes and entry points are now gated on `!browser` instead of `!wasm`. The brotli libs are linked in the CoreCLR WASI app relink, the NativeAOT WASI link and the Mono WASI runtime link; libzstd is linked unconditionally in the CoreCLR relink. Duplicate libs.native copies are excluded from the WASI CoreCLR runtime pack, as for `libz.a`. - **Managed:** `System.IO.Compression.Brotli` gains a `-wasi` TFM. System.IO.Compression compiles the real Zstandard implementation for wasi. `wasi` is removed from `UnsupportedOSPlatform` on the Brotli assembly, the Zstandard types, and `BrotliCompressedContent`/`ZstandardCompressedContent` (src and ref). - **Call helpers:** `src/coreclr/vm/wasm/wasi/callhelpers-pinvoke.cpp` is regenerated in the commit that adds each codec's P/Invokes (Compression.Native table: 9 → 21 → 51 entries). - **Tests:** the Brotli tests now target WASI, and `CompressedContentTest.NonBrowser.cs` is compiled for WASI. ## Validation CoreCLR WASI, Release, wasmtime 49.0.1: - System.IO.Compression.Brotli.Tests: 259 run, 251 passed, 0 failed, 8 skipped (existing `IsMultithreadingSupported` conditions). - System.Net.Http.Functional.Tests `-class CompressedContentTest`: 48 run, 47 passed, 1 skipped (existing 32-bit zstd `SmallestSize` case). This covers the br and zstd round-trips. - System.IO.Compression.Tests built for wasi locally: all 316 Zstandard tests passed, 10 skipped on capability conditions. - ApiCompat outer builds of Brotli, System.IO.Compression and System.Net.Http are clean. - The Mono WASI runtime (`mono.runtime+mono.corelib+mono.wasiruntime`) builds and links. - Not run locally: a NativeAOT WASI app link that uses Brotli. ## Known gaps - There's no dedicated Zstandard test suite on WASI yet. `System.IO.Compression.Tests` doesn't target wasi because its WinZip AES and encrypted-zip tests fail there (cryptography isn't supported on WASI) and need gating first. - `SocketsHttpHandler` decompression of br/zstd on WASI is covered by #136234. Related: #91279 (Brotli on browser; not addressed here) > [!NOTE] > This PR description was generated with GitHub Copilot. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ttp-handler Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ttp-handler Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Brotli and Zstandard now work on WASI (dotnet#136257). The decompression test servers disposed compression streams synchronously over the connection, which WASI doesn't support, and the Brotli buffer-limit test's write loop never yielded to a single-threaded client. The new Alt-Svc test needs TLS. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
On Unix, a zero-byte receive is implemented as a 1-byte `MSG_PEEK`. wasi-libc rejects `MSG_PEEK` with `EOPNOTSUPP`, so every zero-byte receive fails on WASI. Callers such as HTTP/2 in `SocketsHttpHandler` rely on zero-byte reads to wait for data. This was split out of #136234 at a reviewer's request. ## Fix On WASI, a zero-byte receive with no flags now does this instead: - It polls the socket for `POLLIN` with a zero timeout, and completes only when the socket is readable. - If the socket can't be polled, it does a zero-length `recv` so the real error is reported. For example, wasi-libc returns `EOPNOTSUPP` for an unconnected TCP socket, and the `recv` reports `ENOTCONN` instead. - WASI poll events don't say which direction is ready, so queued zero-byte receives re-run this check. They no longer complete on any event. Non-WASI behavior is unchanged. Not covered: a zero-byte `ReceiveFrom`/`ReceiveMessageFrom` that passes an endpoint still takes the `MSG_PEEK` path on WASI. ## Known caveat After data has been drained, one zero-byte receive can complete early, because wasmtime keeps readiness set until a read would block. wasmtime treats this as by design (bytecodealliance/wasmtime#9667). The next real read then waits correctly. ## Validation - **CoreCLR WASI, Release, wasmtime 49.0.1:** the full System.Net.Sockets functional tests ran 653 tests: 565 passed, 0 failed, 88 skipped. The baseline on main is about 645 run with 0 failed. The two new tests passed in all 4 variants that run on WASI. - **macOS, Release:** I ran the `SendReceive_*` classes (Sync, SyncForceNonBlocking, Apm, Task, Eap, SpanSync, MemoryArrayTask, MemoryNativeTask). Total was 252 with 0 failed, and both new tests passed in every variant. - `Receive0Byte_NotConnected_Throws` runs on all platforms. `Receive0Byte_Udp_CompletesWhenDatagramArrives` runs on Unix only, because on Windows a zero-byte receive consumes the datagram. - `SocketsHttpHandler`/HTTP/2 isn't enabled on WASI in main, so the motivating HTTP/2 scenario is covered in #136234 rather than here. - Building main locally needed the `fgopt.cpp` fix from #136388. That fix is not part of this PR. Related: #107981 > [!NOTE] > This PR was generated with GitHub Copilot. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
) Split out of #136234 at a reviewer's request. Two bugs in the WASI event loop's socket poll hooks: 1. **Hook duplication.** `WasiEventLoop.CheckPollables` re-added each poll hook once per pollable it subscribed, not once per hook. A connected TCP socket subscribes two pollables (input and output), so its hook doubled on every pass. Hooks that subscribed nothing were dropped for good. The fix tracks the live hooks and re-adds each one once. 2. **Idle socket spinning.** For a connected TCP socket, `BeforePollHook` always subscribed the output stream, which is almost always writable, so any idle connection kept `poll` from blocking. It now subscribes input or output only when `_readQueue` or `_writeQueue` has a waiting operation (new `OperationQueue.IsWaiting`). The hook runs before every poll, so operations enqueued later are picked up on the next pass. **Found** running System.Net.WebSockets.Client.Tests with SocketsHttpHandler enabled on WASI (#136234). That run ran out of memory inside the event loop. With the fix, the suite ran 391 tests with no OOM, and idle CPU of the wasmtime process dropped from about 99% to 0%. Main has no HTTP/2 or WebSockets client on WASI, so that exact repro needs #136234. **Validation** (CoreCLR WASI Release, wasmtime 49.0.1), System.Net.Sockets functional tests: no new failures with the fix (one extra candidate test was included in both runs and later removed). - with the fix: 646 run, 558 passed, 0 failed, 88 skipped - main: 646 run, 558 passed, 0 failed, 88 skipped Instrumenting `CheckPollables` on main showed the hook count going 1 → 2 → 7 → 124 → 31,744 within 16 passes while 4 connected sockets waited on receives. The Sockets tests don't keep connections alive long enough to OOM. Two candidate regression tests (idle TCP pairs with pending receives, and a UDP receive before any datagram) both passed on main, so no new test is included. Known gap (follow-up): the UDP `BOUND_STREAMING`/`CONNECTED` path still always subscribes the outgoing datagram stream. > [!NOTE] > This PR was generated with GitHub Copilot. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ttp-handler Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>


Enables
SocketsHttpHandler,System.Net.WebSocketsandClientWebSocketon WASI over wasi:sockets. WASI has noSystem.Net.Security, so onlyhttp://andws://work. This lets WASI apps and tools (for example adotnet watchagent) talk to a local server without wasi:http, which can't upgrade a connection to a WebSocket.The main parts:
HttpClientHandleron WASI now follows the mobileUseNativeHttpHandlerpattern. The default is stillWasiHttpHandler, andWasiApp.targetsdefaultsUseNativeHttpHandler=true, so the SDK passes the switch to ILLink andSocketsHttpHandleris trimmed away. A trimmed hello-worldHttpClientapp keepsSystem.Net.Http.dllat ~289 KB by default, and ~631 KB withUseNativeHttpHandler=false.SocketsHttpHandlerfunctional tests,System.Net.WebSockets.TestsandSystem.Net.WebSockets.Client.Testson CoreCLR WASI.PlatformDetection.IsTlsSupported/IsMD5Supportedcapability probes rather thanIsWasi, so they light up if either becomes available.LoopbackProxyServeris now async and doesn't block inDisposewithout threads.Configuration.Certificatesno longer fails fast when TLS isn't supported. Its PFX files can't be decrypted on WASI.The WASI socket and event loop fixes these tests depend on were split out and have merged: zero-byte receives (#136394), event loop hook duplication and idle-socket spinning (#136395), and UDP
ReceiveAsync(#136250).Known gaps and follow-ups:
UseNativeHttpHandler=false, so they no longer exercise the defaultHttpClientHandleroverWasiHttpHandlerpath on WASI.std::io::ErrorKind::WouldBlock) bytecodealliance/wasmtime#9667); the following real read waits correctly.HttpClient.DefaultProxyon WASI now reads the standard proxy environment variables, as on Unix.Validated locally on CoreCLR WASI (Release) with the
System.Net.Httpfunctional,System.Net.Sockets,System.Net.WebSocketsandSystem.Net.WebSockets.Clienttests, plus macOS runs of the changed shared test infrastructure.Note
This PR description was generated with GitHub Copilot.