Skip to content

Commit

Permalink
[WASI] timers based on wasi:clocks (#105879)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelsavara authored Aug 8, 2024
1 parent 019d758 commit 7c0364e
Show file tree
Hide file tree
Showing 19 changed files with 474 additions and 89 deletions.
7 changes: 4 additions & 3 deletions eng/illink.targets
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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')" />

Expand Down Expand Up @@ -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:
Expand Down
21 changes: 20 additions & 1 deletion src/libraries/Common/tests/WasmTestRunner/WasmTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ ITypes.OutgoingRequest request
}
else
{
await WasiEventLoop.RegisterWasiPollable(future.Subscribe()).ConfigureAwait(false);
await RegisterWasiPollable(future.Subscribe()).ConfigureAwait(false);
}
}
}
Expand Down Expand Up @@ -461,19 +461,22 @@ private static async Task SendContentAsync(HttpContent? content, Stream stream)
}
}

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;
Expand Down Expand Up @@ -559,8 +562,7 @@ CancellationToken cancellationToken
var buffer = result;
if (buffer.Length == 0)
{
await WasiEventLoop
.RegisterWasiPollable(stream.Subscribe())
await RegisterWasiPollable(stream.Subscribe())
.ConfigureAwait(false);
}
else
Expand Down Expand Up @@ -697,7 +699,7 @@ CancellationToken cancellationToken
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)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,34 @@
using System.Runtime;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.Threading.Tasks;

namespace System.Threading
{
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 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]]);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/libraries/tests.proj
Original file line number Diff line number Diff line change
Expand Up @@ -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 -->
Expand Down
Loading

0 comments on commit 7c0364e

Please sign in to comment.