Skip to content

Commit

Permalink
support System.Net.Http.HttpClient on WASIp2
Browse files Browse the repository at this point in the history
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 <[email protected]>

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 <[email protected]>

various WASI build tweaks

Signed-off-by: Joel Dice <[email protected]>

quote libWasiHttp.a path in custom linker arg

Signed-off-by: Joel Dice <[email protected]>

fix wasm-component-ld download command

Signed-off-by: Joel Dice <[email protected]>

tweak EmccExtraArgs in CustomMain.csproj so wasm-component-ld understands it

Signed-off-by: Joel Dice <[email protected]>

update CMake minimum version in wasi-sdk-p2.cmake

Signed-off-by: Joel Dice <[email protected]>

use `HeaderDescriptor` to sort content and response headers

Signed-off-by: Joel Dice <[email protected]>

allow building native WASI test code in src/tests/build.sh

Signed-off-by: Joel Dice <[email protected]>

allow WASI runtime tests to be built and run on non-Windows systems

Signed-off-by: Joel Dice <[email protected]>

update runtime tests to work with WASIp2

As of this writing, WASIp2 [does not support process exit
statuses](WebAssembly/wasi-cli#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 <[email protected]>

fix CI for Windows builds; remove unused file

Signed-off-by: Joel Dice <[email protected]>

disable sprintf warning in llvmlssa.cpp on macOS

Signed-off-by: Joel Dice <[email protected]>

remove LibraryWorld_cabi_realloc.o

I didn't mean to add this to Git.

Signed-off-by: Joel Dice <[email protected]>

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 <[email protected]>

update to `wit-bindgen` 0.27.0 and regenerate bindings

Signed-off-by: Joel Dice <[email protected]>

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 <[email protected]>

implement SystemNative_SysLog for WASI

Signed-off-by: Joel Dice <[email protected]>

increase NodeJS stack trace limit to 200

Signed-off-by: Joel Dice <[email protected]>

give guest no filesystem access in SharedLibraryDriver.mjs

Signed-off-by: Joel Dice <[email protected]>

switch to Trace.Assert into HttpClient smoke test

Signed-off-by: Joel Dice <[email protected]>

rename WASIp2 directory to Wasi

Signed-off-by: Joel Dice <[email protected]>

fix non-GET methods and add HttpClient echo test

Signed-off-by: Joel Dice <[email protected]>

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 <[email protected]>

fix gen-buildsys.sh regression

Signed-off-by: Joel Dice <[email protected]>

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 <[email protected]>

use `&` operator to simplify install-jco.ps1

Signed-off-by: Joel Dice <[email protected]>

remove redundant `CheckWasmSdks` target from SharedLibrary.csproj

Signed-off-by: Joel Dice <[email protected]>

split `FindWasmHostExecutable.sh` out of `FindWasmHostExecutableAndRun.sh`

Signed-off-by: Joel Dice <[email protected]>

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 <[email protected]>

preserve slashes in path in SharedLibrary.csproj

Signed-off-by: Joel Dice <[email protected]>

temporarily disable ThreadPoolWorkQueue.Dispatch assertion

See dotnet/runtime#104803

Signed-off-by: Joel Dice <[email protected]>

update `wit-bindgen` to version 0.28.0

Signed-off-by: Joel Dice <[email protected]>

upgrade to wasi-sdk 24 and wit-bindgen 0.29.0

Signed-off-by: Joel Dice <[email protected]>

check for WASI in `PhysicalFileProvider.CreateFileWatcher`

Signed-off-by: Joel Dice <[email protected]>

switch back to WASI 0.2.0

0.2.1 is not yet widely supported, and causes
[trouble](bytecodealliance/jco#486) for Jco, which
rely on for the `SharedLibrary` test.

Signed-off-by: Joel Dice <[email protected]>

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 <[email protected]>

skip unsupported signal handling on WASI

Signed-off-by: Joel Dice <[email protected]>

throw PlatformNotSupportedException in ManualResetEventSlim.Wait on WASI

Otherwise, we end up in an infinite loop.

Signed-off-by: Joel Dice <[email protected]>

Revert "switch back to WASI 0.2.0"

This reverts commit a8608b4.

enable `NameResolution` and `Sockets` on WASI

Signed-off-by: Joel Dice <[email protected]>

set `SocketsHttpHandler.IsEnabled` to `false` on WASI

...at least until we get `System.Net.Sockets` working.

Signed-off-by: Joel Dice <[email protected]>
  • Loading branch information
dicej committed Aug 26, 2024
1 parent c213405 commit beabe5a
Show file tree
Hide file tree
Showing 29 changed files with 1,245 additions and 81 deletions.
2 changes: 2 additions & 0 deletions eng/pipelines/common/global-build-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions eng/pipelines/runtimelab/install-jco.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
$RootPath = $Args[0]

$NpmExePath = $Env:NPM_EXECUTABLE

Set-Location -Path $RootPath

& $NpmExePath install @bytecodealliance/jco
& $NpmExePath install @bytecodealliance/preview2-shim

11 changes: 11 additions & 0 deletions eng/pipelines/runtimelab/install-nodejs.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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"
5 changes: 4 additions & 1 deletion eng/testing/FindWasmHostExecutable.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 5 additions & 3 deletions src/coreclr/build-runtime.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
)
)
)
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/llvmlssa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
1 change: 0 additions & 1 deletion src/coreclr/nativeaot/Runtime/Portable/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ public partial class ConsoleLifetime : IHostLifetime

