Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wasm): Add support for threads #8998

Merged
merged 2 commits into from
Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build/ci/.azure-devops-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ jobs:
msbuildArguments: /r /p:CheckExclusions=True /p:Configuration=Release /nodeReuse:true /detailedsummary /m $(ADDITIONAL_FLAGS) # /bl:$(build.artifactstagingdirectory)\build.binlog

- task: VisualStudioTestPlatformInstaller@1
inputs:
versionSelector: latestStable

- task: VSTest@2
inputs:
Expand Down
1 change: 1 addition & 0 deletions src/Uno.Foundation.Runtime.WebAssembly/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using global::System.Runtime.InteropServices;

[assembly: InternalsVisibleTo("Uno.UI")]
[assembly: InternalsVisibleTo("Uno.UI.Dispatching")]
[assembly: InternalsVisibleTo("Uno")]
[assembly: InternalsVisibleTo("Uno.Foundation")]
[assembly: InternalsVisibleTo("Uno.UI.Wasm")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ internal static class InternalCalls
[MethodImpl(MethodImplOptions.InternalCall)]
[EditorBrowsable(EditorBrowsableState.Never)]
public static extern IntPtr InvokeJSUnmarshalled(out string exceptionMessage, string functionIdentifier, IntPtr arg0, IntPtr arg1, IntPtr arg2);

// Uno-Specific implementation for https://github.com/dotnet/runtime/issues/69409.
// To be removed when the runtime will support the main SynchronizationContext.
[MethodImplAttribute(MethodImplOptions.InternalCall)]
public static extern void InvokeOnMainThread();
}
}
}
50 changes: 29 additions & 21 deletions src/Uno.UI.Dispatching/Core/CoreDispatcher.wasm.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Uno.Foundation;
using Uno.Foundation.Interop;
using Uno.Foundation.Logging;

