From beabe5ace4e49e4008c7b30d5e20c57ec40dbddc Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 12 Jun 2024 10:34:39 -0600 Subject: [PATCH 01/20] support `System.Net.Http.HttpClient` on WASIp2 This adds `WasiHttpHandler`, a new implementation of `HttpMessageHandler` based on [wasi:http/outgoing-handler](https://github.com/WebAssembly/wasi-http/blob/v0.2.0/wit/handler.wit), plus tweaks to `System.Threading` to allow async `Task`s to work in a single-threaded context, with `ThreadPool` work items dispatched from an application-provided event loop. WASIp2 supports asynchronous I/O and timers via `wasi:io/poll/pollable` resource handles. One or more of those handles may be passed to `wasi:io/poll/poll`, which will block until at least one of them is ready. In order to make this model play nice with C#'s `async`/`await` and `Task` features, we need to reconcile several constraints: - WASI is currently single-threaded, and will continue to be that way for a while. - C#'s `async`/`await` and `Task` features require a working `ThreadPool` implementation capable of deferring work. - A WASI component can export an arbitrary number of functions to the host, and though they will always be called synchronously from the host, they need to be able to perform asynchronous operations before returning. - WASIp3 (currently in the design and prototype phase) will support asynchronous exports, with the top level event loop running in the host instead of the guest, and `wasi:io/poll/pollable` will no longer exist. Therefore, we don't want to add any temporary public APIs to the .NET runtime which will become obsolete when WASIp3 arrives. The solution we arrived at looks something like this: - Tweak the existing `ThreadPool` implementation for WASI so that methods such as `RequestWorkerThread` don't throw `PlatformNotSupportedException`s (i.e. allow work items to be queued even though the "worker thread" is always the same one that is queuing the work) - Add two new methods to `Thread`: - `internal static void Dispatch`: Runs an iteration of event loop, draining the `ThreadPool` queue of ready work items and calling `wasi:io/poll/poll` with any accumulated `pollable` handles - `internal static Task Register(int pollableHandle)`: Registers the specified `pollable` handle to be `poll`ed during the next call to `Dispatch` - Note that these methods are `internal` because they're temporary and should not be part of the public API, but they are intended to be called via `UnsafeAccessor` by application code (or more precisely, code generated by `wit-bindgen` for the application) The upshot is that application code can use `wit-bindgen` (either directly or via the new `componentize-dotnet` package) to generate async export bindings which will provide an event loop backed by `Thread.Dispatch`. Additionally, `wit-bindgen` can transparently convert any `pollable` handles returned by WASI imports into `Task`s via `Thread.Register`, allowing the component to `await` them, pass them to a combinator such as `Task.WhenEach`, etc. Later, when WASIp3 arrives and we update the .NET runtime to target it, we'll be able to remove some of this code (and the corresponding code in `wit-bindgen`) without requiring significant changes to the application developer's experience. This PR contains a few C# source files that were generated by `wit-bindgen` from the official WASI WIT files, plus scripts to regenerate them if desired. Signed-off-by: Joel Dice switch to `wasm32-wasip2` and update WASI test infra Now that we're using WASI-SDK 22, we can target `wasm32-wasip2`, which produces components by default and includes full `wasi:sockets` support. In order to run those components, I've updated the test infrastructure to use Wasmtime 21 (the latest release as of this writing). Other changes of note: - Tweaked src/coreclr/jit/compiler.cpp to make `Debug` builds work on Linux - Added libWasiHttp.a, which includes the encoded component type (in the form of a pre-generated Wasm object file) and a `cabi_realloc` definition. Both of these are generated by `wit-bindgen` and required by `wasm-component-ld` to generate a valid component. - Added a `FindWasmHostExecutableAndRun.sh` script for running the WASI tests on UNIX-style platforms. Signed-off-by: Joel Dice various WASI build tweaks Signed-off-by: Joel Dice quote libWasiHttp.a path in custom linker arg Signed-off-by: Joel Dice fix wasm-component-ld download command Signed-off-by: Joel Dice tweak EmccExtraArgs in CustomMain.csproj so wasm-component-ld understands it Signed-off-by: Joel Dice update CMake minimum version in wasi-sdk-p2.cmake Signed-off-by: Joel Dice use `HeaderDescriptor` to sort content and response headers Signed-off-by: Joel Dice allow building native WASI test code in src/tests/build.sh Signed-off-by: Joel Dice allow WASI runtime tests to be built and run on non-Windows systems Signed-off-by: Joel Dice update runtime tests to work with WASIp2 As of this writing, WASIp2 [does not support process exit statuses](https://github.com/WebAssembly/wasi-cli/issues/11) beyond 0 (success) and 1 (failure). To work around this, I've modified the relevant tests to return 0 on success instead of 100. Signed-off-by: Joel Dice fix CI for Windows builds; remove unused file Signed-off-by: Joel Dice disable sprintf warning in llvmlssa.cpp on macOS Signed-off-by: Joel Dice remove LibraryWorld_cabi_realloc.o I didn't mean to add this to Git. Signed-off-by: Joel Dice rename `generate-bindings.sh` files for clarity This makes it more obvious that, though they are similar, they each have a different job. Signed-off-by: Joel Dice update to `wit-bindgen` 0.27.0 and regenerate bindings Signed-off-by: Joel Dice reorganize code; add HttpClient smoke test - move System/WASIp2 to System/Threading/WASIp2 - remove generated `cabi_realloc` functions since `wasi-libc` will provide one - add HttpClient test to SmokeTests/SharedLibrary Note that I put the HttpClient test in SmokeTests/SharedLibrary since we were already using NodeJS for that test, and adding a simple loopback webserver to SharedLibraryDriver.mjs was easiest option available to keep the whole test self-contained. Signed-off-by: Joel Dice implement SystemNative_SysLog for WASI Signed-off-by: Joel Dice increase NodeJS stack trace limit to 200 Signed-off-by: Joel Dice give guest no filesystem access in SharedLibraryDriver.mjs Signed-off-by: Joel Dice switch to Trace.Assert into HttpClient smoke test Signed-off-by: Joel Dice rename WASIp2 directory to Wasi Signed-off-by: Joel Dice fix non-GET methods and add HttpClient echo test Signed-off-by: Joel Dice use azure NPM rename - WasiEventLoop.RegisterWasiPollable - WasiEventLoop.DispatchWasiEventLoop to make it less confusing on the Thread class - unification of gen-buildsys - cleanup pal_process_wasi.c fix build? more buffer /echo request body in SharedLibraryDriver.mjs Signed-off-by: Joel Dice fix gen-buildsys.sh regression Signed-off-by: Joel Dice allow only infinite `HttpClient.Timeout`s on WASI This temporary code will be reverted once we support `System.Threading.Timer` on WASI in a forthcoming PR. Signed-off-by: Joel Dice use `&` operator to simplify install-jco.ps1 Signed-off-by: Joel Dice remove redundant `CheckWasmSdks` target from SharedLibrary.csproj Signed-off-by: Joel Dice split `FindWasmHostExecutable.sh` out of `FindWasmHostExecutableAndRun.sh` Signed-off-by: Joel Dice replace component type object files with WIT files This updates `wit-bindgen` and `wasm-component-ld`, which now support producing and consuming component type WIT files as an alternative to binary object files. These files are easier to audit from a security perspective. Signed-off-by: Joel Dice preserve slashes in path in SharedLibrary.csproj Signed-off-by: Joel Dice temporarily disable ThreadPoolWorkQueue.Dispatch assertion See https://github.com/dotnet/runtime/issues/104803 Signed-off-by: Joel Dice update `wit-bindgen` to version 0.28.0 Signed-off-by: Joel Dice upgrade to wasi-sdk 24 and wit-bindgen 0.29.0 Signed-off-by: Joel Dice check for WASI in `PhysicalFileProvider.CreateFileWatcher` Signed-off-by: Joel Dice switch back to WASI 0.2.0 0.2.1 is not yet widely supported, and causes [trouble](https://github.com/bytecodealliance/jco/issues/486) for Jco, which rely on for the `SharedLibrary` test. Signed-off-by: Joel Dice remove use of `WeakReference` from `WasiEventLoop` This was causing `HttpClient` timeout tests in the `SharedLibrary` smoke test suite to fail, apparently due to `TimerQueue.SetNextTimer` calling `WasiEventLoop.RegisterWasiPollable`, attaching a continuation to the resulting `Task` and then letting go of the reference, allowing it to be GC'd. Signed-off-by: Joel Dice skip unsupported signal handling on WASI Signed-off-by: Joel Dice throw PlatformNotSupportedException in ManualResetEventSlim.Wait on WASI Otherwise, we end up in an infinite loop. Signed-off-by: Joel Dice Revert "switch back to WASI 0.2.0" This reverts commit a8608b47343e605b52e818ebfb4b9fdfd63bc66e. enable `NameResolution` and `Sockets` on WASI Signed-off-by: Joel Dice set `SocketsHttpHandler.IsEnabled` to `false` on WASI ...at least until we get `System.Net.Sockets` working. Signed-off-by: Joel Dice --- eng/pipelines/common/global-build-job.yml | 2 + eng/pipelines/runtimelab/install-jco.ps1 | 9 + eng/pipelines/runtimelab/install-nodejs.ps1 | 11 + eng/testing/FindWasmHostExecutable.sh | 5 +- src/coreclr/build-runtime.cmd | 8 +- src/coreclr/jit/compiler.cpp | 2 +- src/coreclr/jit/llvmlssa.cpp | 1 + .../nativeaot/Runtime/Portable/CMakeLists.txt | 1 - .../src/PhysicalFileProvider.cs | 4 +- .../Internal/ConsoleLifetime.netcoreapp.cs | 20 +- .../src/System.Net.Http.csproj | 3 +- .../src/System/Net/Http/HttpClientHandler.cs | 6 +- .../SocketsHttpHandler/SocketsHttpHandler.cs | 2 +- .../src/System.Net.NameResolution.csproj | 4 +- .../src/System.Net.Sockets.csproj | 4 +- .../System/Threading/ManualResetEventSlim.cs | 5 + .../src/System/Threading/Thread.Unix.cs | 4 + .../System/Threading/Wasi/WasiEventLoop.cs | 48 +- .../SmokeTests/HelloWasm/HelloWasm.cs | 2 +- .../nativeaot/SmokeTests/SharedLibrary/.npmrc | 1 + .../SmokeTests/SharedLibrary/Library.cs | 176 +++++ .../LibraryWorld_component_type.wit | 11 + .../SmokeTests/SharedLibrary/SharedLibrary.cs | 135 ++++ .../SharedLibrary/SharedLibrary.csproj | 5 +- .../SharedLibrary/SharedLibraryDriver.mjs | 121 ++- .../generate-shared-library-bindings.sh | 16 + .../SharedLibrary/package-lock.json | 698 ++++++++++++++++++ .../SmokeTests/SharedLibrary/package.json | 11 + .../SmokeTests/SharedLibrary/wit/world.wit | 11 + 29 files changed, 1245 insertions(+), 81 deletions(-) create mode 100644 eng/pipelines/runtimelab/install-jco.ps1 create mode 100644 src/tests/nativeaot/SmokeTests/SharedLibrary/.npmrc create mode 100644 src/tests/nativeaot/SmokeTests/SharedLibrary/Library.cs create mode 100644 src/tests/nativeaot/SmokeTests/SharedLibrary/LibraryWorld_component_type.wit create mode 100644 src/tests/nativeaot/SmokeTests/SharedLibrary/generate-shared-library-bindings.sh create mode 100644 src/tests/nativeaot/SmokeTests/SharedLibrary/package-lock.json create mode 100644 src/tests/nativeaot/SmokeTests/SharedLibrary/package.json create mode 100644 src/tests/nativeaot/SmokeTests/SharedLibrary/wit/world.wit diff --git a/eng/pipelines/common/global-build-job.yml b/eng/pipelines/common/global-build-job.yml index b068861190c..29b8eb49939 100644 --- a/eng/pipelines/common/global-build-job.yml +++ b/eng/pipelines/common/global-build-job.yml @@ -232,6 +232,8 @@ jobs: displayName: Install wasi-sdk - script: pwsh $(Build.SourcesDirectory)/eng/pipelines/runtimelab/install-wasmtime.ps1 -CI -InstallDir $(Build.SourcesDirectory)/wasm-tools displayName: Install wasmtime + - script: pwsh $(Build.SourcesDirectory)/eng/pipelines/runtimelab/install-jco.ps1 $(Build.SourcesDirectory) + displayName: Install Jco - ${{ if or(eq(parameters.platform, 'browser_wasm_win'), and(eq(parameters.platform, 'wasi_wasm_win'), not(eq(parameters.runtimeFlavor, 'coreclr')))) }}: # Update machine certs diff --git a/eng/pipelines/runtimelab/install-jco.ps1 b/eng/pipelines/runtimelab/install-jco.ps1 new file mode 100644 index 00000000000..06d77575c83 --- /dev/null +++ b/eng/pipelines/runtimelab/install-jco.ps1 @@ -0,0 +1,9 @@ +$RootPath = $Args[0] + +$NpmExePath = $Env:NPM_EXECUTABLE + +Set-Location -Path $RootPath + +& $NpmExePath install @bytecodealliance/jco +& $NpmExePath install @bytecodealliance/preview2-shim + diff --git a/eng/pipelines/runtimelab/install-nodejs.ps1 b/eng/pipelines/runtimelab/install-nodejs.ps1 index 5dee2727f2d..7d3d21d7ad4 100644 --- a/eng/pipelines/runtimelab/install-nodejs.ps1 +++ b/eng/pipelines/runtimelab/install-nodejs.ps1 @@ -51,11 +51,13 @@ if ($IsWindows) { Expand-Archive -LiteralPath "$InstallPath\$NodeJSZipName" -DestinationPath $InstallPath -Force $NodeJSExePath = "$InstallPath\$NodeJSInstallName\node.exe" + $NpmExePath = "$InstallPath\$NodeJSInstallName\npm.cmd" } else { tar xJf $InstallPath/$NodeJSZipName -C $InstallPath $NodeJSExePath = "$InstallPath/$NodeJSInstallName/bin/node" + $NpmExePath = "$InstallPath/$NodeJSInstallName/bin/npm" } if (!(Test-Path $NodeJSExePath)) @@ -64,5 +66,14 @@ if (!(Test-Path $NodeJSExePath)) exit 1 } +if (!(Test-Path $NpmExePath)) +{ + Write-Error "Did not find NPM at: '$NpmExePath'" + exit 1 +} + Write-Host Setting NODEJS_EXECUTABLE to $NodeJSExePath Write-Host "##vso[task.setvariable variable=NODEJS_EXECUTABLE]$NodeJSExePath" + +Write-Host Setting NPM_EXECUTABLE to $NpmExePath +Write-Host "##vso[task.setvariable variable=NPM_EXECUTABLE]$NpmExePath" diff --git a/eng/testing/FindWasmHostExecutable.sh b/eng/testing/FindWasmHostExecutable.sh index 2a3291fa115..4f59d9abf7e 100755 --- a/eng/testing/FindWasmHostExecutable.sh +++ b/eng/testing/FindWasmHostExecutable.sh @@ -21,7 +21,10 @@ elif [ -e "${dirname}/main.mjs" ]; then WASM_HOST_EXECUTABLE=$node WASM_BINARY_TO_EXECUTE="${dirname}/main.mjs" elif [ -e "${dirname}/${exename}.wasm" ]; then - WASM_HOST_EXECUTABLE=$WASMTIME_EXECUTABLE + if [ -z "$WASMTIME_EXECUTABLE" ]; then + WASMTIME_EXECUTABLE=wasmtime + fi + WASM_HOST_EXECUTABLE="$WASMTIME_EXECUTABLE run -S http" WASM_HOST_ARGS_SEPERATOR="--" WASM_BINARY_TO_EXECUTE="${dirname}/${exename}.wasm" fi diff --git a/src/coreclr/build-runtime.cmd b/src/coreclr/build-runtime.cmd index eac7903e1ab..bb679fe5e17 100644 --- a/src/coreclr/build-runtime.cmd +++ b/src/coreclr/build-runtime.cmd @@ -349,9 +349,11 @@ for /f "delims=" %%a in ("-%__RequestedBuildComponents%-") do ( set __CMakeTarget=!__CMakeTarget! nativeaot if "%__TargetArch%"=="wasm" ( - if not defined EMSDK ( - echo %__ErrMsgPrefix%%__MsgPrefix%Error: The EMSDK environment variable pointing to emsdk root must be set. - goto ExitWithError + if "%__TargetOS%"=="browser" ( + if not defined EMSDK ( + echo %__ErrMsgPrefix%%__MsgPrefix%Error: The EMSDK environment variable pointing to emsdk root must be set. + goto ExitWithError + ) ) ) ) diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 516b774f362..75dd9de7500 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -9686,7 +9686,7 @@ void dumpConvertedVarSet(Compiler* comp, VARSET_VALARG_TP vars) { printf(" "); } - printf("V%02u", varNum); + printf("V%02u", (unsigned int) varNum); first = false; } } diff --git a/src/coreclr/jit/llvmlssa.cpp b/src/coreclr/jit/llvmlssa.cpp index 280bf0d2692..03ccad3de50 100644 --- a/src/coreclr/jit/llvmlssa.cpp +++ b/src/coreclr/jit/llvmlssa.cpp @@ -1639,6 +1639,7 @@ class ShadowStackAllocator #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-security" +#pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif // __clang__ if (pBuffer == nullptr) { diff --git a/src/coreclr/nativeaot/Runtime/Portable/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/Portable/CMakeLists.txt index 4eb22481819..8f97a8467d2 100644 --- a/src/coreclr/nativeaot/Runtime/Portable/CMakeLists.txt +++ b/src/coreclr/nativeaot/Runtime/Portable/CMakeLists.txt @@ -40,4 +40,3 @@ install_static_library(standalonegc-disabled aotsdk nativeaot) if (NOT CLR_CMAKE_TARGET_ARCH_WASM) install_static_library(standalonegc-enabled aotsdk nativeaot) endif() - diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFileProvider.cs b/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFileProvider.cs index 925df989275..71fa429b4b5 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFileProvider.cs +++ b/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFileProvider.cs @@ -163,8 +163,8 @@ internal PhysicalFilesWatcher CreateFileWatcher() FileSystemWatcher? watcher; #if NET - // For browser/iOS/tvOS we will proactively fallback to polling since FileSystemWatcher is not supported. - if (OperatingSystem.IsBrowser() || (OperatingSystem.IsIOS() && !OperatingSystem.IsMacCatalyst()) || OperatingSystem.IsTvOS()) + // For WASI/browser/iOS/tvOS we will proactively fallback to polling since FileSystemWatcher is not supported. + if (OperatingSystem.IsWasi() || OperatingSystem.IsBrowser() || (OperatingSystem.IsIOS() && !OperatingSystem.IsMacCatalyst()) || OperatingSystem.IsTvOS()) { UsePollingFileWatcher = true; UseActivePolling = true; diff --git a/src/libraries/Microsoft.Extensions.Hosting/src/Internal/ConsoleLifetime.netcoreapp.cs b/src/libraries/Microsoft.Extensions.Hosting/src/Internal/ConsoleLifetime.netcoreapp.cs index 256697d927b..a2bf6e18064 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/src/Internal/ConsoleLifetime.netcoreapp.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/src/Internal/ConsoleLifetime.netcoreapp.cs @@ -15,10 +15,13 @@ public partial class ConsoleLifetime : IHostLifetime private partial void RegisterShutdownHandlers() { - Action handler = HandlePosixSignal; - _sigIntRegistration = PosixSignalRegistration.Create(PosixSignal.SIGINT, handler); - _sigQuitRegistration = PosixSignalRegistration.Create(PosixSignal.SIGQUIT, handler); - _sigTermRegistration = PosixSignalRegistration.Create(PosixSignal.SIGTERM, handler); + if (!OperatingSystem.IsWasi()) + { + Action handler = HandlePosixSignal; + _sigIntRegistration = PosixSignalRegistration.Create(PosixSignal.SIGINT, handler); + _sigQuitRegistration = PosixSignalRegistration.Create(PosixSignal.SIGQUIT, handler); + _sigTermRegistration = PosixSignalRegistration.Create(PosixSignal.SIGTERM, handler); + } } private void HandlePosixSignal(PosixSignalContext context) @@ -31,9 +34,12 @@ private void HandlePosixSignal(PosixSignalContext context) private partial void UnregisterShutdownHandlers() { - _sigIntRegistration?.Dispose(); - _sigQuitRegistration?.Dispose(); - _sigTermRegistration?.Dispose(); + if (!OperatingSystem.IsWasi()) + { + _sigIntRegistration?.Dispose(); + _sigQuitRegistration?.Dispose(); + _sigTermRegistration?.Dispose(); + } } } } diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj index 03e55dc3b04..734fb12bb01 100644 --- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj @@ -462,7 +462,7 @@ - + @@ -532,5 +532,4 @@ - diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs index 02c333177ba..579798edb33 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; using System.Diagnostics.Metrics; // TODO-LLVM: This is not upstreamable and should be deleted when https://github.com/dotnet/runtimelab/pull/2614 is merged -#if TARGET_WASI && !NATIVE_AOT +#if TARGET_WASI using System.Diagnostics; using System.Net.Http.Metrics; using HttpHandlerType = System.Net.Http.WasiHttpHandler; @@ -30,7 +30,7 @@ public partial class HttpClientHandler : HttpMessageHandler private readonly HttpHandlerType _underlyingHandler; // TODO-LLVM: This is not upstreamable and !NATIVE_AOT should be reverted when https://github.com/dotnet/runtimelab/pull/2614 is merged -#if TARGET_BROWSER || (TARGET_WASI && !NATIVE_AOT) +#if TARGET_BROWSER || TARGET_WASI private IMeterFactory? _meterFactory; private HttpMessageHandler? _firstHandler; // DiagnosticsHandler or MetricsHandler, depending on global configuration. @@ -101,7 +101,7 @@ protected override void Dispose(bool disposing) public IMeterFactory? MeterFactory { // TODO-LLVM: This is not upstreamable and !NATIVE_AOT should be reverted when https://github.com/dotnet/runtimelab/pull/2614 is merged -#if TARGET_BROWSER || (TARGET_WASI && !NATIVE_AOT) +#if TARGET_BROWSER || TARGET_WASI get => _meterFactory; set { diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs index 44acad742e7..172bca3be17 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs @@ -36,7 +36,7 @@ private void CheckDisposedOrStarted() /// Gets a value that indicates whether the handler is supported on the current platform. /// [UnsupportedOSPlatformGuard("browser")] - public static bool IsSupported => !OperatingSystem.IsBrowser(); + public static bool IsSupported => !(OperatingSystem.IsBrowser() || OperatingSystem.IsWasi()); public bool UseCookies { diff --git a/src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj b/src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj index 5d97e2d711f..de4aee96c1c 100644 --- a/src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj +++ b/src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj @@ -1,7 +1,7 @@ - $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-unix;$(NetCoreAppCurrent)-browser;$(NetCoreAppCurrent) + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-unix;$(NetCoreAppCurrent)-browser;$(NetCoreAppCurrent)-wasi;$(NetCoreAppCurrent) true false @@ -68,7 +68,7 @@ Link="Common\Interop\Windows\WinSock\Interop.GetAddrInfoExW.cs" /> - + diff --git a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj index e7a4cfbcce3..0ad1c940c54 100644 --- a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj +++ b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj @@ -1,7 +1,7 @@ - $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-unix;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent) + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-unix;$(NetCoreAppCurrent)-wasi;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent) true @@ -183,7 +183,7 @@ Link="Common\System\Net\CompletionPortHelper.Windows.cs" /> - + diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs index 37f8c06af04..831cfd4f697 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs @@ -481,6 +481,10 @@ public bool Wait(int millisecondsTimeout) #endif public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) { +#if TARGET_WASI + _ = s_cancellationTokenCallback; + throw new PlatformNotSupportedException("ManualResetEventSlim.Wait not supported on WASI"); +#else // not TARGET_WASI ObjectDisposedException.ThrowIf(IsDisposed, this); cancellationToken.ThrowIfCancellationRequested(); // an early convenience check @@ -592,6 +596,7 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) } // automatically disposes (and unregisters) the callback return true; // done. The wait was satisfied. +#endif // not TARGET_WASI } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.Unix.cs index 359c889c975..cd95eff22d9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.Unix.cs @@ -33,6 +33,10 @@ internal static int PollWasiEventLoopUntilResolved(Task mainTask) return mainTask.Result; } + internal static void DispatchWasiEventLoop() + { + WasiEventLoop.DispatchWasiEventLoop(); + } #endif // the closest analog to Sleep(0) on Unix is sched_yield diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs index 379c5c001c0..6dd243af6c9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs @@ -9,7 +9,7 @@ namespace System.Threading { internal static class WasiEventLoop { - private static List> s_pollables = new(); + private static List s_pollables = new(); internal static Task RegisterWasiPollableHandle(int handle) { @@ -22,8 +22,7 @@ internal static Task RegisterWasiPollableHandle(int handle) internal static Task RegisterWasiPollable(IPoll.Pollable pollable) { var tcs = new TaskCompletionSource(pollable); - var weakRef = new WeakReference(tcs); - s_pollables.Add(weakRef); + s_pollables.Add(tcs); return tcs.Task; } @@ -34,39 +33,36 @@ internal static void DispatchWasiEventLoop() if (s_pollables.Count > 0) { var pollables = s_pollables; - s_pollables = new List>(pollables.Count); + s_pollables = new List(pollables.Count); var arguments = new List(pollables.Count); var indexes = new List(pollables.Count); for (var i = 0; i < pollables.Count; i++) { - var weakRef = pollables[i]; - if (weakRef.TryGetTarget(out TaskCompletionSource? tcs)) - { - var pollable = (IPoll.Pollable)tcs!.Task.AsyncState!; - arguments.Add(pollable); - indexes.Add(i); - } + var tcs = pollables[i]; + var pollable = (IPoll.Pollable)tcs.Task.AsyncState!; + arguments.Add(pollable); + indexes.Add(i); } - // this is blocking until at least one pollable resolves - var readyIndexes = PollInterop.Poll(arguments); - - var ready = new bool[arguments.Count]; - foreach (int readyIndex in readyIndexes) + if (arguments.Count > 0) { - ready[readyIndex] = true; - arguments[readyIndex].Dispose(); - var weakRef = pollables[indexes[readyIndex]]; - if (weakRef.TryGetTarget(out TaskCompletionSource? tcs)) + // this is blocking until at least one pollable resolves + var readyIndexes = PollInterop.Poll(arguments); + + var ready = new bool[arguments.Count]; + foreach (int readyIndex in readyIndexes) { - tcs!.SetResult(); + ready[readyIndex] = true; + arguments[readyIndex].Dispose(); + var tcs = pollables[indexes[readyIndex]]; + tcs.SetResult(); } - } - for (var i = 0; i < arguments.Count; ++i) - { - if (!ready[i]) + for (var i = 0; i < arguments.Count; ++i) { - s_pollables.Add(pollables[indexes[i]]); + if (!ready[i]) + { + s_pollables.Add(pollables[indexes[i]]); + } } } } diff --git a/src/tests/nativeaot/SmokeTests/HelloWasm/HelloWasm.cs b/src/tests/nativeaot/SmokeTests/HelloWasm/HelloWasm.cs index b255b8d8f42..64d0d8898d9 100644 --- a/src/tests/nativeaot/SmokeTests/HelloWasm/HelloWasm.cs +++ b/src/tests/nativeaot/SmokeTests/HelloWasm/HelloWasm.cs @@ -46,7 +46,7 @@ private static unsafe int Main(string[] args) PrintLine("Hello from C#!"); int tempInt = 0; int tempInt2 = 0; - StartTest("Address/derefernce test"); + StartTest("Address/dereference test"); (*(&tempInt)) = 9; EndTest(tempInt == 9); diff --git a/src/tests/nativeaot/SmokeTests/SharedLibrary/.npmrc b/src/tests/nativeaot/SmokeTests/SharedLibrary/.npmrc new file mode 100644 index 00000000000..33312472ae5 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/SharedLibrary/.npmrc @@ -0,0 +1 @@ +registry=https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/ \ No newline at end of file diff --git a/src/tests/nativeaot/SmokeTests/SharedLibrary/Library.cs b/src/tests/nativeaot/SmokeTests/SharedLibrary/Library.cs new file mode 100644 index 00000000000..6f7447039b2 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/SharedLibrary/Library.cs @@ -0,0 +1,176 @@ +// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT! +// +#nullable enable +using System; +using System.Runtime.CompilerServices; +using System.Collections; +using System.Runtime.InteropServices; +using System.Text; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace LibraryWorld { + + public interface ILibraryWorld { + static abstract void TestHttp(ushort port); + + static abstract int ReturnsPrimitiveInt(); + + static abstract bool ReturnsPrimitiveBool(); + + static abstract uint ReturnsPrimitiveChar(); + + static abstract void EnsureManagedClassLoaders(); + + static abstract int CheckSimpleExceptionHandling(); + + static abstract int CheckSimpleGcCollect(); + + } + + public readonly struct None {} + + [StructLayout(LayoutKind.Sequential)] + public readonly struct Result + { + public readonly byte Tag; + private readonly object value; + + private Result(byte tag, object value) + { + Tag = tag; + this.value = value; + } + + public static Result ok(Ok ok) + { + return new Result(OK, ok!); + } + + public static Result err(Err err) + { + return new Result(ERR, err!); + } + + public bool IsOk => Tag == OK; + public bool IsErr => Tag == ERR; + + public Ok AsOk + { + get + { + if (Tag == OK) + return (Ok)value; + else + throw new ArgumentException("expected OK, got " + Tag); + } + } + + public Err AsErr + { + get + { + if (Tag == ERR) + return (Err)value; + else + throw new ArgumentException("expected ERR, got " + Tag); + } + } + + public const byte OK = 0; + public const byte ERR = 1; + } + + namespace exports { + public static class LibraryWorld + { + + [UnmanagedCallersOnly(EntryPoint = "test-http")] + public static unsafe void wasmExportTestHttp(int p0) { + + LibraryWorldImpl.TestHttp((((ushort)p0))); + + } + + [UnmanagedCallersOnly(EntryPoint = "returns-primitive-int")] + public static unsafe int wasmExportReturnsPrimitiveInt() { + + int ret; + ret = LibraryWorldImpl.ReturnsPrimitiveInt(); + return ret; + + } + + [UnmanagedCallersOnly(EntryPoint = "cabi_post_returns-primitive-int")] + public static void cabi_post_wasmExportReturnsPrimitiveInt(int returnValue) { + Console.WriteLine("TODO: cabi_post_returns-primitive-int"); + } + + [UnmanagedCallersOnly(EntryPoint = "returns-primitive-bool")] + public static unsafe int wasmExportReturnsPrimitiveBool() { + + bool ret; + ret = LibraryWorldImpl.ReturnsPrimitiveBool(); + return (ret ? 1 : 0); + + } + + [UnmanagedCallersOnly(EntryPoint = "cabi_post_returns-primitive-bool")] + public static void cabi_post_wasmExportReturnsPrimitiveBool(int returnValue) { + Console.WriteLine("TODO: cabi_post_returns-primitive-bool"); + } + + [UnmanagedCallersOnly(EntryPoint = "returns-primitive-char")] + public static unsafe int wasmExportReturnsPrimitiveChar() { + + uint ret; + ret = LibraryWorldImpl.ReturnsPrimitiveChar(); + return ((int)ret); + + } + + [UnmanagedCallersOnly(EntryPoint = "cabi_post_returns-primitive-char")] + public static void cabi_post_wasmExportReturnsPrimitiveChar(int returnValue) { + Console.WriteLine("TODO: cabi_post_returns-primitive-char"); + } + + [UnmanagedCallersOnly(EntryPoint = "ensure-managed-class-loaders")] + public static unsafe void wasmExportEnsureManagedClassLoaders() { + + LibraryWorldImpl.EnsureManagedClassLoaders(); + + } + + [UnmanagedCallersOnly(EntryPoint = "check-simple-exception-handling")] + public static unsafe int wasmExportCheckSimpleExceptionHandling() { + + int ret; + ret = LibraryWorldImpl.CheckSimpleExceptionHandling(); + return ret; + + } + + [UnmanagedCallersOnly(EntryPoint = "cabi_post_check-simple-exception-handling")] + public static void cabi_post_wasmExportCheckSimpleExceptionHandling(int returnValue) { + Console.WriteLine("TODO: cabi_post_check-simple-exception-handling"); + } + + [UnmanagedCallersOnly(EntryPoint = "check-simple-gc-collect")] + public static unsafe int wasmExportCheckSimpleGcCollect() { + + int ret; + ret = LibraryWorldImpl.CheckSimpleGcCollect(); + return ret; + + } + + [UnmanagedCallersOnly(EntryPoint = "cabi_post_check-simple-gc-collect")] + public static void cabi_post_wasmExportCheckSimpleGcCollect(int returnValue) { + Console.WriteLine("TODO: cabi_post_check-simple-gc-collect"); + } + + } + } + +} diff --git a/src/tests/nativeaot/SmokeTests/SharedLibrary/LibraryWorld_component_type.wit b/src/tests/nativeaot/SmokeTests/SharedLibrary/LibraryWorld_component_type.wit new file mode 100644 index 00000000000..3dd01916452 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/SharedLibrary/LibraryWorld_component_type.wit @@ -0,0 +1,11 @@ +package local:local; + +world library { + export test-http: func(port: u16); + export returns-primitive-int: func() -> s32; + export returns-primitive-bool: func() -> bool; + export returns-primitive-char: func() -> char; + export ensure-managed-class-loaders: func(); + export check-simple-exception-handling: func() -> s32; + export check-simple-gc-collect: func() -> s32; +} diff --git a/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibrary.cs b/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibrary.cs index a6c2c7e74ec..4681d08f7d1 100644 --- a/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibrary.cs +++ b/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibrary.cs @@ -2,8 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; namespace SharedLibrary { @@ -36,6 +43,11 @@ public static void EnsureManagedClassLoaders() [UnmanagedCallersOnly(EntryPoint = "CheckSimpleExceptionHandling", CallConvs = new Type[] { typeof(CallConvStdcall) })] public static int CheckSimpleExceptionHandling() + { + return DoCheckSimpleExceptionHandling(); + } + + public static int DoCheckSimpleExceptionHandling() { int result = 10; @@ -79,6 +91,11 @@ private static void MakeGarbage() [UnmanagedCallersOnly(EntryPoint = "CheckSimpleGCCollect", CallConvs = new Type[] { typeof(CallConvStdcall) })] public static int CheckSimpleGCCollect() + { + return DoCheckSimpleGCCollect(); + } + + public static int DoCheckSimpleGCCollect() { string myString = string.Format("Hello {0}", "world"); @@ -93,3 +110,121 @@ public static int CheckSimpleGCCollect() } } } + +// Implements the component model interface defined in wit/world.wit +namespace LibraryWorld +{ + public class LibraryWorldImpl : ILibraryWorld + { + public static void TestHttp(ushort port) + { + var task = TestHttpAsync(port); + while (!task.IsCompleted) + { + WasiEventLoop.DispatchWasiEventLoop(); + } + var exception = task.Exception; + if (exception is not null) + { + throw exception; + } + } + + private static async Task TestHttpAsync(ushort port) + { + using var client = new HttpClient(); + var urlBase = $"http://127.0.0.1:{port}"; + + { + var response = await client.GetAsync($"{urlBase}/hello"); + response.EnsureSuccessStatusCode(); + Trace.Assert( + 4 == response.Content.Headers.ContentLength, + $"unexpected content length: {response.Content.Headers.ContentLength}" + ); + Trace.Assert( + "text/plain".Equals(response.Content.Headers.ContentType.ToString()), + $"unexpected content type: \"{response.Content.Headers.ContentType}\"" + ); + var content = await response.Content.ReadAsStringAsync(); + Trace.Assert("hola".Equals(content), $"unexpected content: \"{content}\""); + } + + { + var length = 10 * 1024 * 1024; + var body = new byte[length]; + new Random().NextBytes(body); + + var content = new StreamContent(new MemoryStream(body)); + var type = "application/octet-stream"; + content.Headers.ContentType = new MediaTypeHeaderValue(type); + + var response = await client.PostAsync($"{urlBase}/echo", content); + response.EnsureSuccessStatusCode(); + Trace.Assert( + length == response.Content.Headers.ContentLength, + $"unexpected content length: {response.Content.Headers.ContentLength}" + ); + Trace.Assert( + type.Equals(response.Content.Headers.ContentType.ToString()), + $"unexpected content type: \"{response.Content.Headers.ContentType}\"" + ); + var received = await response.Content.ReadAsByteArrayAsync(); + Trace.Assert(body.SequenceEqual(received), "unexpected content"); + } + + using var impatientClient = new HttpClient(); + impatientClient.Timeout = TimeSpan.FromMilliseconds(100); + try { + await impatientClient.GetAsync($"{urlBase}/slow-hello"); + throw new Exception("request to /slow-hello endpoint should have timed out"); + } catch (TaskCanceledException _) { + // The /slow-hello endpoint takes 10 seconds to return a + // response, whereas we've set a 100ms timeout, so this is + // expected. + } + } + + public static int ReturnsPrimitiveInt() + { + return 10; + } + + public static bool ReturnsPrimitiveBool() + { + return true; + } + + public static uint ReturnsPrimitiveChar() + { + return (uint)'a'; + } + + public static void EnsureManagedClassLoaders() + { + Random random = new Random(); + random.Next(); + } + + public static int CheckSimpleExceptionHandling() + { + return SharedLibrary.ClassLibrary.DoCheckSimpleExceptionHandling(); + } + + public static int CheckSimpleGcCollect() + { + return SharedLibrary.ClassLibrary.DoCheckSimpleGCCollect(); + } + } + + internal static class WasiEventLoop + { + internal static void DispatchWasiEventLoop() + { + CallDispatchWasiEventLoop((Thread)null!); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "DispatchWasiEventLoop")] + static extern void CallDispatchWasiEventLoop(Thread t); + } + } +} diff --git a/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibrary.csproj b/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibrary.csproj index dd725ef9d8b..800baeea78b 100644 --- a/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibrary.csproj +++ b/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibrary.csproj @@ -10,9 +10,6 @@ true - - - true @@ -33,6 +30,7 @@ cp SharedLibraryDriver native/SharedLibrary + @@ -58,5 +56,6 @@ cp SharedLibraryDriver$(NativeExtension) native/SharedLibrary$(NativeExtension) + diff --git a/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibraryDriver.mjs b/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibraryDriver.mjs index 434aab734ef..3988c4ba076 100644 --- a/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibraryDriver.mjs +++ b/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibraryDriver.mjs @@ -1,37 +1,106 @@ -import { readFile } from 'node:fs/promises'; -import { WASI } from 'wasi'; -import { argv, env } from 'node:process'; +import { Worker, parentPort, isMainThread } from 'node:worker_threads'; +import { readFile, writeFile } from 'node:fs/promises'; +import { transpile } from '@bytecodealliance/jco'; +import { _setPreopens } from '@bytecodealliance/preview2-shim/filesystem'; +import * as http from 'node:http'; -const wasi = new WASI({ - version: 'preview1', - args: argv, - env -}); +// Note that `jco` implements `wasi:io` by synchronously dispatching to worker +// threads. That means that when we run `HttpClient` smoke test below, it will +// block the main thread waiting for I/O. Therefore, the HTTP server we launch +// for that test needs to run in a dedicated thread rather than the main thread. -const wasm = await WebAssembly.compile( - await readFile(new URL("./SharedLibrary.wasm", import.meta.url)), -); +if (isMainThread) { + // Run the tests + const base = import.meta.url; + const component = await readFile(new URL("./SharedLibrary.wasm", base)); + const transpiled = await transpile(component, { + name: "shared-library", + typescript: false, + }); + await writeFile(new URL("./shared-library.core.wasm", base), transpiled.files["shared-library.core.wasm"]); + await writeFile(new URL("./shared-library.core2.wasm", base), transpiled.files["shared-library.core2.wasm"]); + await writeFile(new URL("./shared-library.mjs", base), transpiled.files["shared-library.js"]); + _setPreopens([]); + const instance = await import(new URL("./shared-library.mjs", base)); -const instance = await WebAssembly.instantiate(wasm, wasi.getImportObject()); + // Spawn a worker thread to run the HTTP server and feed the port number + // it's listening on back to us when ready: + const port = await new Promise((resolve, reject) => { + const worker = new Worker(new URL("./SharedLibrary.mjs", base)); + worker.on("message", resolve); + worker.on("error", reject); + worker.on("exit", (code) => { + reject(new Error(`worker stopped with exit code ${code}`)); + }); + }); -wasi.initialize(instance); + instance.testHttp(port); -if (instance.exports.ReturnsPrimitiveInt() != 10) - process.exit(1); + if (instance.returnsPrimitiveInt() != 10) + process.exit(1); -if (instance.exports.ReturnsPrimitiveBool() != 1) - process.exit(2); + if (instance.returnsPrimitiveBool() != 1) + process.exit(2); -if (instance.exports.ReturnsPrimitiveChar() != 97) // 'a' - process.exit(3); + if (instance.returnsPrimitiveChar() != 'a') + process.exit(3); -// As long as no unmanaged exception is thrown managed class loaders were initialized successfully. -instance.exports.EnsureManagedClassLoaders(); + // As long as no unmanaged exception is thrown managed class loaders were initialized successfully. + instance.ensureManagedClassLoaders(); -if (instance.exports.CheckSimpleGCCollect() != 100) - process.exit(4); + if (instance.checkSimpleGcCollect() != 100) + process.exit(4); -if (instance.exports.CheckSimpleExceptionHandling() != 100) - process.exit(5); + if (instance.checkSimpleExceptionHandling() != 100) + process.exit(5); -process.exit(100); + process.exit(100); +} else { + // Run the HTTP server + const server = http.createServer((req, res) => { + if (req.method === "POST" && req.url === "/echo") { + // Note that we buffer the request body here rather than pipe it + // directly to the response. That's because, as of this writing, + // `WasiHttpHandler` sends the entire request body before reading + // any of the response body, which can lead to deadlock if the + // server is blocked on backpressure when sending the response body. + let chunks = []; + req.on("data", (chunk) => { + chunks.push(chunk); + }); + req.on("end", () => { + res.writeHead(200, req.headers); + res.end(Buffer.concat(chunks)); + }); + } else if (req.method === "GET" && req.url === "/slow-hello") { + setTimeout(() => { + const body = "hola"; + res + .writeHead(200, { + "content-length": Buffer.byteLength(body), + "content-type": "text/plain", + }) + .end(body); + }, 10 * 1000); + } else { + let status; + let body; + if (req.method === "GET" && req.url === "/hello") { + status = 200; + body = "hola"; + } else { + status = 400; + body = "Bad Request"; + } + res + .writeHead(status, { + "content-length": Buffer.byteLength(body), + "content-type": "text/plain", + }) + .end(body); + } + }); + server.listen(() => { + parentPort.postMessage(server.address().port); + }); +} diff --git a/src/tests/nativeaot/SmokeTests/SharedLibrary/generate-shared-library-bindings.sh b/src/tests/nativeaot/SmokeTests/SharedLibrary/generate-shared-library-bindings.sh new file mode 100644 index 00000000000..4a051a50554 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/SharedLibrary/generate-shared-library-bindings.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +set -ex + +# This script will regenerate the `wit-bindgen`-generated files in this +# directory. + +# Prerequisites: +# POSIX shell +# tar +# [cargo](https://rustup.rs/) +# [curl](https://curl.se/download.html) + +cargo install --locked --no-default-features --features csharp --version 0.29.0 wit-bindgen-cli +wit-bindgen c-sharp -w library -r native-aot wit +rm LibraryWorld_wasm_import_linkage_attribute.cs LibraryWorld_cabi_realloc.c LibraryWorld_component_type.o diff --git a/src/tests/nativeaot/SmokeTests/SharedLibrary/package-lock.json b/src/tests/nativeaot/SmokeTests/SharedLibrary/package-lock.json new file mode 100644 index 00000000000..0b2ea4e1793 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/SharedLibrary/package-lock.json @@ -0,0 +1,698 @@ +{ + "name": "sharedlibrarytest", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "sharedlibrarytest", + "version": "1.0.0", + "dependencies": { + "@bytecodealliance/jco": "1.3.1" + } + }, + "node_modules/@bytecodealliance/jco": { + "version": "1.3.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@bytecodealliance/jco/-/jco-1.3.1.tgz", + "integrity": "sha1-k/ZHBglXNPz66y9DL7JIuXLuFeg=", + "license": "(Apache-2.0 WITH LLVM-exception)", + "workspaces": [ + "packages/preview2-shim" + ], + "dependencies": { + "@bytecodealliance/preview2-shim": "^0.16.3", + "binaryen": "^116.0.0", + "chalk-template": "^1", + "commander": "^12", + "mkdirp": "^3", + "ora": "^8", + "terser": "^5" + }, + "bin": { + "jco": "src/jco.js" + } + }, + "node_modules/@bytecodealliance/preview2-shim": { + "version": "0.16.4", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@bytecodealliance/preview2-shim/-/preview2-shim-0.16.4.tgz", + "integrity": "sha1-Y3CfvaiO8u7hxJLiGP3+GWPOIaw=", + "license": "(Apache-2.0 WITH LLVM-exception)" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha1-3M5q/3S99trRqVgCtpsEovyx+zY=", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha1-eg7mAfYPmaIMfHxf8MgDiMEYm9Y=", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha1-VY+2Ry7RakyFC4iVMOazZDjEkoA=", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha1-nXHKiG4yUC65NiyadKRnh8Nt+Bo=", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha1-18bmdVx4VnqVHgSrUu8P0m3lnzI=", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha1-FfGQ6YiV8/wjJ27hS8drZ1wuUPA=", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha1-cWFr3MviXielRDngBG6JynbfIkg=", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha1-MYPjj66aZdfLXlOUXNWJfQJgoGo=", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/binaryen": { + "version": "116.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/binaryen/-/binaryen-116.0.0.tgz", + "integrity": "sha1-rXn7k5JGE4+xKNlDjVV0FS7OOf4=", + "license": "Apache-2.0", + "bin": { + "wasm-opt": "bin/wasm-opt", + "wasm2js": "bin/wasm2js" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha1-KxRqb9cugLT1XSVfNe1Zo6mkG9U=", + "license": "MIT" + }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha1-Z8IKfr73Dn85cKAfkPohDLaGA4U=", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template": { + "version": "1.1.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/chalk-template/-/chalk-template-1.1.0.tgz", + "integrity": "sha1-/8Vdtt10XpOUuFMnyKyEZu23p7E=", + "license": "MIT", + "dependencies": { + "chalk": "^5.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha1-POz+NzS/T+Aqg2HL3A9v4oxqV+o=", + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha1-F3Oo9LnE1qwxVj31Oz/B15Ri/kE=", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/commander/-/commander-12.1.0.tgz", + "integrity": "sha1-AUI7NvUBJZ/arE0OTWDJbJkVhdM=", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha1-dpmLkmhAnrPa496YklTUVucM/iM=", + "license": "MIT" + }, + "node_modules/get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha1-Xm69m67m+4t7a9UFIhBl8M2R9k4=", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha1-QMV2FFk4JtoRAK3mBZd41ZfxbpA=", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz", + "integrity": "sha1-/fMt+a6Y/2qyztwVWlpuiVcBxFE=", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha1-u5Xl8FMiZRysMMD+tkBPnyqKlDk=", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha1-2CSYS2FsKSouGYIH1KYJmDhC9xQ=", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha1-ftLCzMyvhNP/y3pptXcR/CCDQBs=", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha1-5E5MVgf7J5wWgkFxPMbg/qmty1A=", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha1-0Oluu1awdHbfHdnEgG5SN5hcpF4=", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "8.0.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/ora/-/ora-8.0.1.tgz", + "integrity": "sha1-bcuSUKYpZCy+DS3zpjMa1veirz4=", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha1-UZVgpDGJdQlt725gnUQQDtqkzLk=", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha1-qaF2f4r4QVURTqq9c/mSc8j1mtk=", + "license": "ISC" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha1-BP58f54e0tZiIzwoyys1ufY/bk8=", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha1-OQA39ExK4aGuU1xf443Dq6jZl74=", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha1-tbuOIWXOJ11NQ0dt0nAK2Qkdttw=", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha1-1bZWjKaJ2FYTcLBwdoXSJDT6/0U=", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/terser": { + "version": "5.31.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/terser/-/terser-5.31.1.tgz", + "integrity": "sha1-c13jyYfdZx6VGQ5rmM/i8H888NQ=", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/commander/-/commander-2.20.3.tgz", + "integrity": "sha1-/UhehMA+tIgcIHIrpIA16FMa6zM=", + "license": "MIT" + } + }, + "dependencies": { + "@bytecodealliance/jco": { + "version": "1.3.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@bytecodealliance/jco/-/jco-1.3.1.tgz", + "integrity": "sha1-k/ZHBglXNPz66y9DL7JIuXLuFeg=", + "requires": { + "@bytecodealliance/preview2-shim": "^0.16.3", + "binaryen": "^116.0.0", + "chalk-template": "^1", + "commander": "^12", + "mkdirp": "^3", + "ora": "^8", + "terser": "^5" + } + }, + "@bytecodealliance/preview2-shim": { + "version": "0.16.4", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@bytecodealliance/preview2-shim/-/preview2-shim-0.16.4.tgz", + "integrity": "sha1-Y3CfvaiO8u7hxJLiGP3+GWPOIaw=" + }, + "@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha1-3M5q/3S99trRqVgCtpsEovyx+zY=", + "requires": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha1-eg7mAfYPmaIMfHxf8MgDiMEYm9Y=" + }, + "@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha1-VY+2Ry7RakyFC4iVMOazZDjEkoA=" + }, + "@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha1-nXHKiG4yUC65NiyadKRnh8Nt+Bo=", + "requires": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha1-18bmdVx4VnqVHgSrUu8P0m3lnzI=" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha1-FfGQ6YiV8/wjJ27hS8drZ1wuUPA=", + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "acorn": { + "version": "8.12.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha1-cWFr3MviXielRDngBG6JynbfIkg=" + }, + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha1-MYPjj66aZdfLXlOUXNWJfQJgoGo=" + }, + "binaryen": { + "version": "116.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/binaryen/-/binaryen-116.0.0.tgz", + "integrity": "sha1-rXn7k5JGE4+xKNlDjVV0FS7OOf4=" + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha1-KxRqb9cugLT1XSVfNe1Zo6mkG9U=" + }, + "chalk": { + "version": "5.3.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha1-Z8IKfr73Dn85cKAfkPohDLaGA4U=" + }, + "chalk-template": { + "version": "1.1.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/chalk-template/-/chalk-template-1.1.0.tgz", + "integrity": "sha1-/8Vdtt10XpOUuFMnyKyEZu23p7E=", + "requires": { + "chalk": "^5.2.0" + } + }, + "cli-cursor": { + "version": "4.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha1-POz+NzS/T+Aqg2HL3A9v4oxqV+o=", + "requires": { + "restore-cursor": "^4.0.0" + } + }, + "cli-spinners": { + "version": "2.9.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha1-F3Oo9LnE1qwxVj31Oz/B15Ri/kE=" + }, + "commander": { + "version": "12.1.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/commander/-/commander-12.1.0.tgz", + "integrity": "sha1-AUI7NvUBJZ/arE0OTWDJbJkVhdM=" + }, + "emoji-regex": { + "version": "10.3.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha1-dpmLkmhAnrPa496YklTUVucM/iM=" + }, + "get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha1-Xm69m67m+4t7a9UFIhBl8M2R9k4=" + }, + "is-interactive": { + "version": "2.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha1-QMV2FFk4JtoRAK3mBZd41ZfxbpA=" + }, + "is-unicode-supported": { + "version": "2.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz", + "integrity": "sha1-/fMt+a6Y/2qyztwVWlpuiVcBxFE=" + }, + "log-symbols": { + "version": "6.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha1-u5Xl8FMiZRysMMD+tkBPnyqKlDk=", + "requires": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "dependencies": { + "is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha1-2CSYS2FsKSouGYIH1KYJmDhC9xQ=" + } + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha1-ftLCzMyvhNP/y3pptXcR/CCDQBs=" + }, + "mkdirp": { + "version": "3.0.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha1-5E5MVgf7J5wWgkFxPMbg/qmty1A=" + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha1-0Oluu1awdHbfHdnEgG5SN5hcpF4=", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "ora": { + "version": "8.0.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/ora/-/ora-8.0.1.tgz", + "integrity": "sha1-bcuSUKYpZCy+DS3zpjMa1veirz4=", + "requires": { + "chalk": "^5.3.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + } + }, + "restore-cursor": { + "version": "4.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha1-UZVgpDGJdQlt725gnUQQDtqkzLk=", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha1-qaF2f4r4QVURTqq9c/mSc8j1mtk=" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=" + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha1-BP58f54e0tZiIzwoyys1ufY/bk8=", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "stdin-discarder": { + "version": "0.2.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha1-OQA39ExK4aGuU1xf443Dq6jZl74=" + }, + "string-width": { + "version": "7.2.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha1-tbuOIWXOJ11NQ0dt0nAK2Qkdttw=", + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha1-1bZWjKaJ2FYTcLBwdoXSJDT6/0U=", + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "terser": { + "version": "5.31.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/terser/-/terser-5.31.1.tgz", + "integrity": "sha1-c13jyYfdZx6VGQ5rmM/i8H888NQ=", + "requires": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/commander/-/commander-2.20.3.tgz", + "integrity": "sha1-/UhehMA+tIgcIHIrpIA16FMa6zM=" + } + } + } + } +} diff --git a/src/tests/nativeaot/SmokeTests/SharedLibrary/package.json b/src/tests/nativeaot/SmokeTests/SharedLibrary/package.json new file mode 100644 index 00000000000..3d278ba3cb3 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/SharedLibrary/package.json @@ -0,0 +1,11 @@ +{ + "name": "sharedlibrarytest", + "type": "module", + "version": "1.0.0", + "scripts": { + "test": "node SharedLibraryDriver.mjs" + }, + "dependencies": { + "@bytecodealliance/jco": "1.3.1" + } +} \ No newline at end of file diff --git a/src/tests/nativeaot/SmokeTests/SharedLibrary/wit/world.wit b/src/tests/nativeaot/SmokeTests/SharedLibrary/wit/world.wit new file mode 100644 index 00000000000..3dd01916452 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/SharedLibrary/wit/world.wit @@ -0,0 +1,11 @@ +package local:local; + +world library { + export test-http: func(port: u16); + export returns-primitive-int: func() -> s32; + export returns-primitive-bool: func() -> bool; + export returns-primitive-char: func() -> char; + export ensure-managed-class-loaders: func(); + export check-simple-exception-handling: func() -> s32; + export check-simple-gc-collect: func() -> s32; +} From 28d758f866f78abd9631ae9ed6cb196d8037361f Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Mon, 26 Aug 2024 13:52:13 -0600 Subject: [PATCH 02/20] make `HttpClient.Timeout` actually work on WASI This requires passing the `CancellationToken` to `WasiEventLoop` and checking it before polling. Signed-off-by: Joel Dice --- .../Http/WasiHttpHandler/WasiHttpHandler.cs | 27 ++++++--- .../src/System/Threading/Thread.Unix.cs | 4 +- .../System/Threading/Wasi/WasiEventLoop.cs | 55 ++++++++++++++----- .../System/Threading/TimerQueue.Wasi.Mono.cs | 2 +- 4 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpHandler.cs index dcccb989910..6e115123873 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpHandler.cs @@ -242,7 +242,7 @@ CancellationToken cancellationToken await Task.WhenAll( new Task[] { - SendRequestAsync(outgoingRequest), + SendRequestAsync(outgoingRequest, cancellationToken), sendContent() } ) @@ -279,7 +279,8 @@ await Task.WhenAll( } private static async Task SendRequestAsync( - ITypes.OutgoingRequest request + ITypes.OutgoingRequest request, + CancellationToken cancellationToken ) { ITypes.FutureIncomingResponse future; @@ -314,7 +315,8 @@ ITypes.OutgoingRequest request } else { - await RegisterWasiPollable(future.Subscribe()).ConfigureAwait(false); + await RegisterWasiPollable(future.Subscribe(), cancellationToken) + .ConfigureAwait(false); } } } @@ -461,7 +463,10 @@ private static async Task SendContentAsync(HttpContent? content, Stream stream) } } - private static Task RegisterWasiPollable(IPoll.Pollable pollable) + private static Task RegisterWasiPollable( + IPoll.Pollable pollable, + CancellationToken cancellationToken + ) { var handle = pollable.Handle; @@ -470,12 +475,15 @@ private static Task RegisterWasiPollable(IPoll.Pollable pollable) pollable.Handle = 0; GC.SuppressFinalize(pollable); - return CallRegisterWasiPollableHandle((Thread)null!, handle); - + return CallRegisterWasiPollableHandle((Thread)null!, handle, cancellationToken); } [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "RegisterWasiPollableHandle")] - private static extern Task CallRegisterWasiPollableHandle(Thread t, int handle); + private static extern Task CallRegisterWasiPollableHandle( + Thread t, + int handle, + CancellationToken cancellationToken + ); private sealed class InputStream : Stream { @@ -562,7 +570,7 @@ CancellationToken cancellationToken var buffer = result; if (buffer.Length == 0) { - await RegisterWasiPollable(stream.Subscribe()) + await RegisterWasiPollable(stream.Subscribe(), cancellationToken) .ConfigureAwait(false); } else @@ -699,7 +707,8 @@ CancellationToken cancellationToken var count = (int)stream.CheckWrite(); if (count == 0) { - await RegisterWasiPollable(stream.Subscribe()).ConfigureAwait(false); + await RegisterWasiPollable(stream.Subscribe(), cancellationToken) + .ConfigureAwait(false); } else if (offset == limit) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.Unix.cs index cd95eff22d9..9bc569376fd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.Unix.cs @@ -13,9 +13,9 @@ public sealed partial class Thread { // these methods are temporarily accessed via UnsafeAccessor from generated code until we have it in public API, probably in WASI preview3 and promises #if TARGET_WASI - internal static System.Threading.Tasks.Task RegisterWasiPollableHandle(int handle) + internal static System.Threading.Tasks.Task RegisterWasiPollableHandle(int handle, CancellationToken cancellationToken) { - return WasiEventLoop.RegisterWasiPollableHandle(handle); + return WasiEventLoop.RegisterWasiPollableHandle(handle, cancellationToken); } internal static int PollWasiEventLoopUntilResolved(Task mainTask) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs index 6dd243af6c9..7e6c87b27d6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs @@ -11,17 +11,23 @@ internal static class WasiEventLoop { private static List s_pollables = new(); - internal static Task RegisterWasiPollableHandle(int handle) + internal static Task RegisterWasiPollableHandle( + int handle, + CancellationToken cancellationToken + ) { // note that this is duplicate of the original Pollable // the original should be neutralized without disposing the handle var pollableCpy = new IPoll.Pollable(new IPoll.Pollable.THandle(handle)); - return RegisterWasiPollable(pollableCpy); + return RegisterWasiPollable(pollableCpy, cancellationToken); } - internal static Task RegisterWasiPollable(IPoll.Pollable pollable) + internal static Task RegisterWasiPollable( + IPoll.Pollable pollable, + CancellationToken cancellationToken + ) { - var tcs = new TaskCompletionSource(pollable); + var tcs = new TaskCompletionSource((pollable, cancellationToken)); s_pollables.Add(tcs); return tcs.Task; } @@ -36,27 +42,46 @@ internal static void DispatchWasiEventLoop() s_pollables = new List(pollables.Count); var arguments = new List(pollables.Count); var indexes = new List(pollables.Count); + var tasksCanceled = false; for (var i = 0; i < pollables.Count; i++) { var tcs = pollables[i]; - var pollable = (IPoll.Pollable)tcs.Task.AsyncState!; - arguments.Add(pollable); - indexes.Add(i); + var (pollable, cancellationToken) = ((IPoll.Pollable, CancellationToken)) + tcs.Task.AsyncState!; + if (cancellationToken.IsCancellationRequested) + { + tcs.SetException(new TaskCanceledException()); + tasksCanceled = true; + } + else + { + arguments.Add(pollable); + indexes.Add(i); + } } if (arguments.Count > 0) { - // this is blocking until at least one pollable resolves - var readyIndexes = PollInterop.Poll(arguments); - var ready = new bool[arguments.Count]; - foreach (int readyIndex in readyIndexes) + + // If at least one task was canceled, we'll return without + // calling `poll` (i.e. delay calling `poll` until the next + // call to this function) to give any dependent tasks a + // chance to make progress before we block. + if (!tasksCanceled) { - ready[readyIndex] = true; - arguments[readyIndex].Dispose(); - var tcs = pollables[indexes[readyIndex]]; - tcs.SetResult(); + // this is blocking until at least one pollable resolves + var readyIndexes = PollInterop.Poll(arguments); + + foreach (int readyIndex in readyIndexes) + { + ready[readyIndex] = true; + arguments[readyIndex].Dispose(); + var tcs = pollables[indexes[readyIndex]]; + tcs.SetResult(); + } } + for (var i = 0; i < arguments.Count; ++i) { if (!ready[i]) diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Wasi.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Wasi.Mono.cs index 20c017cce1f..385134bdcc1 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Wasi.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Wasi.Mono.cs @@ -81,7 +81,7 @@ private static unsafe void SetNextTimer(long shortestDueTimeMs, long currentTime // `SubscribeDuration` expects nanoseconds: var pollable = MonotonicClockInterop.SubscribeDuration(shortestWaitMs * 1000 * 1000); - Task task = WasiEventLoop.RegisterWasiPollable(pollable); + Task task = WasiEventLoop.RegisterWasiPollable(pollable, new CancellationToken(false)); task.ContinueWith(TimerHandler, TaskScheduler.Default); } } From e32a223a1c054ee15f8934c592d6800835dffd1b Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Mon, 26 Aug 2024 16:42:21 -0600 Subject: [PATCH 03/20] fix file count confusion in SharedLibraryDriver.mjs Sometimes there are two Wasm files in Jco's output; sometimes three. In any case, hard-coding the number won't fly. Signed-off-by: Joel Dice --- .../SmokeTests/SharedLibrary/SharedLibraryDriver.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibraryDriver.mjs b/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibraryDriver.mjs index 3988c4ba076..89ec207a009 100644 --- a/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibraryDriver.mjs +++ b/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibraryDriver.mjs @@ -17,8 +17,9 @@ if (isMainThread) { name: "shared-library", typescript: false, }); - await writeFile(new URL("./shared-library.core.wasm", base), transpiled.files["shared-library.core.wasm"]); - await writeFile(new URL("./shared-library.core2.wasm", base), transpiled.files["shared-library.core2.wasm"]); + for (const key of Object.keys(transpiled.files)) { + await writeFile(new URL(key, base), transpiled.files[key]); + } await writeFile(new URL("./shared-library.mjs", base), transpiled.files["shared-library.js"]); _setPreopens([]); const instance = await import(new URL("./shared-library.mjs", base)); From e79642edc920f59aa30c59792cee092c09623030 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Tue, 27 Aug 2024 07:33:36 -0600 Subject: [PATCH 04/20] fix Linux tests Signed-off-by: Joel Dice --- eng/testing/FindWasmHostExecutable.sh | 2 -- eng/testing/FindWasmHostExecutableAndRun.sh | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/eng/testing/FindWasmHostExecutable.sh b/eng/testing/FindWasmHostExecutable.sh index 4f59d9abf7e..556d8b7cc79 100755 --- a/eng/testing/FindWasmHostExecutable.sh +++ b/eng/testing/FindWasmHostExecutable.sh @@ -6,7 +6,6 @@ exename=$(basename "$exename" .js) dirname=$(dirname "$1") node="node --stack_trace_limit=100" -WASM_HOST_ARGS_SEPERATOR="" if [ -e "${dirname}/${exename}.js" ]; then WASM_HOST_EXECUTABLE=$node @@ -25,6 +24,5 @@ elif [ -e "${dirname}/${exename}.wasm" ]; then WASMTIME_EXECUTABLE=wasmtime fi WASM_HOST_EXECUTABLE="$WASMTIME_EXECUTABLE run -S http" - WASM_HOST_ARGS_SEPERATOR="--" WASM_BINARY_TO_EXECUTE="${dirname}/${exename}.wasm" fi diff --git a/eng/testing/FindWasmHostExecutableAndRun.sh b/eng/testing/FindWasmHostExecutableAndRun.sh index 1f84e2a4533..95d60fa6ce8 100755 --- a/eng/testing/FindWasmHostExecutableAndRun.sh +++ b/eng/testing/FindWasmHostExecutableAndRun.sh @@ -6,8 +6,8 @@ source $SCRIPT_DIR/FindWasmHostExecutable.sh "$1" if [ -n "${WASM_HOST_EXECUTABLE}" ]; then shift - echo $WASM_HOST_EXECUTABLE "$WASM_BINARY_TO_EXECUTE" $WASM_HOST_ARGS_SEPERATOR "$@" - $WASM_HOST_EXECUTABLE "$WASM_BINARY_TO_EXECUTE" $WASM_HOST_ARGS_SEPERATOR "$@" + echo $WASM_HOST_EXECUTABLE "$WASM_BINARY_TO_EXECUTE" "$@" + $WASM_HOST_EXECUTABLE "$WASM_BINARY_TO_EXECUTE" "$@" else echo WASM_HOST_EXECUTABLE not set. exit 1 From 2f3ce607880dfa04d6c1bfde04d23279f911b6af Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Tue, 27 Aug 2024 07:55:21 -0600 Subject: [PATCH 05/20] use `TaskCompletionSource.SetCanceled` instead of `SetException` Signed-off-by: Joel Dice --- .../src/System/Threading/Wasi/WasiEventLoop.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs index 7e6c87b27d6..4ad7dff12fe 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs @@ -50,7 +50,7 @@ internal static void DispatchWasiEventLoop() tcs.Task.AsyncState!; if (cancellationToken.IsCancellationRequested) { - tcs.SetException(new TaskCanceledException()); + tcs.SetCanceled(cancellationToken); tasksCanceled = true; } else From 18f9f6c7311ec57b95b3b2646513eb6f4bd2dc22 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 28 Aug 2024 09:29:12 -0600 Subject: [PATCH 06/20] use `Stopwatch` in `HttpClient` test to verify timeout works as expected Signed-off-by: Joel Dice --- .../nativeaot/SmokeTests/SharedLibrary/SharedLibrary.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibrary.cs b/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibrary.cs index 4681d08f7d1..609482269a6 100644 --- a/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibrary.cs +++ b/src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibrary.cs @@ -175,6 +175,8 @@ private static async Task TestHttpAsync(ushort port) using var impatientClient = new HttpClient(); impatientClient.Timeout = TimeSpan.FromMilliseconds(100); + var stopwatch = new Stopwatch(); + stopwatch.Start(); try { await impatientClient.GetAsync($"{urlBase}/slow-hello"); throw new Exception("request to /slow-hello endpoint should have timed out"); @@ -183,6 +185,9 @@ private static async Task TestHttpAsync(ushort port) // response, whereas we've set a 100ms timeout, so this is // expected. } + stopwatch.Stop(); + Trace.Assert(stopwatch.ElapsedMilliseconds >= 100); + Trace.Assert(stopwatch.ElapsedMilliseconds < 1000); } public static int ReturnsPrimitiveInt() From ebdbabf5288ea679aeaf59c29a61e1969fdf9d53 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 28 Aug 2024 09:30:44 -0600 Subject: [PATCH 07/20] provide more informative exception messages in `WasiHttpHandler` Signed-off-by: Joel Dice --- .../Http/WasiHttpHandler/WasiHttpHandler.cs | 52 +++++++++++++++++-- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpHandler.cs index 6e115123873..cd18da5526c 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpHandler.cs @@ -221,7 +221,35 @@ CancellationToken cancellationToken } } - var outgoingRequest = new ITypes.OutgoingRequest(ITypes.Fields.FromList(headers)); + ITypes.Fields fields; + try + { + fields = ITypes.Fields.FromList(headers); + } + catch (WitException e) + { + var error = (ITypes.HeaderError)e.Value; + var buffer = new StringBuilder($"header error: {HeaderErrorToString(error)}\n"); + foreach (var pair in request.Headers) + { + foreach (var value in pair.Value) + { + buffer.Append($"\n{pair.Key}: {value}"); + } + } + if (request.Content is not null) + { + foreach (var pair in request.Content.Headers) + { + foreach (var value in pair.Value) + { + buffer.Append($"\n{pair.Key}: {value}"); + } + } + } + throw new HttpRequestException(buffer.ToString()); + } + var outgoingRequest = new ITypes.OutgoingRequest(fields); outgoingRequest.SetMethod(method); outgoingRequest.SetScheme(scheme); outgoingRequest.SetAuthority(authority); @@ -242,7 +270,7 @@ CancellationToken cancellationToken await Task.WhenAll( new Task[] { - SendRequestAsync(outgoingRequest, cancellationToken), + SendRequestAsync(uri, outgoingRequest, cancellationToken), sendContent() } ) @@ -279,6 +307,7 @@ await Task.WhenAll( } private static async Task SendRequestAsync( + Uri? uri, ITypes.OutgoingRequest request, CancellationToken cancellationToken ) @@ -291,7 +320,7 @@ CancellationToken cancellationToken catch (WasiHttpWorld.WitException e) { var message = ErrorCodeToString((ITypes.ErrorCode)e.Value); - throw new Exception($"Request Error: {message}"); + throw new Exception($"Request Error for {uri}: {message}"); } while (true) @@ -310,7 +339,7 @@ CancellationToken cancellationToken else { var message = ErrorCodeToString(result.AsErr); - throw new Exception($"Request Error: {message}"); + throw new Exception($"Request Error for {uri}: {message}"); } } else @@ -321,6 +350,21 @@ await RegisterWasiPollable(future.Subscribe(), cancellationToken) } } + private static string HeaderErrorToString(ITypes.HeaderError error) + { + switch (error.Tag) + { + case ITypes.HeaderError.INVALID_SYNTAX: + return "INVALID_SYNTAX"; + case ITypes.HeaderError.FORBIDDEN: + return "FORBIDDEN"; + case ITypes.HeaderError.IMMUTABLE: + return "IMMUTABLE"; + default: + return $"{error.Tag}"; + } + } + private static string ErrorCodeToString(ITypes.ErrorCode code) { // TODO: include payload data in result where applicable From 9d9ed59f2ec05b9430bdcb314e95adea0dc5ab16 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 28 Aug 2024 09:31:45 -0600 Subject: [PATCH 08/20] remove obsolete TODO-LLVM comments Signed-off-by: Joel Dice --- src/libraries/System.Net.Http/src/System.Net.Http.csproj | 2 -- .../System.Net.Http/src/System/Net/Http/HttpClientHandler.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj index 734fb12bb01..b9598d8b3cf 100644 --- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj @@ -24,7 +24,6 @@ true - $(DefineConstants);NATIVE_AOT @@ -461,7 +460,6 @@ Link="Common\System\Net\Http\HttpHandlerDefaults.cs" /> - diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs index 579798edb33..30ac6848581 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs @@ -29,7 +29,6 @@ public partial class HttpClientHandler : HttpMessageHandler { private readonly HttpHandlerType _underlyingHandler; -// TODO-LLVM: This is not upstreamable and !NATIVE_AOT should be reverted when https://github.com/dotnet/runtimelab/pull/2614 is merged #if TARGET_BROWSER || TARGET_WASI private IMeterFactory? _meterFactory; private HttpMessageHandler? _firstHandler; // DiagnosticsHandler or MetricsHandler, depending on global configuration. @@ -100,7 +99,6 @@ protected override void Dispose(bool disposing) [CLSCompliant(false)] public IMeterFactory? MeterFactory { -// TODO-LLVM: This is not upstreamable and !NATIVE_AOT should be reverted when https://github.com/dotnet/runtimelab/pull/2614 is merged #if TARGET_BROWSER || TARGET_WASI get => _meterFactory; set From 4879365e8e3bb22fb37fa76c4f1e317144ce9ec8 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 28 Aug 2024 18:30:45 -0600 Subject: [PATCH 09/20] update wit-bindgen and regenerate bindings See https://github.com/bytecodealliance/wit-bindgen/pull/1040 Signed-off-by: Joel Dice --- .../Net/Http/WasiHttpHandler/WasiHttp.cs | 2 +- ...asi.clocks.v0_2_1.MonotonicClockInterop.cs | 2 +- ...rld.wit.imports.wasi.http.v0_2_1.ITypes.cs | 24 +++++++++---------- ...wasi.http.v0_2_1.OutgoingHandlerInterop.cs | 2 +- ...t.imports.wasi.http.v0_2_1.TypesInterop.cs | 2 +- ...wit.imports.wasi.io.v0_2_1.ErrorInterop.cs | 2 +- ...World.wit.imports.wasi.io.v0_2_1.IError.cs | 4 ++-- ...pWorld.wit.imports.wasi.io.v0_2_1.IPoll.cs | 4 ++-- ...rld.wit.imports.wasi.io.v0_2_1.IStreams.cs | 6 ++--- ....wit.imports.wasi.io.v0_2_1.PollInterop.cs | 2 +- ...t.imports.wasi.io.v0_2_1.StreamsInterop.cs | 2 +- .../generate-wasi-http-bindings.sh | 5 ++-- .../src/System/Threading/Wasi/WasiPoll.cs | 2 +- ...asi.clocks.v0_2_1.MonotonicClockInterop.cs | 2 +- ...lWorld.wit.imports.wasi.io.v0_2_1.IPoll.cs | 4 ++-- ....wit.imports.wasi.io.v0_2_1.PollInterop.cs | 2 +- .../Wasi/generate-wasi-poll-bindings.sh | 3 ++- .../SmokeTests/SharedLibrary/Library.cs | 2 +- .../generate-shared-library-bindings.sh | 5 ++-- 19 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttp.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttp.cs index 67ec0a4b7c8..db7224a7034 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttp.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttp.cs @@ -1,4 +1,4 @@ -// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.30.0. DO NOT EDIT! // #nullable enable using System; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.clocks.v0_2_1.MonotonicClockInterop.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.clocks.v0_2_1.MonotonicClockInterop.cs index 1de0ee2b247..4eef64ee389 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.clocks.v0_2_1.MonotonicClockInterop.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.clocks.v0_2_1.MonotonicClockInterop.cs @@ -1,4 +1,4 @@ -// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.30.0. DO NOT EDIT! // #nullable enable diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.http.v0_2_1.ITypes.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.http.v0_2_1.ITypes.cs index ed00640bb85..da4478ab226 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.http.v0_2_1.ITypes.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.http.v0_2_1.ITypes.cs @@ -1,4 +1,4 @@ -// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.30.0. DO NOT EDIT! // #nullable enable @@ -612,7 +612,7 @@ public void Dispose() { private static extern void wasmImportResourceDrop(int p0); protected virtual void Dispose(bool disposing) { - if (Handle != 0) { + if (disposing && Handle != 0) { wasmImportResourceDrop(Handle); Handle = 0; } @@ -1078,7 +1078,7 @@ public void Dispose() { private static extern void wasmImportResourceDrop(int p0); protected virtual void Dispose(bool disposing) { - if (Handle != 0) { + if (disposing && Handle != 0) { wasmImportResourceDrop(Handle); Handle = 0; } @@ -1392,7 +1392,7 @@ public void Dispose() { private static extern void wasmImportResourceDrop(int p0); protected virtual void Dispose(bool disposing) { - if (Handle != 0) { + if (disposing && Handle != 0) { wasmImportResourceDrop(Handle); Handle = 0; } @@ -2076,7 +2076,7 @@ public void Dispose() { private static extern void wasmImportResourceDrop(int p0); protected virtual void Dispose(bool disposing) { - if (Handle != 0) { + if (disposing && Handle != 0) { wasmImportResourceDrop(Handle); Handle = 0; } @@ -2408,7 +2408,7 @@ public void Dispose() { private static extern void wasmImportResourceDrop(int p0); protected virtual void Dispose(bool disposing) { - if (Handle != 0) { + if (disposing && Handle != 0) { wasmImportResourceDrop(Handle); Handle = 0; } @@ -3416,7 +3416,7 @@ public void Dispose() { private static extern void wasmImportResourceDrop(int p0); protected virtual void Dispose(bool disposing) { - if (Handle != 0) { + if (disposing && Handle != 0) { wasmImportResourceDrop(Handle); Handle = 0; } @@ -3535,7 +3535,7 @@ public void Dispose() { private static extern void wasmImportResourceDrop(int p0); protected virtual void Dispose(bool disposing) { - if (Handle != 0) { + if (disposing && Handle != 0) { wasmImportResourceDrop(Handle); Handle = 0; } @@ -3636,7 +3636,7 @@ public void Dispose() { private static extern void wasmImportResourceDrop(int p0); protected virtual void Dispose(bool disposing) { - if (Handle != 0) { + if (disposing && Handle != 0) { wasmImportResourceDrop(Handle); Handle = 0; } @@ -4360,7 +4360,7 @@ public void Dispose() { private static extern void wasmImportResourceDrop(int p0); protected virtual void Dispose(bool disposing) { - if (Handle != 0) { + if (disposing && Handle != 0) { wasmImportResourceDrop(Handle); Handle = 0; } @@ -4542,7 +4542,7 @@ public void Dispose() { private static extern void wasmImportResourceDrop(int p0); protected virtual void Dispose(bool disposing) { - if (Handle != 0) { + if (disposing && Handle != 0) { wasmImportResourceDrop(Handle); Handle = 0; } @@ -5270,7 +5270,7 @@ public void Dispose() { private static extern void wasmImportResourceDrop(int p0); protected virtual void Dispose(bool disposing) { - if (Handle != 0) { + if (disposing && Handle != 0) { wasmImportResourceDrop(Handle); Handle = 0; } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.http.v0_2_1.OutgoingHandlerInterop.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.http.v0_2_1.OutgoingHandlerInterop.cs index 10aba859a4b..77435d5b2c7 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.http.v0_2_1.OutgoingHandlerInterop.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.http.v0_2_1.OutgoingHandlerInterop.cs @@ -1,4 +1,4 @@ -// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.30.0. DO NOT EDIT! // #nullable enable diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.http.v0_2_1.TypesInterop.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.http.v0_2_1.TypesInterop.cs index 121f6cfdd50..8a5802302e1 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.http.v0_2_1.TypesInterop.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.http.v0_2_1.TypesInterop.cs @@ -1,4 +1,4 @@ -// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.30.0. DO NOT EDIT! // #nullable enable diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.ErrorInterop.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.ErrorInterop.cs index 2c3246555fc..a395aa89033 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.ErrorInterop.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.ErrorInterop.cs @@ -1,4 +1,4 @@ -// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.30.0. DO NOT EDIT! // #nullable enable diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IError.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IError.cs index 3621e1c1fd3..848adb0d65e 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IError.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IError.cs @@ -1,4 +1,4 @@ -// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.30.0. DO NOT EDIT! // #nullable enable @@ -53,7 +53,7 @@ public void Dispose() { private static extern void wasmImportResourceDrop(int p0); protected virtual void Dispose(bool disposing) { - if (Handle != 0) { + if (disposing && Handle != 0) { wasmImportResourceDrop(Handle); Handle = 0; } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IPoll.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IPoll.cs index 0c4559fe841..225c9915334 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IPoll.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IPoll.cs @@ -1,4 +1,4 @@ -// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.30.0. DO NOT EDIT! // #nullable enable @@ -37,7 +37,7 @@ public void Dispose() { private static extern void wasmImportResourceDrop(int p0); protected virtual void Dispose(bool disposing) { - if (Handle != 0) { + if (disposing && Handle != 0) { wasmImportResourceDrop(Handle); Handle = 0; } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IStreams.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IStreams.cs index ab775cbfeb0..05899108409 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IStreams.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IStreams.cs @@ -1,4 +1,4 @@ -// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.30.0. DO NOT EDIT! // #nullable enable @@ -80,7 +80,7 @@ public void Dispose() { private static extern void wasmImportResourceDrop(int p0); protected virtual void Dispose(bool disposing) { - if (Handle != 0) { + if (disposing && Handle != 0) { wasmImportResourceDrop(Handle); Handle = 0; } @@ -396,7 +396,7 @@ public void Dispose() { private static extern void wasmImportResourceDrop(int p0); protected virtual void Dispose(bool disposing) { - if (Handle != 0) { + if (disposing && Handle != 0) { wasmImportResourceDrop(Handle); Handle = 0; } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.PollInterop.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.PollInterop.cs index 4c48ffe69e2..01157399463 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.PollInterop.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.PollInterop.cs @@ -1,4 +1,4 @@ -// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.30.0. DO NOT EDIT! // #nullable enable diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.StreamsInterop.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.StreamsInterop.cs index 7d479e6b160..1e2ade74bb3 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.StreamsInterop.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.StreamsInterop.cs @@ -1,4 +1,4 @@ -// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.30.0. DO NOT EDIT! // #nullable enable diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/generate-wasi-http-bindings.sh b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/generate-wasi-http-bindings.sh index f4bc2b72ef7..56a192b2508 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/generate-wasi-http-bindings.sh +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/generate-wasi-http-bindings.sh @@ -11,7 +11,8 @@ set -ex # [cargo](https://rustup.rs/) # [curl](https://curl.se/download.html) -cargo install --locked --no-default-features --features csharp --version 0.29.0 wit-bindgen-cli +# TODO: switch to crates.io release once https://github.com/bytecodealliance/wit-bindgen/pull/1040 is merged and released +cargo install --locked --no-default-features --features csharp --git https://github.com/dicej/wit-bindgen --rev 34afca03 wit-bindgen-cli curl -OL https://github.com/WebAssembly/wasi-http/archive/refs/tags/v0.2.1.tar.gz tar xzf v0.2.1.tar.gz cat >wasi-http-0.2.1/wit/world.wit < #nullable enable using System; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiPollWorld.wit.imports.wasi.clocks.v0_2_1.MonotonicClockInterop.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiPollWorld.wit.imports.wasi.clocks.v0_2_1.MonotonicClockInterop.cs index 1a0ef026540..b3c646e7e4b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiPollWorld.wit.imports.wasi.clocks.v0_2_1.MonotonicClockInterop.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiPollWorld.wit.imports.wasi.clocks.v0_2_1.MonotonicClockInterop.cs @@ -1,4 +1,4 @@ -// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.30.0. DO NOT EDIT! // #nullable enable diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiPollWorld.wit.imports.wasi.io.v0_2_1.IPoll.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiPollWorld.wit.imports.wasi.io.v0_2_1.IPoll.cs index 8ad5cabf64b..93d90d4c826 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiPollWorld.wit.imports.wasi.io.v0_2_1.IPoll.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiPollWorld.wit.imports.wasi.io.v0_2_1.IPoll.cs @@ -1,4 +1,4 @@ -// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.30.0. DO NOT EDIT! // #nullable enable @@ -37,7 +37,7 @@ public void Dispose() { private static extern void wasmImportResourceDrop(int p0); protected virtual void Dispose(bool disposing) { - if (Handle != 0) { + if (disposing && Handle != 0) { wasmImportResourceDrop(Handle); Handle = 0; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiPollWorld.wit.imports.wasi.io.v0_2_1.PollInterop.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiPollWorld.wit.imports.wasi.io.v0_2_1.PollInterop.cs index dc01e875749..c403ef10d1f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiPollWorld.wit.imports.wasi.io.v0_2_1.PollInterop.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiPollWorld.wit.imports.wasi.io.v0_2_1.PollInterop.cs @@ -1,4 +1,4 @@ -// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.30.0. DO NOT EDIT! // #nullable enable diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/generate-wasi-poll-bindings.sh b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/generate-wasi-poll-bindings.sh index fd20df8e9df..3a9a1d3ef70 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/generate-wasi-poll-bindings.sh +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/generate-wasi-poll-bindings.sh @@ -11,7 +11,8 @@ set -ex # [cargo](https://rustup.rs/) # [curl](https://curl.se/download.html) -cargo install --locked --no-default-features --features csharp --version 0.29.0 wit-bindgen-cli +# TODO: switch to crates.io release once https://github.com/bytecodealliance/wit-bindgen/pull/1040 is merged and released +cargo install --locked --no-default-features --features csharp --git https://github.com/dicej/wit-bindgen --rev 34afca03 wit-bindgen-cli curl -OL https://github.com/WebAssembly/wasi-http/archive/refs/tags/v0.2.1.tar.gz tar xzf v0.2.1.tar.gz cat >wasi-http-0.2.1/wit/world.wit < #nullable enable using System; diff --git a/src/tests/nativeaot/SmokeTests/SharedLibrary/generate-shared-library-bindings.sh b/src/tests/nativeaot/SmokeTests/SharedLibrary/generate-shared-library-bindings.sh index 4a051a50554..84d41b7ed09 100644 --- a/src/tests/nativeaot/SmokeTests/SharedLibrary/generate-shared-library-bindings.sh +++ b/src/tests/nativeaot/SmokeTests/SharedLibrary/generate-shared-library-bindings.sh @@ -11,6 +11,7 @@ set -ex # [cargo](https://rustup.rs/) # [curl](https://curl.se/download.html) -cargo install --locked --no-default-features --features csharp --version 0.29.0 wit-bindgen-cli +# TODO: switch to crates.io release once https://github.com/bytecodealliance/wit-bindgen/pull/1040 is merged and released +cargo install --locked --no-default-features --features csharp --git https://github.com/dicej/wit-bindgen --rev 34afca03 wit-bindgen-cli wit-bindgen c-sharp -w library -r native-aot wit -rm LibraryWorld_wasm_import_linkage_attribute.cs LibraryWorld_cabi_realloc.c LibraryWorld_component_type.o +rm LibraryWorld_wasm_import_linkage_attribute.cs From 137614ef9d7d3ebb97ba4931561dcd941608b10a Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 28 Aug 2024 21:34:46 +0200 Subject: [PATCH 10/20] to match https://github.com/dotnet/runtime/pull/107096 --- .../src/System.Net.Http.csproj | 4 + .../Http/WasiHttpHandler/WasiHttpHandler.cs | 848 ++++-------------- .../Http/WasiHttpHandler/WasiHttpInterop.cs | 290 ++++++ .../Http/WasiHttpHandler/WasiInputStream.cs | 197 ++++ .../Http/WasiHttpHandler/WasiOutputStream.cs | 162 ++++ .../generate-wasi-http-bindings.ps1 | 21 + .../generate-wasi-http-bindings.sh | 1 - .../System/Net/Http/WasiHttpHandler/world.wit | 3 + .../System/Threading/Wasi/WasiEventLoop.cs | 145 +-- .../nativeaot/SmokeTests/http-p2/Program.cs | 84 ++ .../http-p2/Wasip2.Http.Console.Sample.csproj | 16 + 11 files changed, 1022 insertions(+), 749 deletions(-) create mode 100644 src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpInterop.cs create mode 100644 src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiInputStream.cs create mode 100644 src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiOutputStream.cs create mode 100644 src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/generate-wasi-http-bindings.ps1 create mode 100644 src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/world.wit create mode 100644 src/tests/nativeaot/SmokeTests/http-p2/Program.cs create mode 100644 src/tests/nativeaot/SmokeTests/http-p2/Wasip2.Http.Console.Sample.csproj diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj index b9598d8b3cf..f5d95181c2b 100644 --- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj @@ -463,6 +463,9 @@ + + + @@ -530,4 +533,5 @@ + diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpHandler.cs index cd18da5526c..f5d416cbb09 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpHandler.cs @@ -14,13 +14,170 @@ using WasiHttpWorld; using WasiHttpWorld.wit.imports.wasi.http.v0_2_1; using WasiHttpWorld.wit.imports.wasi.io.v0_2_1; +using static WasiHttpWorld.wit.imports.wasi.http.v0_2_1.ITypes; namespace System.Net.Http { + // on top of https://github.com/WebAssembly/wasi-http/blob/main/wit/types.wit + internal sealed class WasiRequestWrapper : IDisposable + { + private FutureIncomingResponse? future; // owned by this instance + private WasiOutputStream? wasiOutputStream; // owned by this instance + private WasiInputStream? incomingStream; // owned by this instance + + public Task? requestBodyComplete; + public Task? requestComplete; + private bool isDisposed; + + public async Task SendRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (request.RequestUri is null) + { + throw new ArgumentException(); + } + + try + { + var requestHeaders = WasiHttpInterop.ConvertRequestHeaders(request); + var outgoingRequest = new OutgoingRequest(requestHeaders); // passing requestHeaders ownership + outgoingRequest.SetMethod(WasiHttpInterop.ConvertMethod(request.Method)); + outgoingRequest.SetScheme(WasiHttpInterop.ConvertScheme(request.RequestUri)); + outgoingRequest.SetAuthority(WasiHttpInterop.ConvertAuthority(request.RequestUri)); + outgoingRequest.SetPathWithQuery(request.RequestUri.PathAndQuery); + + requestBodyComplete = SendContent(request.Content, outgoingRequest, cancellationToken); + + future = OutgoingHandlerInterop.Handle(outgoingRequest, null); + + requestComplete = SendRequest(cancellationToken); + + using var incomingResponse = await requestComplete.ConfigureAwait(false); + + ObjectDisposedException.ThrowIf(isDisposed, this); + cancellationToken.ThrowIfCancellationRequested(); + + var response = new HttpResponseMessage((HttpStatusCode)incomingResponse.Status()); + WasiHttpInterop.ConvertResponseHeaders(incomingResponse, response); + + + // request body could be still streaming after response headers are received and started streaming response + // we will leave scope of this method + // we need to pass the ownership of the request and this wrapper to the response (via response content stream) + // unless we know that we are not streaming anymore + incomingStream = new WasiInputStream(this, incomingResponse.Consume());// passing self ownership, passing body ownership + response.Content = new StreamContent(incomingStream); // passing incomingStream ownership to SendAsync() caller + + return response; + } + catch (WitException e) + { + Dispose(); + throw new HttpRequestException(WasiHttpInterop.ErrorCodeToString((ErrorCode)e.Value), e); + } + catch (Exception) + { + Dispose(); + throw; + } + } + + private async Task SendRequest(CancellationToken cancellationToken) + { + try + { + while (true) + { + var response = (Result, None>?)future!.Get(); + if (response.HasValue) + { + var result = response.Value.AsOk; + + if (result.IsOk) + { + return result.AsOk; + } + else + { + throw new HttpRequestException(WasiHttpInterop.ErrorCodeToString(result.AsErr)); + } + } + else + { + await WasiHttpInterop.RegisterWasiPollable(future.Subscribe(), cancellationToken).ConfigureAwait(false); + } + } + } + catch (OperationCanceledException oce) + { + if (cancellationToken.IsCancellationRequested) + { + Http.CancellationHelper.ThrowIfCancellationRequested(oce, cancellationToken); + } + throw; + } + } + + public async Task SendContent(HttpContent? content, OutgoingRequest outgoingRequest, CancellationToken cancellationToken) + { + if (content is not null) + { + wasiOutputStream = new WasiOutputStream(outgoingRequest.Body()); // passing body ownership + await content.CopyToAsync(wasiOutputStream, cancellationToken).ConfigureAwait(false); + wasiOutputStream.Close(); + } + } + + ~WasiRequestWrapper() + { + Dispose(); + GC.SuppressFinalize(this); + } + + public void Dispose() + { + if (!isDisposed) + { + isDisposed = true; + wasiOutputStream?.Dispose(); + incomingStream?.Dispose(); + + // TODO why this fails ? + // future?.Dispose(); + } + } + } + internal sealed class WasiHttpHandler : HttpMessageHandler { + public const bool SupportsAutomaticDecompression = false; + public const bool SupportsProxy = false; + public const bool SupportsRedirectConfiguration = false; + + private Dictionary? _properties; + public IDictionary Properties => _properties ??= new Dictionary(); + + protected internal override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var wasiRequest = new WasiRequestWrapper(); + try + { + return await wasiRequest.SendRequestAsync(request, cancellationToken).ConfigureAwait(false); + } + catch + { + // if there was exception or cancellation, we need to dispose the request + // otherwise it will be disposed by the response + wasiRequest.Dispose(); + throw; + } + } + #region PlatformNotSupported #pragma warning disable CA1822 + + internal ClientCertificateOption ClientCertificateOptions; + + public bool UseCookies { get => throw new PlatformNotSupportedException(); @@ -99,17 +256,6 @@ public bool AllowAutoRedirect set => throw new PlatformNotSupportedException(); } #pragma warning restore CA1822 - #endregion - - internal ClientCertificateOption ClientCertificateOptions; - - public const bool SupportsAutomaticDecompression = false; - public const bool SupportsProxy = false; - public const bool SupportsRedirectConfiguration = false; - - private Dictionary? _properties; - public IDictionary Properties => - _properties ??= new Dictionary(); protected internal override HttpResponseMessage Send( HttpRequestMessage request, @@ -119,684 +265,6 @@ CancellationToken cancellationToken throw new PlatformNotSupportedException(); } - protected internal override async Task SendAsync( - HttpRequestMessage request, - CancellationToken cancellationToken - ) - { - if (request.RequestUri is null) - { - throw new ArgumentException(); - } - - var requestMethod = request.Method.ToString(); - var uri = request.RequestUri; - - ITypes.Method method; - switch (requestMethod) - { - case "": - case "GET": - method = ITypes.Method.get(); - break; - case "HEAD": - method = ITypes.Method.head(); - break; - case "POST": - method = ITypes.Method.post(); - break; - case "PUT": - method = ITypes.Method.put(); - break; - case "DELETE": - method = ITypes.Method.delete(); - break; - case "CONNECT": - method = ITypes.Method.connect(); - break; - case "OPTIONS": - method = ITypes.Method.options(); - break; - case "TRACE": - method = ITypes.Method.trace(); - break; - case "PATCH": - method = ITypes.Method.patch(); - break; - default: - method = ITypes.Method.other(requestMethod); - break; - } - - ITypes.Scheme scheme; - switch (uri.Scheme) - { - case "": - case "http": - scheme = ITypes.Scheme.http(); - break; - case "https": - scheme = ITypes.Scheme.https(); - break; - default: - scheme = ITypes.Scheme.other(uri.Scheme); - break; - } - - string authority; - if (uri.Authority.Length == 0) - { - // `wasi:http/outgoing-handler` requires a non-empty authority, - // so we set one here: - if (scheme.Tag == ITypes.Scheme.HTTPS) - { - authority = ":443"; - } - else - { - authority = ":80"; - } - } - else - { - authority = uri.Authority; - } - - var headers = new List<(string, byte[])>(); - foreach (var pair in request.Headers) - { - foreach (var value in pair.Value) - { - headers.Add((pair.Key, Encoding.UTF8.GetBytes(value))); - } - } - if (request.Content is not null) - { - foreach (var pair in request.Content.Headers) - { - foreach (var value in pair.Value) - { - headers.Add((pair.Key, Encoding.UTF8.GetBytes(value))); - } - } - } - - ITypes.Fields fields; - try - { - fields = ITypes.Fields.FromList(headers); - } - catch (WitException e) - { - var error = (ITypes.HeaderError)e.Value; - var buffer = new StringBuilder($"header error: {HeaderErrorToString(error)}\n"); - foreach (var pair in request.Headers) - { - foreach (var value in pair.Value) - { - buffer.Append($"\n{pair.Key}: {value}"); - } - } - if (request.Content is not null) - { - foreach (var pair in request.Content.Headers) - { - foreach (var value in pair.Value) - { - buffer.Append($"\n{pair.Key}: {value}"); - } - } - } - throw new HttpRequestException(buffer.ToString()); - } - var outgoingRequest = new ITypes.OutgoingRequest(fields); - outgoingRequest.SetMethod(method); - outgoingRequest.SetScheme(scheme); - outgoingRequest.SetAuthority(authority); - outgoingRequest.SetPathWithQuery(uri.PathAndQuery); - - var outgoingStream = new OutputStream(outgoingRequest.Body()); - - Func> sendContent = async () => - { - await SendContentAsync(request.Content, outgoingStream).ConfigureAwait(false); - return null; - }; - - // Concurrently send the request and the content stream, allowing - // the server to start sending a response before it's received the - // entire request body. - var incomingResponse = ( - await Task.WhenAll( - new Task[] - { - SendRequestAsync(uri, outgoingRequest, cancellationToken), - sendContent() - } - ) - .ConfigureAwait(false) - )[0]; - - if (incomingResponse is null) - { - // Shouldn't be possible, since `SendRequestAsync` always - // returns a non-null value. - throw new Exception("unreachable code"); - } - - var response = new HttpResponseMessage((HttpStatusCode)incomingResponse.Status()); - var responseHeaders = incomingResponse.Headers().Entries(); - response.Content = new StreamContent(new InputStream(incomingResponse.Consume())); - foreach ((var key, var value) in responseHeaders) - { - var valueString = Encoding.UTF8.GetString(value); - if ( - HeaderDescriptor.TryGet(key, out HeaderDescriptor descriptor) - && (descriptor.HeaderType & HttpHeaderType.Content) != 0 - ) - { - response.Content.Headers.Add(key, valueString); - } - else - { - response.Headers.Add(key, valueString); - } - } - - return response; - } - - private static async Task SendRequestAsync( - Uri? uri, - ITypes.OutgoingRequest request, - CancellationToken cancellationToken - ) - { - ITypes.FutureIncomingResponse future; - try - { - future = OutgoingHandlerInterop.Handle(request, null); - } - catch (WasiHttpWorld.WitException e) - { - var message = ErrorCodeToString((ITypes.ErrorCode)e.Value); - throw new Exception($"Request Error for {uri}: {message}"); - } - - while (true) - { - var response = future.Get(); - if (response is not null) - { - var result = ( - (Result, None>)response - ).AsOk; - - if (result.IsOk) - { - return result.AsOk; - } - else - { - var message = ErrorCodeToString(result.AsErr); - throw new Exception($"Request Error for {uri}: {message}"); - } - } - else - { - await RegisterWasiPollable(future.Subscribe(), cancellationToken) - .ConfigureAwait(false); - } - } - } - - private static string HeaderErrorToString(ITypes.HeaderError error) - { - switch (error.Tag) - { - case ITypes.HeaderError.INVALID_SYNTAX: - return "INVALID_SYNTAX"; - case ITypes.HeaderError.FORBIDDEN: - return "FORBIDDEN"; - case ITypes.HeaderError.IMMUTABLE: - return "IMMUTABLE"; - default: - return $"{error.Tag}"; - } - } - - private static string ErrorCodeToString(ITypes.ErrorCode code) - { - // TODO: include payload data in result where applicable - switch (code.Tag) - { - case ITypes.ErrorCode.DNS_TIMEOUT: - return "DNS_TIMEOUT"; - - case ITypes.ErrorCode.DNS_ERROR: - return "DNS_ERROR"; - - case ITypes.ErrorCode.DESTINATION_NOT_FOUND: - return "DESTINATION_NOT_FOUND"; - - case ITypes.ErrorCode.DESTINATION_UNAVAILABLE: - return "DESTINATION_UNAVAILABLE"; - - case ITypes.ErrorCode.DESTINATION_IP_PROHIBITED: - return "DESTINATION_IP_PROHIBITED"; - - case ITypes.ErrorCode.DESTINATION_IP_UNROUTABLE: - return "DESTINATION_IP_UNROUTABLE"; - - case ITypes.ErrorCode.CONNECTION_REFUSED: - return "CONNECTION_REFUSED"; - - case ITypes.ErrorCode.CONNECTION_TERMINATED: - return "CONNECTION_TERMINATED"; - - case ITypes.ErrorCode.CONNECTION_TIMEOUT: - return "CONNECTION_TIMEOUT"; - - case ITypes.ErrorCode.CONNECTION_READ_TIMEOUT: - return "CONNECTION_READ_TIMEOUT"; - - case ITypes.ErrorCode.CONNECTION_WRITE_TIMEOUT: - return "CONNECTION_WRITE_TIMEOUT"; - - case ITypes.ErrorCode.CONNECTION_LIMIT_REACHED: - return "CONNECTION_LIMIT_REACHED"; - - case ITypes.ErrorCode.TLS_PROTOCOL_ERROR: - return "TLS_PROTOCOL_ERROR"; - - case ITypes.ErrorCode.TLS_CERTIFICATE_ERROR: - return "TLS_CERTIFICATE_ERROR"; - - case ITypes.ErrorCode.TLS_ALERT_RECEIVED: - return "TLS_ALERT_RECEIVED"; - - case ITypes.ErrorCode.HTTP_REQUEST_DENIED: - return "HTTP_REQUEST_DENIED"; - - case ITypes.ErrorCode.HTTP_REQUEST_LENGTH_REQUIRED: - return "HTTP_REQUEST_LENGTH_REQUIRED"; - - case ITypes.ErrorCode.HTTP_REQUEST_BODY_SIZE: - return "HTTP_REQUEST_BODY_SIZE"; - - case ITypes.ErrorCode.HTTP_REQUEST_METHOD_INVALID: - return "HTTP_REQUEST_METHOD_INVALID"; - - case ITypes.ErrorCode.HTTP_REQUEST_URI_INVALID: - return "HTTP_REQUEST_URI_INVALID"; - - case ITypes.ErrorCode.HTTP_REQUEST_URI_TOO_LONG: - return "HTTP_REQUEST_URI_TOO_LONG"; - - case ITypes.ErrorCode.HTTP_REQUEST_HEADER_SECTION_SIZE: - return "HTTP_REQUEST_HEADER_SECTION_SIZE"; - - case ITypes.ErrorCode.HTTP_REQUEST_HEADER_SIZE: - return "HTTP_REQUEST_HEADER_SIZE"; - - case ITypes.ErrorCode.HTTP_REQUEST_TRAILER_SECTION_SIZE: - return "HTTP_REQUEST_TRAILER_SECTION_SIZE"; - - case ITypes.ErrorCode.HTTP_REQUEST_TRAILER_SIZE: - return "HTTP_REQUEST_TRAILER_SIZE"; - - case ITypes.ErrorCode.HTTP_RESPONSE_INCOMPLETE: - return "HTTP_RESPONSE_INCOMPLETE"; - - case ITypes.ErrorCode.HTTP_RESPONSE_HEADER_SECTION_SIZE: - return "HTTP_RESPONSE_HEADER_SECTION_SIZE"; - - case ITypes.ErrorCode.HTTP_RESPONSE_HEADER_SIZE: - return "HTTP_RESPONSE_HEADER_SIZE"; - - case ITypes.ErrorCode.HTTP_RESPONSE_BODY_SIZE: - return "HTTP_RESPONSE_BODY_SIZE"; - - case ITypes.ErrorCode.HTTP_RESPONSE_TRAILER_SECTION_SIZE: - return "HTTP_RESPONSE_TRAILER_SECTION_SIZE"; - - case ITypes.ErrorCode.HTTP_RESPONSE_TRAILER_SIZE: - return "HTTP_RESPONSE_TRAILER_SIZE"; - - case ITypes.ErrorCode.HTTP_RESPONSE_TRANSFER_CODING: - return "HTTP_RESPONSE_TRANSFER_CODING"; - - case ITypes.ErrorCode.HTTP_RESPONSE_CONTENT_CODING: - return "HTTP_RESPONSE_CONTENT_CODING"; - - case ITypes.ErrorCode.HTTP_RESPONSE_TIMEOUT: - return "HTTP_RESPONSE_TIMEOUT"; - - case ITypes.ErrorCode.HTTP_UPGRADE_FAILED: - return "HTTP_UPGRADE_FAILED"; - - case ITypes.ErrorCode.HTTP_PROTOCOL_ERROR: - return "HTTP_PROTOCOL_ERROR"; - - case ITypes.ErrorCode.LOOP_DETECTED: - return "LOOP_DETECTED"; - - case ITypes.ErrorCode.CONFIGURATION_ERROR: - return "CONFIGURATION_ERROR"; - - case ITypes.ErrorCode.INTERNAL_ERROR: - return "INTERNAL_ERROR"; - - default: - return $"{code.Tag}"; - } - } - - private static async Task SendContentAsync(HttpContent? content, Stream stream) - { - try - { - if (content is not null) - { - await content.CopyToAsync(stream).ConfigureAwait(false); - } - } - finally - { - stream.Dispose(); - } - } - - private static Task RegisterWasiPollable( - IPoll.Pollable pollable, - CancellationToken cancellationToken - ) - { - var handle = pollable.Handle; - - // this will effectively neutralize Dispose() of the Pollable() - // because in the CoreLib we create another instance, which will dispose it - pollable.Handle = 0; - GC.SuppressFinalize(pollable); - - return CallRegisterWasiPollableHandle((Thread)null!, handle, cancellationToken); - } - - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "RegisterWasiPollableHandle")] - private static extern Task CallRegisterWasiPollableHandle( - Thread t, - int handle, - CancellationToken cancellationToken - ); - - private sealed class InputStream : Stream - { - private ITypes.IncomingBody body; - private IStreams.InputStream stream; - private int offset; - private byte[]? buffer; - private bool closed; - - public InputStream(ITypes.IncomingBody body) - { - this.body = body; - this.stream = body.Stream(); - } - - ~InputStream() - { - Dispose(false); - } - - public override bool CanRead => true; - public override bool CanWrite => false; - public override bool CanSeek => false; - public override long Length => throw new NotImplementedException(); - public override long Position - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - protected override void Dispose(bool disposing) - { - stream.Dispose(); - ITypes.IncomingBody.Finish(body); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override void Flush() - { - // ignore - } - - public override void SetLength(long length) - { - throw new NotImplementedException(); - } - - public override int Read(byte[] buffer, int offset, int length) - { - throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int length) - { - throw new NotImplementedException(); - } - - public override async Task ReadAsync( - byte[] bytes, - int offset, - int length, - CancellationToken cancellationToken - ) - { - // TODO: handle `cancellationToken` - while (true) - { - if (closed) - { - return 0; - } - else if (this.buffer == null) - { - try - { - // TODO: should we add a special case to the bindings generator - // to allow passing a buffer to IStreams.InputStream.Read and - // avoid the extra copy? - var result = stream.Read(16 * 1024); - var buffer = result; - if (buffer.Length == 0) - { - await RegisterWasiPollable(stream.Subscribe(), cancellationToken) - .ConfigureAwait(false); - } - else - { - this.buffer = buffer; - this.offset = 0; - } - } - catch (WitException e) - { - if (((IStreams.StreamError)e.Value).Tag == IStreams.StreamError.CLOSED) - { - closed = true; - return 0; - } - else - { - throw e; - } - } - } - else - { - var min = Math.Min(this.buffer.Length - this.offset, length); - Array.Copy(this.buffer, this.offset, bytes, offset, min); - if (min < buffer.Length - this.offset) - { - this.offset += min; - } - else - { - this.buffer = null; - } - return min; - } - } - } - - public override async ValueTask ReadAsync( - Memory buffer, - CancellationToken cancellationToken = default - ) - { - // TODO: avoid copy when possible and use ArrayPool when not - var dst = new byte[buffer.Length]; - // We disable "CA1835: Prefer the memory-based overloads of - // ReadAsync/WriteAsync methods in stream-based classes" for - // now, since `ReadyAsync(byte[], int, int, CancellationToken)` - // is where the implementation currently resides, but we should - // revisit this if/when `wit-bindgen` learns to generate - // memory-based bindings. -#pragma warning disable CA1835 - var result = await ReadAsync(dst, 0, buffer.Length, cancellationToken) - .ConfigureAwait(false); -#pragma warning restore CA1835 - new ReadOnlySpan(dst, 0, result).CopyTo(buffer.Span); - return result; - } - } - - private sealed class OutputStream : Stream - { - private ITypes.OutgoingBody body; - private IStreams.OutputStream stream; - - public OutputStream(ITypes.OutgoingBody body) - { - this.body = body; - this.stream = body.Write(); - } - - ~OutputStream() - { - Dispose(false); - } - - public override bool CanRead => false; - public override bool CanWrite => true; - public override bool CanSeek => false; - public override long Length => throw new NotImplementedException(); - public override long Position - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - protected override void Dispose(bool disposing) - { - stream.Dispose(); - ITypes.OutgoingBody.Finish(body, null); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override void Flush() - { - // ignore - // - // Note that flushing a `wasi:io/streams/output-stream` is an - // asynchronous operation, so it's not clear how we would - // implement it here instead of taking care of it as part of - // `WriteAsync`. - } - - public override void SetLength(long length) - { - throw new NotImplementedException(); - } - - public override int Read(byte[] buffer, int offset, int length) - { - throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int length) - { - throw new NotImplementedException(); - } - - public override async Task WriteAsync( - byte[] bytes, - int offset, - int length, - CancellationToken cancellationToken - ) - { - var limit = offset + length; - var flushing = false; - while (true) - { - var count = (int)stream.CheckWrite(); - if (count == 0) - { - await RegisterWasiPollable(stream.Subscribe(), cancellationToken) - .ConfigureAwait(false); - } - else if (offset == limit) - { - if (flushing) - { - return; - } - else - { - stream.Flush(); - flushing = true; - } - } - else - { - var min = Math.Min(count, limit - offset); - if (offset == 0 && min == bytes.Length) - { - stream.Write(bytes); - } - else - { - // TODO: is there a more efficient option than copying here? - // Do we need to change the binding generator to accept - // e.g. `Span`s? - var copy = new byte[min]; - Array.Copy(bytes, offset, copy, 0, min); - stream.Write(copy); - } - offset += min; - } - } - } - - public override ValueTask WriteAsync( - ReadOnlyMemory buffer, - CancellationToken cancellationToken = default - ) - { - // TODO: avoid copy when possible and use ArrayPool when not - var copy = new byte[buffer.Length]; - buffer.Span.CopyTo(copy); - return new ValueTask(WriteAsync(copy, 0, buffer.Length, cancellationToken)); - } - } + #endregion } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpInterop.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpInterop.cs new file mode 100644 index 00000000000..b53ebcd999f --- /dev/null +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpInterop.cs @@ -0,0 +1,290 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Net.Http.Headers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices.Marshalling; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using WasiHttpWorld; +using WasiHttpWorld.wit.imports.wasi.http.v0_2_1; +using WasiHttpWorld.wit.imports.wasi.io.v0_2_1; +using static WasiHttpWorld.wit.imports.wasi.http.v0_2_1.ITypes; +using static WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IStreams; + +namespace System.Net.Http +{ + internal static class WasiHttpInterop + { + public static Task RegisterWasiPollable(IPoll.Pollable pollable, CancellationToken cancellationToken) + { + var handle = pollable.Handle; + + // this will effectively neutralize Dispose() of the Pollable() + // because in the CoreLib we create another instance, which will dispose it + pollable.Handle = 0; + GC.SuppressFinalize(pollable); + + return CallRegisterWasiPollableHandle((Thread)null!, handle, cancellationToken); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "RegisterWasiPollableHandle")] + static extern Task CallRegisterWasiPollableHandle(Thread t, int handle, CancellationToken cancellationToken); + } + + public static Method ConvertMethod(HttpMethod requestMethod) + { + Method method; + switch (requestMethod.Method) + { + case "": + case "GET": + method = Method.get(); + break; + case "HEAD": + method = Method.head(); + break; + case "POST": + method = Method.post(); + break; + case "PUT": + method = Method.put(); + break; + case "DELETE": + method = Method.delete(); + break; + case "CONNECT": + method = Method.connect(); + break; + case "OPTIONS": + method = Method.options(); + break; + case "TRACE": + method = Method.trace(); + break; + case "PATCH": + method = Method.patch(); + break; + default: + method = Method.other(requestMethod.Method); + break; + } + return method; + } + + public static Scheme ConvertScheme(Uri uri) + { + Scheme scheme; + switch (uri.Scheme) + { + case "": + case "http": + scheme = Scheme.http(); + break; + case "https": + scheme = Scheme.https(); + break; + default: + scheme = Scheme.other(uri.Scheme); + break; + } + return scheme; + } + + public static string ConvertAuthority(Uri uri) + { + // `wasi:http/outgoing-handler` requires a non-empty authority, + // so we set one here: + if (string.IsNullOrEmpty(uri.Authority)) + { + if (uri.Scheme == "https") + { + return ":443"; + } + else + { + return ":80"; + } + } + else + { + return uri.Authority; + } + } + + public static Fields ConvertRequestHeaders(HttpRequestMessage request) + { + var headers = new List<(string, byte[])>(); + foreach (var pair in request.Headers) + { + foreach (var value in pair.Value) + { + headers.Add((pair.Key, Encoding.UTF8.GetBytes(value))); + } + } + if (request.Content is not null) + { + foreach (var pair in request.Content.Headers) + { + foreach (var value in pair.Value) + { + headers.Add((pair.Key, Encoding.UTF8.GetBytes(value))); + } + } + } + return Fields.FromList(headers); + } + + public static void ConvertResponseHeaders(IncomingResponse incomingResponse, HttpResponseMessage response) + { + using var headers = incomingResponse.Headers(); + foreach ((var key, var value) in headers.Entries()) + { + var valueString = Encoding.UTF8.GetString(value); + if (IsContentHeader(key)) + { + response.Content.Headers.Add(key, valueString); + } + else + { + response.Headers.Add(key, valueString); + } + } + } + + private static bool IsContentHeader(string headerName) + { + return HeaderDescriptor.TryGet(headerName, out HeaderDescriptor descriptor) && (descriptor.HeaderType & HttpHeaderType.Content) != 0; + } + + public static string ErrorCodeToString(ErrorCode code) + { + // TODO: include payload data in result where applicable + switch (code.Tag) + { + case ErrorCode.DNS_TIMEOUT: + return "DNS_TIMEOUT"; + + case ErrorCode.DNS_ERROR: + return "DNS_ERROR"; + + case ErrorCode.DESTINATION_NOT_FOUND: + return "DESTINATION_NOT_FOUND"; + + case ErrorCode.DESTINATION_UNAVAILABLE: + return "DESTINATION_UNAVAILABLE"; + + case ErrorCode.DESTINATION_IP_PROHIBITED: + return "DESTINATION_IP_PROHIBITED"; + + case ErrorCode.DESTINATION_IP_UNROUTABLE: + return "DESTINATION_IP_UNROUTABLE"; + + case ErrorCode.CONNECTION_REFUSED: + return "CONNECTION_REFUSED"; + + case ErrorCode.CONNECTION_TERMINATED: + return "CONNECTION_TERMINATED"; + + case ErrorCode.CONNECTION_TIMEOUT: + return "CONNECTION_TIMEOUT"; + + case ErrorCode.CONNECTION_READ_TIMEOUT: + return "CONNECTION_READ_TIMEOUT"; + + case ErrorCode.CONNECTION_WRITE_TIMEOUT: + return "CONNECTION_WRITE_TIMEOUT"; + + case ErrorCode.CONNECTION_LIMIT_REACHED: + return "CONNECTION_LIMIT_REACHED"; + + case ErrorCode.TLS_PROTOCOL_ERROR: + return "TLS_PROTOCOL_ERROR"; + + case ErrorCode.TLS_CERTIFICATE_ERROR: + return "TLS_CERTIFICATE_ERROR"; + + case ErrorCode.TLS_ALERT_RECEIVED: + return "TLS_ALERT_RECEIVED"; + + case ErrorCode.HTTP_REQUEST_DENIED: + return "HTTP_REQUEST_DENIED"; + + case ErrorCode.HTTP_REQUEST_LENGTH_REQUIRED: + return "HTTP_REQUEST_LENGTH_REQUIRED"; + + case ErrorCode.HTTP_REQUEST_BODY_SIZE: + return "HTTP_REQUEST_BODY_SIZE"; + + case ErrorCode.HTTP_REQUEST_METHOD_INVALID: + return "HTTP_REQUEST_METHOD_INVALID"; + + case ErrorCode.HTTP_REQUEST_URI_INVALID: + return "HTTP_REQUEST_URI_INVALID"; + + case ErrorCode.HTTP_REQUEST_URI_TOO_LONG: + return "HTTP_REQUEST_URI_TOO_LONG"; + + case ErrorCode.HTTP_REQUEST_HEADER_SECTION_SIZE: + return "HTTP_REQUEST_HEADER_SECTION_SIZE"; + + case ErrorCode.HTTP_REQUEST_HEADER_SIZE: + return "HTTP_REQUEST_HEADER_SIZE"; + + case ErrorCode.HTTP_REQUEST_TRAILER_SECTION_SIZE: + return "HTTP_REQUEST_TRAILER_SECTION_SIZE"; + + case ErrorCode.HTTP_REQUEST_TRAILER_SIZE: + return "HTTP_REQUEST_TRAILER_SIZE"; + + case ErrorCode.HTTP_RESPONSE_INCOMPLETE: + return "HTTP_RESPONSE_INCOMPLETE"; + + case ErrorCode.HTTP_RESPONSE_HEADER_SECTION_SIZE: + return "HTTP_RESPONSE_HEADER_SECTION_SIZE"; + + case ErrorCode.HTTP_RESPONSE_HEADER_SIZE: + return "HTTP_RESPONSE_HEADER_SIZE"; + + case ErrorCode.HTTP_RESPONSE_BODY_SIZE: + return "HTTP_RESPONSE_BODY_SIZE"; + + case ErrorCode.HTTP_RESPONSE_TRAILER_SECTION_SIZE: + return "HTTP_RESPONSE_TRAILER_SECTION_SIZE"; + + case ErrorCode.HTTP_RESPONSE_TRAILER_SIZE: + return "HTTP_RESPONSE_TRAILER_SIZE"; + + case ErrorCode.HTTP_RESPONSE_TRANSFER_CODING: + return "HTTP_RESPONSE_TRANSFER_CODING"; + + case ErrorCode.HTTP_RESPONSE_CONTENT_CODING: + return "HTTP_RESPONSE_CONTENT_CODING"; + + case ErrorCode.HTTP_RESPONSE_TIMEOUT: + return "HTTP_RESPONSE_TIMEOUT"; + + case ErrorCode.HTTP_UPGRADE_FAILED: + return "HTTP_UPGRADE_FAILED"; + + case ErrorCode.HTTP_PROTOCOL_ERROR: + return "HTTP_PROTOCOL_ERROR"; + + case ErrorCode.LOOP_DETECTED: + return "LOOP_DETECTED"; + + case ErrorCode.CONFIGURATION_ERROR: + return "CONFIGURATION_ERROR"; + + case ErrorCode.INTERNAL_ERROR: + return "INTERNAL_ERROR"; + + default: + return $"{code.Tag}"; + } + } + } +} diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiInputStream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiInputStream.cs new file mode 100644 index 00000000000..95c5577e3b2 --- /dev/null +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiInputStream.cs @@ -0,0 +1,197 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using WasiHttpWorld; +using WasiHttpWorld.wit.imports.wasi.http.v0_2_1; +using WasiHttpWorld.wit.imports.wasi.io.v0_2_1; +using static WasiHttpWorld.wit.imports.wasi.http.v0_2_1.ITypes; +using static WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IStreams; + +namespace System.Net.Http +{ + // on top of https://github.com/WebAssembly/wasi-io/blob/main/wit/streams.wit + internal sealed class WasiInputStream : Stream + { + private WasiRequestWrapper wrapper; // owned by this instance + private IncomingBody body; // owned by this instance + private InputStream stream; // owned by this instance + + private int offset; + private byte[]? buffer; + private bool otherSideClosed; + internal bool isClosed; + + public override bool CanRead => true; + public override bool CanWrite => false; + public override bool CanSeek => false; + + public WasiInputStream(WasiRequestWrapper wrapper, IncomingBody body) + { + this.wrapper = wrapper; + this.body = body; + this.stream = body.Stream(); + } + + ~WasiInputStream() + { + Dispose(false); + } + + public override void Close() + { + if (!isClosed) + { + isClosed = true; + stream.Dispose(); + var futureTrailers = IncomingBody.Finish(body); // we just passed body ownership to Finish + futureTrailers.Dispose(); + } + base.Close(); + } + + protected override void Dispose(bool disposing) + { + if (!isClosed) + { + isClosed = true; + stream.Dispose(); + body.Dispose(); + } + + if (disposing) + { + // this helps with disposing WIT resources at the Close() time of this stream, instead of waiting for the GC + wrapper.Dispose(); + } + + base.Dispose(disposing); + } + + public override async Task ReadAsync( + byte[] bytes, + int offset, + int length, + CancellationToken cancellationToken + ) + { + ObjectDisposedException.ThrowIf(isClosed, this); + cancellationToken.ThrowIfCancellationRequested(); + while (true) + { + if (otherSideClosed) + { + return 0; + } + else if (this.buffer == null) + { + try + { + // TODO: should we add a special case to the bindings generator + // to allow passing a buffer to InputStream.Read and + // avoid the extra copy? + var result = stream.Read(16 * 1024); + var buffer = result; + if (buffer.Length == 0) + { + cancellationToken.ThrowIfCancellationRequested(); + await WasiHttpInterop.RegisterWasiPollable(stream.Subscribe(), cancellationToken).ConfigureAwait(false); + ObjectDisposedException.ThrowIf(isClosed, this); + } + else + { + this.buffer = buffer; + this.offset = 0; + } + } + catch (WitException e) + { + if (((StreamError)e.Value).Tag == StreamError.CLOSED) + { + otherSideClosed = true; + return 0; + } + else + { + // TODO translate error ? + throw; + } + } + } + else + { + var min = Math.Min(this.buffer.Length - this.offset, length); + Array.Copy(this.buffer, this.offset, bytes, offset, min); + if (min < buffer.Length - this.offset) + { + this.offset += min; + } + else + { + this.buffer = null; + } + return min; + } + } + } + + public override async ValueTask ReadAsync( + Memory buffer, + CancellationToken cancellationToken = default + ) + { + // TODO: avoid copy when possible and use ArrayPool when not + var dst = new byte[buffer.Length]; + // We disable "CA1835: Prefer the memory-based overloads of + // ReadAsync/WriteAsync methods in stream-based classes" for + // now, since `ReadyAsync(byte[], int, int, CancellationToken)` + // is where the implementation currently resides, but we should + // revisit this if/when `wit-bindgen` learns to generate + // memory-based bindings. +#pragma warning disable CA1835 + var result = await ReadAsync(dst, 0, buffer.Length, cancellationToken) + .ConfigureAwait(false); +#pragma warning restore CA1835 + new ReadOnlySpan(dst, 0, result).CopyTo(buffer.Span); + return result; + } + + #region PlatformNotSupported + + public override void Flush() + { + // ignore + } + + public override void SetLength(long length) + { + throw new PlatformNotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int length) + { + throw new PlatformNotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new PlatformNotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int length) + { + throw new PlatformNotSupportedException(); + } + + public override long Length => throw new PlatformNotSupportedException(); + public override long Position + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + + #endregion + } +} diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiOutputStream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiOutputStream.cs new file mode 100644 index 00000000000..5bf3ea3ddc6 --- /dev/null +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiOutputStream.cs @@ -0,0 +1,162 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using WasiHttpWorld; +using WasiHttpWorld.wit.imports.wasi.http.v0_2_1; +using WasiHttpWorld.wit.imports.wasi.io.v0_2_1; +using static WasiHttpWorld.wit.imports.wasi.http.v0_2_1.ITypes; +using static WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IStreams; + +namespace System.Net.Http +{ + // on top of https://github.com/WebAssembly/wasi-io/blob/main/wit/streams.wit + internal sealed class WasiOutputStream : Stream + { + private OutputStream stream; // owned by this instance + private OutgoingBody body; // owned by this instance + internal bool isClosed; + + public override bool CanRead => false; + public override bool CanWrite => true; + public override bool CanSeek => false; + + public WasiOutputStream(OutgoingBody body) + { + this.body = body; + this.stream = body.Write(); + } + + ~WasiOutputStream() + { + Dispose(false); + } + + public override void Close() + { + if (!isClosed) + { + isClosed = true; + stream.Dispose(); + OutgoingBody.Finish(body, null); + } + base.Close(); + } + + protected override void Dispose(bool disposing) + { + if (!isClosed) + { + isClosed = true; + stream.Dispose(); + body.Dispose(); + } + base.Dispose(disposing); + } + + public override async Task WriteAsync( + byte[] bytes, + int offset, + int length, + CancellationToken cancellationToken + ) + { + ObjectDisposedException.ThrowIf(isClosed, this); + var limit = offset + length; + var flushing = false; + while (true) + { + var count = (int)stream.CheckWrite(); + if (count == 0) + { + await WasiHttpInterop.RegisterWasiPollable(stream.Subscribe(), cancellationToken).ConfigureAwait(false); + ObjectDisposedException.ThrowIf(isClosed, this); + } + else if (offset == limit) + { + if (flushing) + { + return; + } + else + { + stream.Flush(); + flushing = true; + } + } + else + { + var min = Math.Min(count, limit - offset); + if (offset == 0 && min == bytes.Length) + { + stream.Write(bytes); + } + else + { + // TODO: is there a more efficient option than copying here? + // Do we need to change the binding generator to accept + // e.g. `Span`s? + var copy = new byte[min]; + Array.Copy(bytes, offset, copy, 0, min); + stream.Write(copy); + } + offset += min; + } + } + } + + public override ValueTask WriteAsync( + ReadOnlyMemory buffer, + CancellationToken cancellationToken = default + ) + { + // TODO: avoid copy when possible and use ArrayPool when not + var copy = new byte[buffer.Length]; + buffer.Span.CopyTo(copy); + return new ValueTask(WriteAsync(copy, 0, buffer.Length, cancellationToken)); + } + + #region PlatformNotSupported + + public override void Flush() + { + // ignore + // + // Note that flushing a `wasi:io/streams/output-stream` is an + // asynchronous operation, so it's not clear how we would + // implement it here instead of taking care of it as part of + // `WriteAsync`. + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new PlatformNotSupportedException(); + } + + public override void SetLength(long length) + { + throw new PlatformNotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int length) + { + throw new PlatformNotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int length) + { + throw new PlatformNotSupportedException(); + } + + public override long Length => throw new PlatformNotSupportedException(); + public override long Position + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + + #endregion + } +} diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/generate-wasi-http-bindings.ps1 b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/generate-wasi-http-bindings.ps1 new file mode 100644 index 00000000000..b57834c2242 --- /dev/null +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/generate-wasi-http-bindings.ps1 @@ -0,0 +1,21 @@ +# Prerequisites: +# powershell +# tar +# [cargo](https://rustup.rs/) +$ProgressPreference = 'SilentlyContinue' +$ErrorActionPreference='Stop' +$scriptpath = $MyInvocation.MyCommand.Path +$dir = Split-Path $scriptpath + +Push-Location $dir + + +cargo install --locked --no-default-features --features csharp --version 0.30.0 wit-bindgen-cli +Invoke-WebRequest -Uri https://github.com/WebAssembly/wasi-http/archive/refs/tags/v0.2.1.tar.gz -OutFile v0.2.1.tar.gz +tar xzf v0.2.1.tar.gz +cp world.wit wasi-http-0.2.1/wit/world.wit +wit-bindgen c-sharp -w wasi-http -r native-aot --internal --skip-support-files wasi-http-0.2.1/wit +rm -r wasi-http-0.2.1 +rm v0.2.1.tar.gz + +Pop-Location diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/generate-wasi-http-bindings.sh b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/generate-wasi-http-bindings.sh index 56a192b2508..3a9540c103c 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/generate-wasi-http-bindings.sh +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/generate-wasi-http-bindings.sh @@ -21,4 +21,3 @@ world wasi-http { } EOF wit-bindgen c-sharp -w wasi-http -r native-aot --internal wasi-http-0.2.1/wit -rm -r wasi-http-0.2.1 v0.2.1.tar.gz WasiHttpWorld_wasm_import_linkage_attribute.cs diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/world.wit b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/world.wit new file mode 100644 index 00000000000..3afd4fa8b6b --- /dev/null +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/world.wit @@ -0,0 +1,3 @@ +world wasi-http { + import outgoing-handler; +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs index 4ad7dff12fe..d52d443bd51 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs @@ -4,92 +4,121 @@ using System.Collections.Generic; using System.Threading.Tasks; using WasiPollWorld.wit.imports.wasi.io.v0_2_1; +using Pollable = WasiPollWorld.wit.imports.wasi.io.v0_2_1.IPoll.Pollable; namespace System.Threading { internal static class WasiEventLoop { - private static List s_pollables = new(); + // TODO: if the Pollable never resolves and and the Task is abandoned + // it will be leaked and stay in this list forever. + // it will also keep the Pollable handle alive and prevent it from being disposed + private static readonly List s_pollables = new(); - internal static Task RegisterWasiPollableHandle( - int handle, - CancellationToken cancellationToken - ) + internal static Task RegisterWasiPollableHandle(int handle, CancellationToken cancellationToken) { // note that this is duplicate of the original Pollable - // the original should be neutralized without disposing the handle - var pollableCpy = new IPoll.Pollable(new IPoll.Pollable.THandle(handle)); + // the original should have been neutralized without disposing the handle + var pollableCpy = new Pollable(new Pollable.THandle(handle)); return RegisterWasiPollable(pollableCpy, cancellationToken); } - internal static Task RegisterWasiPollable( - IPoll.Pollable pollable, - CancellationToken cancellationToken - ) + internal static Task RegisterWasiPollable(Pollable pollable, CancellationToken cancellationToken) { - var tcs = new TaskCompletionSource((pollable, cancellationToken)); - s_pollables.Add(tcs); - return tcs.Task; + // this will register the pollable holder into s_pollables + var holder = new PollableHolder(pollable, cancellationToken); + s_pollables.Add(holder); + return holder.taskCompletionSource.Task; } + // this is not thread safe internal static void DispatchWasiEventLoop() { ThreadPoolWorkQueue.Dispatch(); - if (s_pollables.Count > 0) + var holders = new List(s_pollables.Count); + var pending = new List(s_pollables.Count); + for (int i = 0; i < s_pollables.Count; i++) { - var pollables = s_pollables; - s_pollables = new List(pollables.Count); - var arguments = new List(pollables.Count); - var indexes = new List(pollables.Count); - var tasksCanceled = false; - for (var i = 0; i < pollables.Count; i++) + var holder = s_pollables[i]; + if (!holder.isDisposed) { - var tcs = pollables[i]; - var (pollable, cancellationToken) = ((IPoll.Pollable, CancellationToken)) - tcs.Task.AsyncState!; - if (cancellationToken.IsCancellationRequested) - { - tcs.SetCanceled(cancellationToken); - tasksCanceled = true; - } - else + holders.Add(holder); + pending.Add(holder.pollable); + } + } + + s_pollables.Clear(); + + if (pending.Count > 0) + { + var readyIndexes = PollInterop.Poll(pending); + for (int i = 0; i < readyIndexes.Length; i++) + { + uint readyIndex = readyIndexes[i]; + var holder = holders[(int)readyIndex]; + holder.ResolveAndDispose(); + } + for (int i = 0; i < holders.Count; i++) + { + PollableHolder holder = holders[i]; + if (!holder.isDisposed) { - arguments.Add(pollable); - indexes.Add(i); + s_pollables.Add(holder); } } + } + } - if (arguments.Count > 0) + private sealed class PollableHolder + { + public bool isDisposed; + public readonly Pollable pollable; + public readonly TaskCompletionSource taskCompletionSource; + public readonly CancellationTokenRegistration cancellationTokenRegistration; + public readonly CancellationToken cancellationToken; + + public PollableHolder(Pollable pollable, CancellationToken cancellationToken) + { + this.pollable = pollable; + this.cancellationToken = cancellationToken; + + // this means that taskCompletionSource.Task.AsyncState -> this; + // which means PollableHolder will be alive until the Task alive + taskCompletionSource = new TaskCompletionSource(this); + + // static method is used to avoid allocating a delegate + cancellationTokenRegistration = cancellationToken.Register(CancelAndDispose, this); + } + + public void ResolveAndDispose() + { + if (isDisposed) { - var ready = new bool[arguments.Count]; + return; + } - // If at least one task was canceled, we'll return without - // calling `poll` (i.e. delay calling `poll` until the next - // call to this function) to give any dependent tasks a - // chance to make progress before we block. - if (!tasksCanceled) - { - // this is blocking until at least one pollable resolves - var readyIndexes = PollInterop.Poll(arguments); - - foreach (int readyIndex in readyIndexes) - { - ready[readyIndex] = true; - arguments[readyIndex].Dispose(); - var tcs = pollables[indexes[readyIndex]]; - tcs.SetResult(); - } - } + // no need to unregister the holder from s_pollables, when this is called + isDisposed = true; + taskCompletionSource.TrySetResult(); + pollable.Dispose(); + cancellationTokenRegistration.Dispose(); + } - for (var i = 0; i < arguments.Count; ++i) - { - if (!ready[i]) - { - s_pollables.Add(pollables[indexes[i]]); - } - } + // for GC of abandoned Tasks or for cancellation + private static void CancelAndDispose(object? s) + { + PollableHolder self = (PollableHolder)s!; + if (self.isDisposed) + { + return; } + + // it will be removed from s_pollables on the next run + self.isDisposed = true; + self.taskCompletionSource.TrySetCanceled(self.cancellationToken); + self.pollable.Dispose(); + self.cancellationTokenRegistration.Dispose(); } } } diff --git a/src/tests/nativeaot/SmokeTests/http-p2/Program.cs b/src/tests/nativeaot/SmokeTests/http-p2/Program.cs new file mode 100644 index 00000000000..e940c0a4100 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/http-p2/Program.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Net.Http.Headers; +using System.Net.Http; +using System.Threading.Tasks; +using System.Threading; +using System.Runtime.CompilerServices; + +// keep in sync with src\mono\wasi\testassets\Http.cs +public static class WasiMainWrapper +{ + public static async Task MainAsync(string[] args) + { + await Task.Delay(100); + GC.Collect(); // test that Pollable->Task is not collected until resolved + + var ctsDelay = new CancellationTokenSource(10); + try { + await Task.Delay(1000, ctsDelay.Token); + throw new Exception("delay should have timed out"); + } catch (TaskCanceledException tce) { + if (ctsDelay.Token != tce.CancellationToken) + { + throw new Exception("Different CancellationToken for delay"); + } + Console.WriteLine("impatient delay was canceled as expected"); + } + + using HttpClient impatientClient1 = new(); + impatientClient1.DefaultRequestHeaders.Add("User-Agent", "dotnet WASI unit test"); + impatientClient1.Timeout = TimeSpan.FromMilliseconds(10); + try { + await impatientClient1.GetAsync("https://corefx-net-http11.azurewebsites.net/Echo.ashx?delay10sec"); + throw new Exception("request should have timed out"); + } catch (TaskCanceledException) { + Console.WriteLine("1st impatientClient was canceled as expected"); + } + + GC.Collect(); + + using HttpClient impatientClient2 = new(); + impatientClient2.DefaultRequestHeaders.Add("User-Agent", "dotnet WASI unit test"); + var cts = new CancellationTokenSource(10); + try { + // in reality server side doesn't delay because it doesn't implement it. So 10ms is bit fragile. + // TODO: remove this once we have real WASI HTTP library unit tests with local server loop in Xharness. + // https://github.com/dotnet/xharness/pull/1244 + await impatientClient2.GetAsync("https://corefx-net-http11.azurewebsites.net/Echo.ashx?delay10sec", cts.Token); + throw new Exception("request should have timed out"); + } catch (TaskCanceledException tce) { + if (cts.Token != tce.CancellationToken) + { + throw new Exception("Different CancellationToken"); + } + Console.WriteLine("2nd impatientClient was canceled as expected"); + } + + using HttpClient client = new(); + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Add("User-Agent", "dotnet WASI unit test"); + + var query="https://corefx-net-http11.azurewebsites.net/Echo.ashx"; + var json = await client.GetStringAsync(query); + + Console.WriteLine(); + Console.WriteLine("GET "+query); + Console.WriteLine(); + Console.WriteLine(json); + + GC.Collect(); + return 0; + } + + public static int Main(string[] args) + { + return PollWasiEventLoopUntilResolved((Thread)null!, MainAsync(args)); + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "PollWasiEventLoopUntilResolved")] + static extern int PollWasiEventLoopUntilResolved(Thread t, Task mainTask); + } + +} diff --git a/src/tests/nativeaot/SmokeTests/http-p2/Wasip2.Http.Console.Sample.csproj b/src/tests/nativeaot/SmokeTests/http-p2/Wasip2.Http.Console.Sample.csproj new file mode 100644 index 00000000000..a34345709a9 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/http-p2/Wasip2.Http.Console.Sample.csproj @@ -0,0 +1,16 @@ + + + $(NetCoreAppCurrent) + <_WasiNeedsHttp>true + + + + + + + + + From b25bc9e3080f63c1438c344c0b5a801830ec9052 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 29 Aug 2024 10:50:48 +0200 Subject: [PATCH 11/20] fix disposal of pollable --- .../src/System/Net/Http/WasiHttpHandler/WasiHttpHandler.cs | 4 +--- .../src/System/Threading/Wasi/WasiEventLoop.cs | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpHandler.cs index f5d416cbb09..f4e7a5925c2 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpHandler.cs @@ -140,9 +140,7 @@ public void Dispose() isDisposed = true; wasiOutputStream?.Dispose(); incomingStream?.Dispose(); - - // TODO why this fails ? - // future?.Dispose(); + future?.Dispose(); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs index d52d443bd51..450bd84898d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs @@ -100,9 +100,9 @@ public void ResolveAndDispose() // no need to unregister the holder from s_pollables, when this is called isDisposed = true; - taskCompletionSource.TrySetResult(); pollable.Dispose(); cancellationTokenRegistration.Dispose(); + taskCompletionSource.TrySetResult(); } // for GC of abandoned Tasks or for cancellation @@ -116,9 +116,9 @@ private static void CancelAndDispose(object? s) // it will be removed from s_pollables on the next run self.isDisposed = true; - self.taskCompletionSource.TrySetCanceled(self.cancellationToken); self.pollable.Dispose(); self.cancellationTokenRegistration.Dispose(); + self.taskCompletionSource.TrySetCanceled(self.cancellationToken); } } } From 5f3528547cc98c202a17a9bea3feaf6a867641ad Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Thu, 29 Aug 2024 10:45:11 -0600 Subject: [PATCH 12/20] update wit-bindgen and regenerate bindings (again) Signed-off-by: Joel Dice --- ...rld.wit.imports.wasi.http.v0_2_1.ITypes.cs | 55 ------------------- ...World.wit.imports.wasi.io.v0_2_1.IError.cs | 5 -- ...pWorld.wit.imports.wasi.io.v0_2_1.IPoll.cs | 5 -- ...rld.wit.imports.wasi.io.v0_2_1.IStreams.cs | 10 ---- .../generate-wasi-http-bindings.sh | 2 +- .../Wasi/generate-wasi-poll-bindings.sh | 2 +- .../generate-shared-library-bindings.sh | 2 +- 7 files changed, 3 insertions(+), 78 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.http.v0_2_1.ITypes.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.http.v0_2_1.ITypes.cs index da4478ab226..49d90ea14a9 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.http.v0_2_1.ITypes.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.http.v0_2_1.ITypes.cs @@ -605,7 +605,6 @@ internal Fields(THandle handle) { public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } [DllImport("wasi:http/types@0.2.1", EntryPoint = "[resource-drop]fields"), WasmImportLinkage] @@ -618,10 +617,6 @@ protected virtual void Dispose(bool disposing) { } } - ~Fields() { - Dispose(false); - } - internal static class ConstructorWasmInterop { [DllImport("wasi:http/types@0.2.1", EntryPoint = "[constructor]fields"), WasmImportLinkage] @@ -1071,7 +1066,6 @@ internal IncomingRequest(THandle handle) { public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } [DllImport("wasi:http/types@0.2.1", EntryPoint = "[resource-drop]incoming-request"), WasmImportLinkage] @@ -1084,10 +1078,6 @@ protected virtual void Dispose(bool disposing) { } } - ~IncomingRequest() { - Dispose(false); - } - internal static class MethodWasmInterop { [DllImport("wasi:http/types@0.2.1", EntryPoint = "[method]incoming-request.method"), WasmImportLinkage] @@ -1385,7 +1375,6 @@ internal OutgoingRequest(THandle handle) { public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } [DllImport("wasi:http/types@0.2.1", EntryPoint = "[resource-drop]outgoing-request"), WasmImportLinkage] @@ -1398,10 +1387,6 @@ protected virtual void Dispose(bool disposing) { } } - ~OutgoingRequest() { - Dispose(false); - } - internal static class ConstructorWasmInterop { [DllImport("wasi:http/types@0.2.1", EntryPoint = "[constructor]outgoing-request"), WasmImportLinkage] @@ -2069,7 +2054,6 @@ internal RequestOptions(THandle handle) { public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } [DllImport("wasi:http/types@0.2.1", EntryPoint = "[resource-drop]request-options"), WasmImportLinkage] @@ -2082,10 +2066,6 @@ protected virtual void Dispose(bool disposing) { } } - ~RequestOptions() { - Dispose(false); - } - internal static class ConstructorWasmInterop { [DllImport("wasi:http/types@0.2.1", EntryPoint = "[constructor]request-options"), WasmImportLinkage] @@ -2401,7 +2381,6 @@ internal ResponseOutparam(THandle handle) { public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } [DllImport("wasi:http/types@0.2.1", EntryPoint = "[resource-drop]response-outparam"), WasmImportLinkage] @@ -2414,10 +2393,6 @@ protected virtual void Dispose(bool disposing) { } } - ~ResponseOutparam() { - Dispose(false); - } - internal static class SetWasmInterop { [DllImport("wasi:http/types@0.2.1", EntryPoint = "[static]response-outparam.set"), WasmImportLinkage] @@ -3409,7 +3384,6 @@ internal IncomingResponse(THandle handle) { public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } [DllImport("wasi:http/types@0.2.1", EntryPoint = "[resource-drop]incoming-response"), WasmImportLinkage] @@ -3422,10 +3396,6 @@ protected virtual void Dispose(bool disposing) { } } - ~IncomingResponse() { - Dispose(false); - } - internal static class StatusWasmInterop { [DllImport("wasi:http/types@0.2.1", EntryPoint = "[method]incoming-response.status"), WasmImportLinkage] @@ -3528,7 +3498,6 @@ internal IncomingBody(THandle handle) { public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } [DllImport("wasi:http/types@0.2.1", EntryPoint = "[resource-drop]incoming-body"), WasmImportLinkage] @@ -3541,10 +3510,6 @@ protected virtual void Dispose(bool disposing) { } } - ~IncomingBody() { - Dispose(false); - } - internal static class StreamWasmInterop { [DllImport("wasi:http/types@0.2.1", EntryPoint = "[method]incoming-body.stream"), WasmImportLinkage] @@ -3629,7 +3594,6 @@ internal FutureTrailers(THandle handle) { public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } [DllImport("wasi:http/types@0.2.1", EntryPoint = "[resource-drop]future-trailers"), WasmImportLinkage] @@ -3642,10 +3606,6 @@ protected virtual void Dispose(bool disposing) { } } - ~FutureTrailers() { - Dispose(false); - } - internal static class SubscribeWasmInterop { [DllImport("wasi:http/types@0.2.1", EntryPoint = "[method]future-trailers.subscribe"), WasmImportLinkage] @@ -4353,7 +4313,6 @@ internal OutgoingResponse(THandle handle) { public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } [DllImport("wasi:http/types@0.2.1", EntryPoint = "[resource-drop]outgoing-response"), WasmImportLinkage] @@ -4366,10 +4325,6 @@ protected virtual void Dispose(bool disposing) { } } - ~OutgoingResponse() { - Dispose(false); - } - internal static class ConstructorWasmInterop { [DllImport("wasi:http/types@0.2.1", EntryPoint = "[constructor]outgoing-response"), WasmImportLinkage] @@ -4535,7 +4490,6 @@ internal OutgoingBody(THandle handle) { public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } [DllImport("wasi:http/types@0.2.1", EntryPoint = "[resource-drop]outgoing-body"), WasmImportLinkage] @@ -4548,10 +4502,6 @@ protected virtual void Dispose(bool disposing) { } } - ~OutgoingBody() { - Dispose(false); - } - internal static class WriteWasmInterop { [DllImport("wasi:http/types@0.2.1", EntryPoint = "[method]outgoing-body.write"), WasmImportLinkage] @@ -5263,7 +5213,6 @@ internal FutureIncomingResponse(THandle handle) { public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } [DllImport("wasi:http/types@0.2.1", EntryPoint = "[resource-drop]future-incoming-response"), WasmImportLinkage] @@ -5276,10 +5225,6 @@ protected virtual void Dispose(bool disposing) { } } - ~FutureIncomingResponse() { - Dispose(false); - } - internal static class SubscribeWasmInterop { [DllImport("wasi:http/types@0.2.1", EntryPoint = "[method]future-incoming-response.subscribe"), WasmImportLinkage] diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IError.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IError.cs index 848adb0d65e..d343e4d6792 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IError.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IError.cs @@ -46,7 +46,6 @@ internal Error(THandle handle) { public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } [DllImport("wasi:io/error@0.2.1", EntryPoint = "[resource-drop]error"), WasmImportLinkage] @@ -59,10 +58,6 @@ protected virtual void Dispose(bool disposing) { } } - ~Error() { - Dispose(false); - } - internal static class ToDebugStringWasmInterop { [DllImport("wasi:io/error@0.2.1", EntryPoint = "[method]error.to-debug-string"), WasmImportLinkage] diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IPoll.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IPoll.cs index 225c9915334..f8b819a4ce1 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IPoll.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IPoll.cs @@ -30,7 +30,6 @@ internal Pollable(THandle handle) { public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } [DllImport("wasi:io/poll@0.2.1", EntryPoint = "[resource-drop]pollable"), WasmImportLinkage] @@ -43,10 +42,6 @@ protected virtual void Dispose(bool disposing) { } } - ~Pollable() { - Dispose(false); - } - internal static class ReadyWasmInterop { [DllImport("wasi:io/poll@0.2.1", EntryPoint = "[method]pollable.ready"), WasmImportLinkage] diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IStreams.cs b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IStreams.cs index 05899108409..2c70d84b1dd 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IStreams.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/WasiHttpWorld.wit.imports.wasi.io.v0_2_1.IStreams.cs @@ -73,7 +73,6 @@ internal InputStream(THandle handle) { public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } [DllImport("wasi:io/streams@0.2.1", EntryPoint = "[resource-drop]input-stream"), WasmImportLinkage] @@ -86,10 +85,6 @@ protected virtual void Dispose(bool disposing) { } } - ~InputStream() { - Dispose(false); - } - internal static class ReadWasmInterop { [DllImport("wasi:io/streams@0.2.1", EntryPoint = "[method]input-stream.read"), WasmImportLinkage] @@ -389,7 +384,6 @@ internal OutputStream(THandle handle) { public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } [DllImport("wasi:io/streams@0.2.1", EntryPoint = "[resource-drop]output-stream"), WasmImportLinkage] @@ -402,10 +396,6 @@ protected virtual void Dispose(bool disposing) { } } - ~OutputStream() { - Dispose(false); - } - internal static class CheckWriteWasmInterop { [DllImport("wasi:io/streams@0.2.1", EntryPoint = "[method]output-stream.check-write"), WasmImportLinkage] diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/generate-wasi-http-bindings.sh b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/generate-wasi-http-bindings.sh index 3a9540c103c..73b186234cf 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/generate-wasi-http-bindings.sh +++ b/src/libraries/System.Net.Http/src/System/Net/Http/WasiHttpHandler/generate-wasi-http-bindings.sh @@ -12,7 +12,7 @@ set -ex # [curl](https://curl.se/download.html) # TODO: switch to crates.io release once https://github.com/bytecodealliance/wit-bindgen/pull/1040 is merged and released -cargo install --locked --no-default-features --features csharp --git https://github.com/dicej/wit-bindgen --rev 34afca03 wit-bindgen-cli +cargo install --locked --no-default-features --features csharp --git https://github.com/dicej/wit-bindgen --rev 694fd927 wit-bindgen-cli curl -OL https://github.com/WebAssembly/wasi-http/archive/refs/tags/v0.2.1.tar.gz tar xzf v0.2.1.tar.gz cat >wasi-http-0.2.1/wit/world.wit <wasi-http-0.2.1/wit/world.wit < Date: Thu, 29 Aug 2024 12:35:35 -0600 Subject: [PATCH 13/20] specify zero expected exit code for WASIp2 smoke test Signed-off-by: Joel Dice --- .../SmokeTests/http-p2/Wasip2.Http.Console.Sample.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tests/nativeaot/SmokeTests/http-p2/Wasip2.Http.Console.Sample.csproj b/src/tests/nativeaot/SmokeTests/http-p2/Wasip2.Http.Console.Sample.csproj index a34345709a9..dce62534892 100644 --- a/src/tests/nativeaot/SmokeTests/http-p2/Wasip2.Http.Console.Sample.csproj +++ b/src/tests/nativeaot/SmokeTests/http-p2/Wasip2.Http.Console.Sample.csproj @@ -2,6 +2,7 @@ $(NetCoreAppCurrent) <_WasiNeedsHttp>true + 0 - - - - - - - -