From 166b2ee8ebe13a34f9d943f20c0804cece86b87a Mon Sep 17 00:00:00 2001
From: SingleAccretion <62474226+SingleAccretion@users.noreply.github.com>
Date: Sun, 10 Mar 2024 22:04:58 +0300
Subject: [PATCH] [NativeAOT-LLVM] Implement timers on Browser (#2521)
* Implement timers on Browser
We can take a slightly different approach to avoid needing JS.
* Enable timer tests
* Revert "Enable timer tests"
Actually, we first need async main for the tests to yield properly.
---
.../src/System.Private.CoreLib.csproj | 3 +-
.../Threading/TimerQueue.Browser.Mono.cs | 24 ++++++++
.../SmokeTests/HelloWasm/HelloWasm.cs | 58 ++++++++++++++-----
3 files changed, 68 insertions(+), 17 deletions(-)
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj
index 8abac43ea471..10aa342162cf 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj
@@ -338,8 +338,7 @@
-
-
+
diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.Mono.cs
index dce59b97046f..9ee9981fbd4b 100644
--- a/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.Mono.cs
+++ b/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.Mono.cs
@@ -28,9 +28,33 @@ private TimerQueue(int _)
{
}
+#if NATIVEAOT
+ private static nuint s_lastScheduledHandlerId;
+
+ [UnmanagedCallersOnly]
+ private static unsafe void TimerHandlerWithId(void* arg)
+ {
+ // Turn all handlers scheduled before the last one into no-ops.
+ if ((nuint)arg != s_lastScheduledHandlerId)
+ {
+ return;
+ }
+
+ // TODO-LLVM-Upstream: remove this double thunking by modifying "TimerHandler" directly.
+ ((delegate* unmanaged[Cdecl])&TimerHandler)();
+ }
+
+ private static unsafe void MainThreadScheduleTimer(void* _, int shortestDueTimeMs)
+ {
+ [DllImport("*")]
+ static extern void emscripten_async_call(delegate* unmanaged func, void* arg, int millis);
+ emscripten_async_call(&TimerHandlerWithId, (void*)++s_lastScheduledHandlerId, shortestDueTimeMs);
+ }
+#else
// This replaces the current pending setTimeout with shorter one
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern unsafe void MainThreadScheduleTimer(void* callback, int shortestDueTimeMs);
+#endif
#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
diff --git a/src/tests/nativeaot/SmokeTests/HelloWasm/HelloWasm.cs b/src/tests/nativeaot/SmokeTests/HelloWasm/HelloWasm.cs
index 09cbd3b56bfd..2e7358e3ed77 100644
--- a/src/tests/nativeaot/SmokeTests/HelloWasm/HelloWasm.cs
+++ b/src/tests/nativeaot/SmokeTests/HelloWasm/HelloWasm.cs
@@ -4081,28 +4081,56 @@ class EventLoopTestClass
{
public static void TestEventLoopIntegration()
{
- async Task SumToTen()
+ Task.Run(async () =>
{
- const int Count = 10;
-
- int counter = 0;
- Task head = new Task(() => { });
- Task tail = head;
- for (int i = 0; i < Count; i++)
+ try
{
- tail = tail.ContinueWith((_) => counter++);
+ await TestEventLoopIntegrationImpl();
}
-
- head.Start();
- await tail;
- if (counter != Count)
+ catch (Exception e)
{
- // We have already returned the exit code by now, so indicate failure with a fail fast.
- Environment.FailFast("TestEventLoopIntegration failed");
+ Environment.FailFast($"Unhandled exception: {e}");
}
+ });
+ }
+
+ private static async Task TestEventLoopIntegrationImpl()
+ {
+ const int Count = 10;
+
+ int counter = 0;
+ Task head = new Task(() => { });
+ Task tail = head;
+ for (int i = 0; i < Count; i++)
+ {
+ tail = tail.ContinueWith((_) => counter++);
}
- Task.Run(SumToTen);
+ head.Start();
+ await tail;
+ EndEventLoopTest(counter == Count, "Event loop integration (thread pool)");
+
+ TimeSpan delay = TimeSpan.FromMilliseconds(25);
+ long startTime = Stopwatch.GetTimestamp();
+ using (PeriodicTimer timer = new PeriodicTimer(delay))
+ {
+ await timer.WaitForNextTickAsync();
+ }
+ TimeSpan elapsed = Stopwatch.GetElapsedTime(startTime);
+ EndEventLoopTest(elapsed >= delay, "Event loop integration (timers)");
+ }
+
+ private static void EndEventLoopTest(bool success, string test)
+ {
+ if (success)
+ {
+ Program.PrintLine($"{test}: ok");
+ }
+ else
+ {
+ // We have already returned the exit code by now, so indicate failure with a fail fast.
+ Environment.FailFast($"{test} failed");
+ }
}
}