mirror of
https://github.com/VSadov/Satori.git
synced 2025-06-08 03:27:04 +09:00
[WASI] timers based on wasi:clocks (#105879)
This commit is contained in:
parent
019d7580a2
commit
7c0364edf7
19 changed files with 474 additions and 89 deletions
|
@ -20,8 +20,6 @@
|
|||
<ILLinkTrimOutputPath>$(IntermediateOutputPath)</ILLinkTrimOutputPath>
|
||||
|
||||
<ILLinkDescriptorsXml Condition="'$(ILLinkDescriptorsXml)' == '' and Exists('$(ILLinkDirectory)ILLink.Descriptors.xml')">$(ILLinkDirectory)ILLink.Descriptors.xml</ILLinkDescriptorsXml>
|
||||
<!-- ILLink.Descriptors.LibraryBuild.xml files are only used during building the library, not an app. They shouldn't be embedded into the assembly. -->
|
||||
<ILLinkDescriptorsLibraryBuildXml Condition="'$(ILLinkDescriptorsLibraryBuildXml)' == '' and Exists('$(ILLinkDirectory)ILLink.Descriptors.LibraryBuild.xml')">$(ILLinkDirectory)ILLink.Descriptors.LibraryBuild.xml</ILLinkDescriptorsLibraryBuildXml>
|
||||
<ILLinkDescriptorsXmlIntermediatePath>$(IntermediateOutputPath)ILLink.Descriptors.xml</ILLinkDescriptorsXmlIntermediatePath>
|
||||
|
||||
<ILLinkSubstitutionsXmlIntermediatePath>$(IntermediateOutputPath)ILLink.Substitutions.xml</ILLinkSubstitutionsXmlIntermediatePath>
|
||||
|
@ -41,6 +39,9 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- ILLink.Descriptors.LibraryBuild.xml files are only used during building the library, not an app. They shouldn't be embedded into the assembly. -->
|
||||
<ILLinkDescriptorsLibraryBuildXml Include="$(ILLinkDirectory)ILLink.Descriptors.LibraryBuild.xml"
|
||||
Condition="Exists('$(ILLinkDirectory)ILLink.Descriptors.LibraryBuild.xml')" />
|
||||
<ILLinkSuppressionsLibraryBuildXml Include="$(ILLinkSuppressionsXmlPrefix).LibraryBuild.xml"
|
||||
Condition="Exists('$(ILLinkSuppressionsXmlPrefix).LibraryBuild.xml')" />
|
||||
|
||||
|
@ -210,7 +211,7 @@
|
|||
<ILLinkArgs Condition="'$(ILLinkRewritePDBs)' == 'true' and Exists('$(ILLinkTrimAssemblySymbols)')">$(ILLinkArgs) -b true</ILLinkArgs>
|
||||
<ILLinkArgs Condition="'$(ILLinkRewritePDBs)' == 'true' and Exists('$(ILLinkTrimAssemblySymbols)') and '$(DeterministicSourcePaths)' == 'true'">$(ILLinkArgs) --preserve-symbol-paths</ILLinkArgs>
|
||||
<!-- pass the non-embedded descriptors xml file on the command line -->
|
||||
<ILLinkArgs Condition="'$(ILLinkDescriptorsLibraryBuildXml)' != ''">$(ILLinkArgs) -x "$(ILLinkDescriptorsLibraryBuildXml)"</ILLinkArgs>
|
||||
<ILLinkArgs Condition="'@(ILLinkDescriptorsLibraryBuildXml)' != ''">$(ILLinkArgs) -x "@(ILLinkDescriptorsLibraryBuildXml->'%(FullPath)', '" -x "')"</ILLinkArgs>
|
||||
<ILLinkArgs Condition="'$(ILLinkSubstitutionsLibraryBuildXml)' != ''">$(ILLinkArgs) --substitutions "$(ILLinkSubstitutionsLibraryBuildXml)"</ILLinkArgs>
|
||||
<ILLinkArgs Condition="'@(ILLinkSuppressionsLibraryBuildXml)' != ''">$(ILLinkArgs) --link-attributes "@(ILLinkSuppressionsLibraryBuildXml->'%(FullPath)', '" --link-attributes "')"</ILLinkArgs>
|
||||
<!-- suppress warnings with the following codes:
|
||||
|
|
|
@ -3,16 +3,35 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.DotNet.XHarness.TestRunners.Common;
|
||||
using Microsoft.DotNet.XHarness.TestRunners.Xunit;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
public class WasmTestRunner : WasmApplicationEntryPoint
|
||||
{
|
||||
protected int MaxParallelThreadsFromArg { get; set; }
|
||||
protected override int? MaxParallelThreads => RunInParallel ? MaxParallelThreadsFromArg : base.MaxParallelThreads;
|
||||
|
||||
public static async Task<int> Main(string[] args)
|
||||
#if TARGET_WASI
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
return PollWasiEventLoopUntilResolved((Thread)null!, MainAsync(args));
|
||||
|
||||
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "PollWasiEventLoopUntilResolved")]
|
||||
static extern int PollWasiEventLoopUntilResolved(Thread t, Task<int> mainTask);
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
public static Task<int> Main(string[] args)
|
||||
{
|
||||
return MainAsync(args);
|
||||
}
|
||||
#endif
|
||||
|
||||
public static async Task<int> MainAsync(string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
|
|
|
@ -2,7 +2,13 @@
|
|||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Nullable>enable</Nullable>
|
||||
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
|
||||
<TargetFrameworks>$(NetCoreAppCurrent)-browser;$(NetCoreAppCurrent)-wasi;$(NetCoreAppCurrent)</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<!-- DesignTimeBuild requires all the TargetFramework Derived Properties to not be present in the first property group. -->
|
||||
<PropertyGroup>
|
||||
<TargetPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))</TargetPlatformIdentifier>
|
||||
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'browser'">$(DefineConstants);TARGET_BROWSER</DefineConstants>
|
||||
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'wasi'">$(DefineConstants);TARGET_WASI</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="WasmTestRunner.cs" />
|
||||
|
|
|
@ -314,7 +314,7 @@ namespace System.Net.Http
|
|||
}
|
||||
else
|
||||
{
|
||||
await WasiEventLoop.RegisterWasiPollable(future.Subscribe()).ConfigureAwait(false);
|
||||
await RegisterWasiPollable(future.Subscribe()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -461,19 +461,22 @@ namespace System.Net.Http
|
|||
}
|
||||
}
|
||||
|
||||
private static class WasiEventLoop
|
||||
private static Task RegisterWasiPollable(IPoll.Pollable pollable)
|
||||
{
|
||||
internal static Task RegisterWasiPollable(IPoll.Pollable pollable)
|
||||
{
|
||||
var handle = pollable.Handle;
|
||||
pollable.Handle = 0;
|
||||
return CallRegisterWasiPollable((Thread)null!, handle);
|
||||
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);
|
||||
|
||||
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "RegisterWasiPollable")]
|
||||
static extern Task CallRegisterWasiPollable(Thread t, int handle);
|
||||
}
|
||||
}
|
||||
|
||||
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "RegisterWasiPollableHandle")]
|
||||
private static extern Task CallRegisterWasiPollableHandle(Thread t, int handle);
|
||||
|
||||
private sealed class InputStream : Stream
|
||||
{
|
||||
private ITypes.IncomingBody body;
|
||||
|
@ -559,8 +562,7 @@ namespace System.Net.Http
|
|||
var buffer = result;
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
await WasiEventLoop
|
||||
.RegisterWasiPollable(stream.Subscribe())
|
||||
await RegisterWasiPollable(stream.Subscribe())
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
|
@ -697,7 +699,7 @@ namespace System.Net.Http
|
|||
var count = (int)stream.CheckWrite();
|
||||
if (count == 0)
|
||||
{
|
||||
await WasiEventLoop.RegisterWasiPollable(stream.Subscribe()).ConfigureAwait(false);
|
||||
await RegisterWasiPollable(stream.Subscribe()).ConfigureAwait(false);
|
||||
}
|
||||
else if (offset == limit)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<linker>
|
||||
<assembly fullname="System.Private.CoreLib">
|
||||
<!-- these methods are temporarily accessed via UnsafeAccessor from generated code until we have it in public API, probably in WASI preview3 and promises -->
|
||||
<type fullname="System.Threading.Thread">
|
||||
<method name="RegisterWasiPollableHandle" />
|
||||
<method name="PollWasiEventLoopUntilResolved" />
|
||||
</type>
|
||||
</assembly>
|
||||
</linker>
|
|
@ -64,10 +64,9 @@
|
|||
<ILLinkSubstitutionsXmls Include="$(ILLinkSharedDirectory)ILLink.Substitutions.Browser.xml" Condition="'$(TargetsBrowser)' == 'true'" />
|
||||
<ILLinkLinkAttributesXmls Include="$(ILLinkSharedDirectory)ILLink.LinkAttributes.Shared.xml" />
|
||||
<ILLinkSuppressionsLibraryBuildXml Include="$(ILLinkSharedDirectory)ILLink.Suppressions.LibraryBuild.xml" />
|
||||
<ILLinkDescriptorsLibraryBuildXml Include="$(ILLinkSharedDirectory)ILLink.Descriptors.LibraryBuild.xml" />
|
||||
<ILLinkDescriptorsLibraryBuildXml Include="$(ILLinkSharedDirectory)ILLink.Descriptors.LibraryBuild.WASI.xml" Condition="'$(TargetsWasi)' == 'true'" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<ILLinkDescriptorsLibraryBuildXml>$(ILLinkSharedDirectory)ILLink.Descriptors.LibraryBuild.xml</ILLinkDescriptorsLibraryBuildXml>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Internal\AssemblyAttributes.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Internal\Console.cs" />
|
||||
|
@ -2804,6 +2803,7 @@
|
|||
<ItemGroup Condition="'$(TargetsWasi)' == 'true'">
|
||||
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Wasi\WasiEventLoop.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Wasi\WasiPoll.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Wasi\WasiPollWorld.wit.imports.wasi.clocks.v0_2_1.MonotonicClockInterop.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Wasi\WasiPollWorld.wit.imports.wasi.io.v0_2_1.IPoll.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Wasi\WasiPollWorld.wit.imports.wasi.io.v0_2_1.PollInterop.cs" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Diagnostics;
|
|||
using System.Runtime;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace System.Threading
|
||||
{
|
||||
|
@ -12,15 +13,26 @@ namespace System.Threading
|
|||
{
|
||||
// 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 RegisterWasiPollable(int handle)
|
||||
internal static System.Threading.Tasks.Task RegisterWasiPollableHandle(int handle)
|
||||
{
|
||||
return WasiEventLoop.RegisterWasiPollable(handle);
|
||||
return WasiEventLoop.RegisterWasiPollableHandle(handle);
|
||||
}
|
||||
|
||||
internal static void DispatchWasiEventLoop()
|
||||
internal static int PollWasiEventLoopUntilResolved(Task<int> mainTask)
|
||||
{
|
||||
WasiEventLoop.DispatchWasiEventLoop();
|
||||
while (!mainTask.IsCompleted)
|
||||
{
|
||||
WasiEventLoop.DispatchWasiEventLoop();
|
||||
}
|
||||
var exception = mainTask.Exception;
|
||||
if (exception is not null)
|
||||
{
|
||||
throw exception;
|
||||
}
|
||||
|
||||
return mainTask.Result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// the closest analog to Sleep(0) on Unix is sched_yield
|
||||
|
|
|
@ -9,43 +9,64 @@ namespace System.Threading
|
|||
{
|
||||
internal static class WasiEventLoop
|
||||
{
|
||||
private static List<(IPoll.Pollable, TaskCompletionSource)> pollables = new();
|
||||
private static List<WeakReference<TaskCompletionSource>> s_pollables = new();
|
||||
|
||||
internal static Task RegisterWasiPollable(int handle)
|
||||
internal static Task RegisterWasiPollableHandle(int handle)
|
||||
{
|
||||
var source = new TaskCompletionSource(TaskCreationOptions.AttachedToParent);
|
||||
pollables.Add((new IPoll.Pollable(new IPoll.Pollable.THandle(handle)), source));
|
||||
return source.Task;
|
||||
// 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);
|
||||
}
|
||||
|
||||
internal static Task RegisterWasiPollable(IPoll.Pollable pollable)
|
||||
{
|
||||
var tcs = new TaskCompletionSource(pollable);
|
||||
var weakRef = new WeakReference<TaskCompletionSource>(tcs);
|
||||
s_pollables.Add(weakRef);
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
internal static void DispatchWasiEventLoop()
|
||||
{
|
||||
ThreadPoolWorkQueue.Dispatch();
|
||||
|
||||
if (WasiEventLoop.pollables.Count > 0)
|
||||
if (s_pollables.Count > 0)
|
||||
{
|
||||
var pollables = WasiEventLoop.pollables;
|
||||
WasiEventLoop.pollables = new();
|
||||
var arguments = new List<IPoll.Pollable>();
|
||||
var sources = new List<TaskCompletionSource>();
|
||||
foreach ((var pollable, var source) in pollables)
|
||||
var pollables = s_pollables;
|
||||
s_pollables = new List<WeakReference<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++)
|
||||
{
|
||||
arguments.Add(pollable);
|
||||
sources.Add(source);
|
||||
var weakRef = pollables[i];
|
||||
if (weakRef.TryGetTarget(out TaskCompletionSource? tcs))
|
||||
{
|
||||
var pollable = (IPoll.Pollable)tcs!.Task.AsyncState!;
|
||||
arguments.Add(pollable);
|
||||
indexes.Add(i);
|
||||
}
|
||||
}
|
||||
var results = PollInterop.Poll(arguments);
|
||||
|
||||
// this is blocking until at least one pollable resolves
|
||||
var readyIndexes = PollInterop.Poll(arguments);
|
||||
|
||||
var ready = new bool[arguments.Count];
|
||||
foreach (var result in results)
|
||||
foreach (int readyIndex in readyIndexes)
|
||||
{
|
||||
ready[result] = true;
|
||||
arguments[(int)result].Dispose();
|
||||
sources[(int)result].SetResult();
|
||||
ready[readyIndex] = true;
|
||||
arguments[readyIndex].Dispose();
|
||||
var weakRef = pollables[indexes[readyIndex]];
|
||||
if (weakRef.TryGetTarget(out TaskCompletionSource? tcs))
|
||||
{
|
||||
tcs!.SetResult();
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < arguments.Count; ++i)
|
||||
{
|
||||
if (!ready[i])
|
||||
{
|
||||
WasiEventLoop.pollables.Add((arguments[i], sources[i]));
|
||||
s_pollables.Add(pollables[indexes[i]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT!
|
||||
// <auto-generated />
|
||||
#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 WasiPollWorld.wit.imports.wasi.clocks.v0_2_1
|
||||
{
|
||||
internal static class MonotonicClockInterop {
|
||||
|
||||
internal static class NowWasmInterop
|
||||
{
|
||||
[DllImport("wasi:clocks/monotonic-clock@0.2.1", EntryPoint = "now"), WasmImportLinkage]
|
||||
internal static extern long wasmImportNow();
|
||||
|
||||
}
|
||||
|
||||
internal static unsafe ulong Now()
|
||||
{
|
||||
var result = NowWasmInterop.wasmImportNow();
|
||||
return unchecked((ulong)(result));
|
||||
|
||||
//TODO: free alloc handle (interopString) if exists
|
||||
}
|
||||
|
||||
internal static class ResolutionWasmInterop
|
||||
{
|
||||
[DllImport("wasi:clocks/monotonic-clock@0.2.1", EntryPoint = "resolution"), WasmImportLinkage]
|
||||
internal static extern long wasmImportResolution();
|
||||
|
||||
}
|
||||
|
||||
internal static unsafe ulong Resolution()
|
||||
{
|
||||
var result = ResolutionWasmInterop.wasmImportResolution();
|
||||
return unchecked((ulong)(result));
|
||||
|
||||
//TODO: free alloc handle (interopString) if exists
|
||||
}
|
||||
|
||||
internal static class SubscribeInstantWasmInterop
|
||||
{
|
||||
[DllImport("wasi:clocks/monotonic-clock@0.2.1", EntryPoint = "subscribe-instant"), WasmImportLinkage]
|
||||
internal static extern int wasmImportSubscribeInstant(long p0);
|
||||
|
||||
}
|
||||
|
||||
internal static unsafe global::WasiPollWorld.wit.imports.wasi.io.v0_2_1.IPoll.Pollable SubscribeInstant(ulong when)
|
||||
{
|
||||
var result = SubscribeInstantWasmInterop.wasmImportSubscribeInstant(unchecked((long)(when)));
|
||||
var resource = new global::WasiPollWorld.wit.imports.wasi.io.v0_2_1.IPoll.Pollable(new global::WasiPollWorld.wit.imports.wasi.io.v0_2_1.IPoll.Pollable.THandle(result));
|
||||
return resource;
|
||||
|
||||
//TODO: free alloc handle (interopString) if exists
|
||||
}
|
||||
|
||||
internal static class SubscribeDurationWasmInterop
|
||||
{
|
||||
[DllImport("wasi:clocks/monotonic-clock@0.2.1", EntryPoint = "subscribe-duration"), WasmImportLinkage]
|
||||
internal static extern int wasmImportSubscribeDuration(long p0);
|
||||
|
||||
}
|
||||
|
||||
internal static unsafe global::WasiPollWorld.wit.imports.wasi.io.v0_2_1.IPoll.Pollable SubscribeDuration(ulong when)
|
||||
{
|
||||
var result = SubscribeDurationWasmInterop.wasmImportSubscribeDuration(unchecked((long)(when)));
|
||||
var resource = new global::WasiPollWorld.wit.imports.wasi.io.v0_2_1.IPoll.Pollable(new global::WasiPollWorld.wit.imports.wasi.io.v0_2_1.IPoll.Pollable.THandle(result));
|
||||
return resource;
|
||||
|
||||
//TODO: free alloc handle (interopString) if exists
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ tar xzf v0.2.1.tar.gz
|
|||
cat >wasi-http-0.2.1/wit/world.wit <<EOF
|
||||
world wasi-poll {
|
||||
import wasi:io/poll@0.2.1;
|
||||
import wasi:clocks/monotonic-clock@0.2.1;
|
||||
}
|
||||
EOF
|
||||
wit-bindgen c-sharp -w wasi-poll -r native-aot --internal --skip-support-files wasi-http-0.2.1/wit
|
||||
|
|
|
@ -581,6 +581,7 @@
|
|||
<SmokeTestProject Include="$(MSBuildThisFileDirectory)System.Runtime\tests\System.Buffers.Tests\System.Buffers.Tests.csproj" />
|
||||
<SmokeTestProject Include="$(MSBuildThisFileDirectory)System.Runtime\tests\System.Globalization.Tests\Invariant\Invariant.Tests.csproj" />
|
||||
<SmokeTestProject Include="$(MSBuildThisFileDirectory)System.Runtime\tests\System.Globalization.Tests\System.Globalization.Tests.csproj" />
|
||||
<SmokeTestProject Include="$(MSBuildThisFileDirectory)System.Runtime\tests\System.Threading.Timer.Tests\System.Threading.Timer.Tests.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- wasi/aot smoke tests -->
|
||||
|
|
|
@ -9,6 +9,9 @@ using System.Runtime.InteropServices;
|
|||
|
||||
namespace System.Threading
|
||||
{
|
||||
#if FEATURE_WASM_MANAGED_THREADS
|
||||
#error when compiled with FEATURE_WASM_MANAGED_THREADS, we use TimerQueue.Portable.cs
|
||||
#endif
|
||||
//
|
||||
// Browser-specific implementation of Timer
|
||||
// Based on TimerQueue.Portable.cs
|
||||
|
|
|
@ -3,30 +3,155 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using WasiPollWorld.wit.imports.wasi.clocks.v0_2_1;
|
||||
|
||||
namespace System.Threading
|
||||
{
|
||||
#if FEATURE_WASM_MANAGED_THREADS
|
||||
#error when compiled with FEATURE_WASM_MANAGED_THREADS, we will use TimerQueue.Portable.cs
|
||||
#endif
|
||||
//
|
||||
// Wasi-specific implementation of Timer
|
||||
// Wasi implementation of Timer, single-threaded, on top of pollable and wasi:clocks
|
||||
// Based on TimerQueue.Portable.cs
|
||||
// Not thread safe
|
||||
//
|
||||
internal partial class TimerQueue
|
||||
internal sealed partial class TimerQueue
|
||||
{
|
||||
private static long TickCount64 => Environment.TickCount64;
|
||||
private static List<TimerQueue>? s_scheduledTimers;
|
||||
private static List<TimerQueue>? s_scheduledTimersToFire;
|
||||
private static long s_shortestDueTimeMs = long.MaxValue;
|
||||
|
||||
// this means that it's in the s_scheduledTimers collection, not that it's the one which would run on the next TimeoutCallback
|
||||
private bool _isScheduled;
|
||||
private long _scheduledDueTimeMs;
|
||||
private TimerQueue(int _)
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
#pragma warning disable CA1822 // Mark members as static
|
||||
|
||||
private static void TimerHandler(object _)
|
||||
{
|
||||
try
|
||||
{
|
||||
s_shortestDueTimeMs = long.MaxValue;
|
||||
|
||||
long currentTimeMs = TickCount64;
|
||||
SetNextTimer(PumpTimerQueue(currentTimeMs), currentTimeMs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Environment.FailFast("TimerQueue.TimerHandler failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
// this is called with shortest of timers scheduled on the particular TimerQueue
|
||||
private bool SetTimer(uint actualDuration)
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
Debug.Assert((int)actualDuration >= 0);
|
||||
long currentTimeMs = TickCount64;
|
||||
if (!_isScheduled)
|
||||
{
|
||||
s_scheduledTimers ??= new List<TimerQueue>(Instances.Length);
|
||||
s_scheduledTimersToFire ??= new List<TimerQueue>(Instances.Length);
|
||||
s_scheduledTimers.Add(this);
|
||||
_isScheduled = true;
|
||||
}
|
||||
|
||||
_scheduledDueTimeMs = currentTimeMs + (int)actualDuration;
|
||||
|
||||
SetNextTimer(ShortestDueTime(), currentTimeMs);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// shortest time of all TimerQueues
|
||||
private static unsafe void SetNextTimer(long shortestDueTimeMs, long currentTimeMs)
|
||||
{
|
||||
if (shortestDueTimeMs == long.MaxValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// this also covers s_shortestDueTimeMs = long.MaxValue when none is scheduled
|
||||
if (s_shortestDueTimeMs > shortestDueTimeMs)
|
||||
{
|
||||
s_shortestDueTimeMs = shortestDueTimeMs;
|
||||
ulong shortestWaitMs = (ulong)Math.Max((long)(shortestDueTimeMs - currentTimeMs), 0);
|
||||
|
||||
// `SubscribeDuration` expects nanoseconds:
|
||||
var pollable = MonotonicClockInterop.SubscribeDuration(shortestWaitMs * 1000 * 1000);
|
||||
Task task = WasiEventLoop.RegisterWasiPollable(pollable);
|
||||
task.ContinueWith(TimerHandler, TaskScheduler.Default);
|
||||
}
|
||||
}
|
||||
|
||||
private static long ShortestDueTime()
|
||||
{
|
||||
if (s_scheduledTimers == null)
|
||||
{
|
||||
return long.MaxValue;
|
||||
}
|
||||
|
||||
long shortestDueTimeMs = long.MaxValue;
|
||||
var timers = s_scheduledTimers!;
|
||||
for (int i = timers.Count - 1; i >= 0; --i)
|
||||
{
|
||||
TimerQueue timer = timers[i];
|
||||
if (timer._scheduledDueTimeMs < shortestDueTimeMs)
|
||||
{
|
||||
shortestDueTimeMs = timer._scheduledDueTimeMs;
|
||||
}
|
||||
}
|
||||
|
||||
return shortestDueTimeMs;
|
||||
}
|
||||
|
||||
private static long PumpTimerQueue(long currentTimeMs)
|
||||
{
|
||||
if (s_scheduledTimersToFire == null)
|
||||
{
|
||||
return ShortestDueTime();
|
||||
}
|
||||
|
||||
List<TimerQueue> timersToFire = s_scheduledTimersToFire!;
|
||||
List<TimerQueue> timers;
|
||||
timers = s_scheduledTimers!;
|
||||
long shortestDueTimeMs = long.MaxValue;
|
||||
for (int i = timers.Count - 1; i >= 0; --i)
|
||||
{
|
||||
TimerQueue timer = timers[i];
|
||||
long waitDurationMs = timer._scheduledDueTimeMs - currentTimeMs;
|
||||
if (waitDurationMs <= 0)
|
||||
{
|
||||
timer._isScheduled = false;
|
||||
timersToFire.Add(timer);
|
||||
|
||||
int lastIndex = timers.Count - 1;
|
||||
if (i != lastIndex)
|
||||
{
|
||||
timers[i] = timers[lastIndex];
|
||||
}
|
||||
timers.RemoveAt(lastIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (timer._scheduledDueTimeMs < shortestDueTimeMs)
|
||||
{
|
||||
shortestDueTimeMs = timer._scheduledDueTimeMs;
|
||||
}
|
||||
}
|
||||
|
||||
if (timersToFire.Count > 0)
|
||||
{
|
||||
foreach (TimerQueue timerToFire in timersToFire)
|
||||
{
|
||||
timerToFire.FireNextTimers();
|
||||
}
|
||||
timersToFire.Clear();
|
||||
}
|
||||
|
||||
return shortestDueTimeMs;
|
||||
}
|
||||
#pragma warning restore CA1822
|
||||
}
|
||||
}
|
||||
|
|
|
@ -346,11 +346,15 @@ JS_ENGINES = [NODE_JS]
|
|||
<Error Text="Expected version: %(_ExpectedVersionLines.Identity) and actual version: %(_ActualVersionLines.Identity) of WASI SDK does not match. Please delete $(WASI_SDK_PATH) folder to provision a new version."
|
||||
Condition="'$(ActualWasiSdkVersion)' != '$(ExpectedWasiSdkVersion)'" />
|
||||
|
||||
<!-- LLVM in WASI SDK 22 will call wasm-opt when found on the PATH. But it will fail because wasm-opt can't read the WASM components.
|
||||
After we upgrade to WASI SDK 23.x, we could use no-wasm-opt LLVM option to avoid this issue.
|
||||
<!-- LLVM in WASI SDK 24 will call wasm-opt when found on the PATH. But it will fail because wasm-opt can't read the WASM components.
|
||||
After we upgrade to WASI SDK with LLVM 19, we could use no-wasm-opt LLVM option to avoid this issue.
|
||||
See https://github.com/llvm/llvm-project/pull/98373
|
||||
See https://github.com/dotnet/runtime/issues/104773
|
||||
-->
|
||||
<Exec Command="wasm-opt --version" IgnoreExitCode="true" IgnoreStandardErrorWarningFormat="true" StandardOutputImportance="Low" >
|
||||
<Exec Command="wasm-opt --version" IgnoreExitCode="true"
|
||||
IgnoreStandardErrorWarningFormat="true"
|
||||
StandardErrorImportance="low"
|
||||
StandardOutputImportance="Low" >
|
||||
<Output TaskParameter="ExitCode" PropertyName="_WasmOptExitCode"/>
|
||||
</Exec>
|
||||
<Error Text="Found wasm-opt tool on the PATH. Please remove it to avoid failures during compilation into wasm32-wasip2 target as WASM components, which is not supported by wasm-opt tool. See https://github.com/llvm/llvm-project/pull/95208#issuecomment-2220400454"
|
||||
|
|
|
@ -8,50 +8,33 @@ using System.Threading.Tasks;
|
|||
using System.Threading;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
public class Test
|
||||
// keep in sync with src\mono\wasi\testassets\Http.cs
|
||||
public static class WasiMainWrapper
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
var task = Work();
|
||||
while (!task.IsCompleted)
|
||||
{
|
||||
WasiEventLoop.DispatchWasiEventLoop();
|
||||
}
|
||||
var exception = task.Exception;
|
||||
if (exception is not null)
|
||||
{
|
||||
throw exception;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static async Task Work()
|
||||
public static async Task<int> MainAsync(string[] args)
|
||||
{
|
||||
using HttpClient client = new();
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
client.DefaultRequestHeaders.Accept.Clear();
|
||||
client.DefaultRequestHeaders.Accept.Add(
|
||||
new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
|
||||
client.DefaultRequestHeaders.Add("User-Agent", ".NET Foundation Repository Reporter");
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "dotnet WASI unit test");
|
||||
|
||||
var query="https://api.github.com/orgs/dotnet/repos?per_page=1";
|
||||
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);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static class WasiEventLoop
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
internal static void DispatchWasiEventLoop()
|
||||
{
|
||||
CallDispatchWasiEventLoop((Thread)null!);
|
||||
return PollWasiEventLoopUntilResolved((Thread)null!, MainAsync(args));
|
||||
|
||||
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "DispatchWasiEventLoop")]
|
||||
static extern void CallDispatchWasiEventLoop(Thread t);
|
||||
}
|
||||
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "PollWasiEventLoopUntilResolved")]
|
||||
static extern int PollWasiEventLoopUntilResolved(Thread t, Task<int> mainTask);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -693,10 +693,14 @@ namespace Wasm.Build.Tests
|
|||
.MultiplyWithSingleArgs(true, false) /*aot*/
|
||||
.UnwrapItemsAsArrays();
|
||||
|
||||
protected CommandResult RunWithoutBuild(string config, string id)
|
||||
protected CommandResult RunWithoutBuild(string config, string id, bool enableHttp = false)
|
||||
{
|
||||
// wasmtime --wasi http is necessary because the default dotnet.wasm (without native rebuild depends on wasi:http world)
|
||||
string runArgs = $"run --no-build -c {config} --forward-exit-code --extra-host-arg=--wasi --extra-host-arg=http";
|
||||
string runArgs = $"run --no-build -c {config} --forward-exit-code";
|
||||
if (enableHttp)
|
||||
{
|
||||
runArgs += " --extra-host-arg=--wasi --extra-host-arg=http";
|
||||
}
|
||||
runArgs += " x y z";
|
||||
int expectedExitCode = 42;
|
||||
CommandResult res = new RunCommand(s_buildEnv, _testOutput, label: id)
|
||||
|
|
69
src/mono/wasi/Wasi.Build.Tests/HttpTests.cs
Normal file
69
src/mono/wasi/Wasi.Build.Tests/HttpTests.cs
Normal file
|
@ -0,0 +1,69 @@
|
|||
// 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.IO;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
using Wasm.Build.Tests;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Wasi.Build.Tests;
|
||||
|
||||
public class HttpTests : BuildTestBase
|
||||
{
|
||||
public HttpTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
|
||||
: base(output, buildContext)
|
||||
{
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TestDataForConsolePublishAndRun))]
|
||||
public void HttpBuildThenRunThenPublish(string config, bool singleFileBundle, bool aot)
|
||||
{
|
||||
string id = $"{config}_{GetRandomId()}";
|
||||
string projectFile = CreateWasmTemplateProject(id, "wasiconsole");
|
||||
string projectName = Path.GetFileNameWithoutExtension(projectFile);
|
||||
File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "Http.cs"), Path.Combine(_projectDir!, "Program.cs"), true);
|
||||
|
||||
var buildArgs = new BuildArgs(projectName, config, aot, id, null);
|
||||
buildArgs = ExpandBuildArgs(buildArgs);
|
||||
|
||||
string extraProperties = "<PublishTrimmed>true</PublishTrimmed>";
|
||||
if (aot)
|
||||
extraProperties += "<RunAOTCompilation>true</RunAOTCompilation><_WasmDevel>false</_WasmDevel>";
|
||||
if (singleFileBundle)
|
||||
extraProperties += "<WasmSingleFileBundle>true</WasmSingleFileBundle>";
|
||||
if (!string.IsNullOrEmpty(extraProperties))
|
||||
AddItemsPropertiesToProject(projectFile, extraProperties);
|
||||
|
||||
BuildProject(buildArgs,
|
||||
id: id,
|
||||
new BuildProjectOptions(
|
||||
DotnetWasmFromRuntimePack: true,
|
||||
CreateProject: false,
|
||||
Publish: false,
|
||||
TargetFramework: BuildTestBase.DefaultTargetFramework));
|
||||
RunWithoutBuild(config, id, true);
|
||||
|
||||
if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product))
|
||||
throw new XunitException($"Test bug: could not get the build product in the cache");
|
||||
|
||||
File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog"));
|
||||
|
||||
_testOutput.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}");
|
||||
|
||||
BuildProject(buildArgs,
|
||||
id: id,
|
||||
new BuildProjectOptions(
|
||||
DotnetWasmFromRuntimePack: true,
|
||||
CreateProject: false,
|
||||
Publish: true,
|
||||
TargetFramework: BuildTestBase.DefaultTargetFramework,
|
||||
UseCache: false,
|
||||
ExpectSuccess: !(config == "Debug" && aot)));
|
||||
}
|
||||
|
||||
}
|
|
@ -50,7 +50,7 @@ public class WasiTemplateTests : BuildTestBase
|
|||
CreateProject: false,
|
||||
Publish: false,
|
||||
TargetFramework: BuildTestBase.DefaultTargetFramework));
|
||||
RunWithoutBuild(config, id);
|
||||
RunWithoutBuild(config, id, true);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
@ -80,7 +80,7 @@ public class WasiTemplateTests : BuildTestBase
|
|||
CreateProject: false,
|
||||
Publish: false,
|
||||
TargetFramework: BuildTestBase.DefaultTargetFramework));
|
||||
RunWithoutBuild(config, id);
|
||||
RunWithoutBuild(config, id, true);
|
||||
|
||||
if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product))
|
||||
throw new XunitException($"Test bug: could not get the build product in the cache");
|
||||
|
|
43
src/mono/wasi/testassets/Http.cs
Normal file
43
src/mono/wasi/testassets/Http.cs
Normal file
|
@ -0,0 +1,43 @@
|
|||
// 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\sample\wasi\http-p2\Program.cs
|
||||
public static class WasiMainWrapper
|
||||
{
|
||||
public static async Task<int> MainAsync(string[] args)
|
||||
{
|
||||
Console.WriteLine("Hello, Wasi Console!");
|
||||
for (int i = 0; i < args.Length; i ++)
|
||||
Console.WriteLine($"args[{i}] = {args[i]}");
|
||||
|
||||
using HttpClient client = new();
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
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);
|
||||
|
||||
return 42;
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
return PollWasiEventLoopUntilResolved((Thread)null!, MainAsync(args));
|
||||
|
||||
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "PollWasiEventLoopUntilResolved")]
|
||||
static extern int PollWasiEventLoopUntilResolved(Thread t, Task<int> mainTask);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue