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"); + } } }