private partial void RegisterShutdownHandlers()
{
Action<PosixSignalContext> handler = HandlePosixSignal;
_sigIntRegistration = PosixSignalRegistration.Create(PosixSignal.SIGINT, handler);
_sigQuitRegistration = PosixSignalRegistration.Create(PosixSignal.SIGQUIT, handler);
_sigTermRegistration = PosixSignalRegistration.Create(PosixSignal.SIGTERM, handler);
if (!OperatingSystem.IsWasi())
{
Action<PosixSignalContext> 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)
Expand All @@ -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();
}
}
}
}
3 changes: 1 addition & 2 deletions src/libraries/System.Net.Http/src/System.Net.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@
</ItemGroup>

<!-- TODO-LLVM: This is not upstreamable because it makes the build runtime-specific. -->
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'wasi' and '$(RuntimeFlavor)' != 'CoreCLR'">
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'wasi'">
<Reference Include="System.Threading.Thread" />
<Compile Include="System\Net\Http\WasiHttpHandler\WasiHttpHandler.cs" />
<Compile Include="System\Net\Http\WasiHttpHandler\WasiHttp.cs" />
Expand Down Expand Up @@ -532,5 +532,4 @@
<ItemGroup>
<None Include="Resources\SR.resx" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.

Expand Down Expand Up @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private void CheckDisposedOrStarted()
/// Gets a value that indicates whether the handler is supported on the current platform.
/// </summary>
[UnsupportedOSPlatformGuard("browser")]
public static bool IsSupported => !OperatingSystem.IsBrowser();
public static bool IsSupported => !(OperatingSystem.IsBrowser() || OperatingSystem.IsWasi());

public bool UseCookies
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-unix;$(NetCoreAppCurrent)-browser;$(NetCoreAppCurrent)</TargetFrameworks>
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-unix;$(NetCoreAppCurrent)-browser;$(NetCoreAppCurrent)-wasi;$(NetCoreAppCurrent)</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseCompilerGeneratedDocXmlFile>false</UseCompilerGeneratedDocXmlFile>
</PropertyGroup>
Expand Down Expand Up @@ -68,7 +68,7 @@
Link="Common\Interop\Windows\WinSock\Interop.GetAddrInfoExW.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'unix'">
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'unix' or '$(TargetPlatformIdentifier)' == 'wasi'">
<Compile Include="System\Net\NameResolutionPal.Unix.cs" />
<Compile Include="$(CommonPath)System\Net\InteropIPAddressExtensions.Unix.cs"
Link="Common\System\Net\InteropIPAddressExtensions.Unix.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-unix;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)</TargetFrameworks>
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-unix;$(NetCoreAppCurrent)-wasi;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- SYSTEM_NET_SOCKETS_DLL is required to allow source-level code sharing for types defined within the
System.Net.Internals namespace. -->
Expand Down Expand Up @@ -183,7 +183,7 @@
Link="Common\System\Net\CompletionPortHelper.Windows.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'unix' or '$(TargetPlatformIdentifier)' == 'osx' or '$(TargetPlatformIdentifier)' == 'ios' or '$(TargetPlatformIdentifier)' == 'tvos'">
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'unix' or '$(TargetPlatformIdentifier)' == 'osx' or '$(TargetPlatformIdentifier)' == 'ios' or '$(TargetPlatformIdentifier)' == 'tvos' or '$(TargetPlatformIdentifier)' == 'wasi'">
<Compile Include="System\Net\Sockets\SafeSocketHandle.Unix.cs" />
<Compile Include="System\Net\Sockets\Socket.Unix.cs" />
<Compile Include="System\Net\Sockets\SocketAsyncContext.Unix.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ internal static int PollWasiEventLoopUntilResolved(Task<int> mainTask)
return mainTask.Result;
}

internal static void DispatchWasiEventLoop()
{
WasiEventLoop.DispatchWasiEventLoop();
}
#endif

// the closest analog to Sleep(0) on Unix is sched_yield
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace System.Threading
{
internal static class WasiEventLoop
{
private static List<WeakReference<TaskCompletionSource>> s_pollables = new();
private static List<TaskCompletionSource> s_pollables = new();

internal static Task RegisterWasiPollableHandle(int handle)
{
Expand All @@ -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<TaskCompletionSource>(tcs);
s_pollables.Add(weakRef);
s_pollables.Add(tcs);
return tcs.Task;
}

Expand All @@ -34,39 +33,36 @@ internal static void DispatchWasiEventLoop()
if (s_pollables.Count > 0)
{
var pollables = s_pollables;
s_pollables = new List<WeakReference<TaskCompletionSource>>(pollables.Count);
s_pollables = new List<TaskCompletionSource>(pollables.Count);
var arguments = new List<IPoll.Pollable>(pollables.Count);
var indexes = new List<int>(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]]);
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/tests/nativeaot/SmokeTests/HelloWasm/HelloWasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
1 change: 1 addition & 0 deletions src/tests/nativeaot/SmokeTests/SharedLibrary/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
registry=https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/
Loading

0 comments on commit beabe5a

Please sign in to comment.