Skip to content

Commit

Permalink
[NativeAOT-LLVM] Implement timers on Browser (#2521)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
SingleAccretion committed Mar 10, 2024
1 parent da0c051 commit 166b2ee
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,7 @@
<Compile Include="$(MonoBclSourcesRoot)\System\Threading\ThreadPoolBoundHandle.Browser.Mono.cs" />
<Compile Include="$(MonoBclSourcesRoot)\System\Threading\PreAllocatedOverlapped.Browser.Mono.cs" />
<Compile Include="$(MonoBclSourcesRoot)\System\Threading\ThreadPool.Browser.Mono.cs" />
<!-- TODO-LLVM: the timer queue is a little more dependent on TS, take the PNSE version for now. -->
<Compile Include="$(MonoBclSourcesRoot)\System\Threading\TimerQueue.Wasi.Mono.cs" />
<Compile Include="$(MonoBclSourcesRoot)\System\Threading\TimerQueue.Browser.Mono.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsWasi)' == 'true' and '$(FeatureWasmThreads)' != 'true'">
<Compile Include="$(MonoBclSourcesRoot)\System\Threading\ThreadPoolBoundHandle.Browser.Mono.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]<void>)&TimerHandler)();
}

private static unsafe void MainThreadScheduleTimer(void* _, int shortestDueTimeMs)
{
[DllImport("*")]
static extern void emscripten_async_call(delegate* unmanaged<void*, void> 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) })]
Expand Down
58 changes: 43 additions & 15 deletions src/tests/nativeaot/SmokeTests/HelloWasm/HelloWasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
}

Expand Down

0 comments on commit 166b2ee

Please sign in to comment.