namespace Uno.UI.Dispatching
{
internal sealed partial class CoreDispatcher
{
internal bool IsThreadingSupported { get; } = Environment.GetEnvironmentVariable("UNO_BOOTSTRAP_MONO_RUNTIME_CONFIGURATION").StartsWith("threads", StringComparison.OrdinalIgnoreCase);

private Timer _backgroundWakeupTimer;
internal bool IsThreadingSupported { get; }
= Environment.GetEnvironmentVariable("UNO_BOOTSTRAP_MONO_RUNTIME_FEATURES")
?.Split(',').Any(v => v.Equals("threads", StringComparison.OrdinalIgnoreCase)) ?? false;

/// <summary>
/// Method invoked from
/// </summary>
private static int DispatcherCallback()
{
if (typeof(CoreDispatcher).Log().IsEnabled(LogLevel.Trace))
{
typeof(CoreDispatcher).Log().Trace($"[tid:{Thread.CurrentThread.ManagedThreadId}]: CoreDispatcher.DispatcherCallback()");
}

Main.DispatchItems();
return 0; // Required by bind_static_method (void is not supported)
}
Expand All @@ -32,44 +39,45 @@ private static int DispatcherCallback()

partial void Initialize()
{
if (IsThreadingSupported)
if (typeof(CoreDispatcher).Log().IsEnabled(LogLevel.Trace))
{
if(Thread.CurrentThread.ManagedThreadId != 1)
{
throw new InvalidOperationException($"CoreDispatcher must be initialized on the main Javascript thread");
}
typeof(CoreDispatcher).Log().Trace($"[tid:{Thread.CurrentThread.ManagedThreadId}]: CoreDispatcher.Initialize IsThreadingSupported:{IsThreadingSupported}");
}

_backgroundWakeupTimer = new Timer(_ => Main.DispatchItems());
_backgroundWakeupTimer.Change(0, 50);
if (IsThreadingSupported && Thread.CurrentThread.ManagedThreadId != 1)
{
throw new InvalidOperationException($"CoreDispatcher must be initialized on the main Javascript thread");
}
}

// Always reschedule, otherwise we may end up in live-lock.
public static bool HasThreadAccessOverride { get; set; } = false;

private bool GetHasThreadAccess()
=> IsThreadingSupported ? Thread.CurrentThread.ManagedThreadId == 1 : HasThreadAccessOverride;

partial void EnqueueNative()
{
if (IsThreadingSupported)
{
return Thread.CurrentThread.ManagedThreadId == 1;
}
else
if (typeof(CoreDispatcher).Log().IsEnabled(LogLevel.Trace))
{
return HasThreadAccessOverride;
typeof(CoreDispatcher).Log().Trace($"[tid:{Thread.CurrentThread.ManagedThreadId}]: CoreDispatcher.EnqueueNative()");
}
}

partial void EnqueueNative()
{
if (DispatchOverride == null)
{
if (!IsThreadingSupported)
if (!IsThreadingSupported || (IsThreadingSupported && GetHasThreadAccess()))
{
WebAssemblyRuntime.InvokeJSUnmarshalled("CoreDispatcher:WakeUp", IntPtr.Zero);
}
else
{
// The _backgroundWakeupTimer will do the dispatching.
// This is a separate function to avoid enclosing early resolution
// by the interpreter/JIT, in case we're running the non-threading
// enabled runtime.
static void InvokeOnMainThread()
=> WebAssembly.JSInterop.InternalCalls.InvokeOnMainThread();

InvokeOnMainThread();
}
}
else
Expand Down
12 changes: 12 additions & 0 deletions src/Uno.UI.RemoteControl.Host/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ public void Configure(IApplicationBuilder app, IOptionsMonitor<RemoteControlOpti
.UseDeveloperExceptionPage()
.UseWebSockets()
.UseRemoteControlServer(options);

app.Use(async (context, next) =>
{
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
context.Response.Headers.Add("Access-Control-Allow-Methods", "*");
context.Response.Headers.Add("Access-Control-Allow-Headers", "*");

// Required for SharedArrayBuffer: https://developer.chrome.com/blog/enabling-shared-array-buffer/
context.Response.Headers.Add("Cross-Origin-Embedder-Policy", "require-corp");
context.Response.Headers.Add("Cross-Origin-Opener-Policy", "same-origin");
await next();
});
}
}
}
2 changes: 2 additions & 0 deletions src/Uno.UI/WasmScripts/Uno.UI.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ declare namespace MonoSupport {
*/
private static cacheMethod;
private static getMethodMapId;
private static dispatcherCallback;
static invokeOnMainThread(): void;
}
}
declare const config: any;
Expand Down
15 changes: 15 additions & 0 deletions src/Uno.UI/WasmScripts/Uno.UI.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,13 +312,28 @@ var MonoSupport;
static getMethodMapId(methodHandle) {
return methodHandle + "";
}
static invokeOnMainThread() {
if (!jsCallDispatcher.dispatcherCallback) {
jsCallDispatcher.dispatcherCallback = Module.mono_bind_static_method("[Uno.UI.Dispatching] Uno.UI.Dispatching.CoreDispatcher:DispatcherCallback");
}
window.setImmediate(() => {
try {
jsCallDispatcher.dispatcherCallback();
}
catch (e) {
console.error(`Unhandled dispatcher exception: ${e} (${e.stack})`);
throw e;
}
});
}
}
jsCallDispatcher.registrations = new Map();
jsCallDispatcher.methodMap = {};
MonoSupport.jsCallDispatcher = jsCallDispatcher;
})(MonoSupport || (MonoSupport = {}));
// Export the DotNet helper for WebAssembly.JSInterop.InvokeJSUnmarshalled
window.DotNet = MonoSupport;
MonoSupport.invokeOnMainThread = MonoSupport.jsCallDispatcher.invokeOnMainThread;
// eslint-disable-next-line @typescript-eslint/no-namespace
var Uno;
(function (Uno) {
Expand Down
24 changes: 24 additions & 0 deletions src/Uno.UI/ts/MonoSupport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace MonoSupport {
private static registrations: Map<string, any> = new Map<string, any>();
private static methodMap: { [id: string]: any } = {};
private static _isUnoRegistered : boolean;
private static dispatcherCallback: () => void;

/**
* Registers a instance for a specified identier
Expand Down Expand Up @@ -88,8 +89,31 @@ namespace MonoSupport {
private static getMethodMapId(methodHandle: number) {
return methodHandle + "";
}

public static invokeOnMainThread() {

if (!jsCallDispatcher.dispatcherCallback) {
jsCallDispatcher.dispatcherCallback = (<any>Module).mono_bind_static_method(
"[Uno.UI.Dispatching] Uno.UI.Dispatching.CoreDispatcher:DispatcherCallback");
}

// Use setImmediate to return avoid blocking the background thread
// on a sync call.
(<any>window).setImmediate(() => {
try {
jsCallDispatcher.dispatcherCallback();
}
catch (e) {
console.error(`Unhandled dispatcher exception: ${e} (${e.stack})`);
throw e;
}
});
}
}
}

// Export the DotNet helper for WebAssembly.JSInterop.InvokeJSUnmarshalled
(<any>window).DotNet = MonoSupport;

// Export the main thread invoker for threading support
(<any>MonoSupport).invokeOnMainThread = MonoSupport.jsCallDispatcher.invokeOnMainThread;