</ItemGroup>
<ItemGroup Condition="$(_subset.Contains('+mono.wasmruntime+'))">
+ <ProjectToBuild Include="$(LibrariesProjectRoot)\System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" Category="mono" />
<ProjectToBuild Include="$(MonoProjectRoot)wasm\wasm.proj" Category="mono" />
</ItemGroup>
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void DeregisterGCRoot(IntPtr handle);
+#if FEATURE_WASM_THREADS
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ public static extern void InstallWebWorkerInterop(bool installJSSynchronizationContext);
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ public static extern void UninstallWebWorkerInterop(bool uninstallJSSynchronizationContext);
+#endif
+
#region Legacy
[MethodImplAttribute(MethodImplOptions.InternalCall)]
<Compile Include="System\Runtime\InteropServices\JavaScript\Marshaling\JSMarshalerArgument.JSObject.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Marshaling\JSMarshalerArgument.String.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Marshaling\JSMarshalerArgument.Exception.cs" />
-
- <Compile Include="System\Runtime\InteropServices\JavaScript\JSSynchronizationContext.cs" />
</ItemGroup>
<!-- only include legacy interop when WasmEnableLegacyJsInterop is enabled -->
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\LegacyHostImplementation.cs" />
</ItemGroup>
+ <!-- only include threads support when FeatureWasmThreads is enabled -->
+ <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'browser' and '$(FeatureWasmThreads)' == 'true'">
+ <Compile Include="System\Runtime\InteropServices\JavaScript\WebWorker.cs" />
+ <Compile Include="System\Runtime\InteropServices\JavaScript\JSSynchronizationContext.cs" />
+ </ItemGroup>
+
<ItemGroup>
<Reference Include="System.Collections" />
<Reference Include="System.Memory" />
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
+using System.Diagnostics.CodeAnalysis;
namespace System.Runtime.InteropServices.JavaScript
{
// the marshaled signature is:
// void InstallSynchronizationContext()
+ [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.WebWorker", "System.Runtime.InteropServices.JavaScript")]
public static void InstallSynchronizationContext (JSMarshalerArgument* arguments_buffer) {
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
try
{
- JSSynchronizationContext.Install();
+ JSHostImplementation.InstallWebWorkerInterop(true);
}
catch (Exception ex)
{
{
get
{
- s_csOwnedObjects ??= new ();
+ s_csOwnedObjects ??= new();
return s_csOwnedObjects;
}
}
}
return res;
}
+
+#if FEATURE_WASM_THREADS
+ public static void InstallWebWorkerInterop(bool installJSSynchronizationContext)
+ {
+ Interop.Runtime.InstallWebWorkerInterop(installJSSynchronizationContext);
+ if (installJSSynchronizationContext)
+ {
+ var currentThreadId = GetNativeThreadId();
+ var ctx = JSSynchronizationContext.CurrentJSSynchronizationContext;
+ if (ctx == null)
+ {
+ ctx = new JSSynchronizationContext(Thread.CurrentThread, currentThreadId);
+ ctx.previousSynchronizationContext = SynchronizationContext.Current;
+ JSSynchronizationContext.CurrentJSSynchronizationContext = ctx;
+ SynchronizationContext.SetSynchronizationContext(ctx);
+ }
+ else if (ctx.TargetThreadId != currentThreadId)
+ {
+ Environment.FailFast($"JSSynchronizationContext.Install failed has wrong native thread id {ctx.TargetThreadId} != {currentThreadId}");
+ }
+ ctx.AwaitNewData();
+ }
+ }
+
+ public static void UninstallWebWorkerInterop()
+ {
+ var ctx = SynchronizationContext.Current as JSSynchronizationContext;
+ var uninstallJSSynchronizationContext = ctx != null;
+ if (uninstallJSSynchronizationContext)
+ {
+ SynchronizationContext.SetSynchronizationContext(ctx!.previousSynchronizationContext);
+ JSSynchronizationContext.CurrentJSSynchronizationContext = null;
+ ctx.isDisposed = true;
+ }
+ Interop.Runtime.UninstallWebWorkerInterop(uninstallJSSynchronizationContext);
+ }
+
+ private static FieldInfo? thread_id_Field;
+ private static FieldInfo? external_eventloop_Field;
+
+ // FIXME: after https://github.com/dotnet/runtime/issues/86040 replace with
+ // [UnsafeAccessor(UnsafeAccessorKind.Field, Name="external_eventloop")]
+ // static extern ref bool ThreadExternalEventloop(Thread @this);
+ [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "System.Threading.Thread", "System.Private.CoreLib")]
+ public static void SetHasExternalEventLoop(Thread thread)
+ {
+ if (external_eventloop_Field == null)
+ {
+ external_eventloop_Field = typeof(Thread).GetField("external_eventloop", BindingFlags.NonPublic | BindingFlags.Instance)!;
+ }
+ external_eventloop_Field.SetValue(thread, true);
+ }
+
+ // FIXME: after https://github.com/dotnet/runtime/issues/86040
+ [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicFields, "System.Threading.Thread", "System.Private.CoreLib")]
+ public static IntPtr GetNativeThreadId()
+ {
+ if (thread_id_Field == null)
+ {
+ thread_id_Field = typeof(Thread).GetField("thread_id", BindingFlags.NonPublic | BindingFlags.Instance)!;
+ }
+ return (int)(long)thread_id_Field.GetValue(Thread.CurrentThread)!;
+ }
+
+#endif
+
}
}
#if FEATURE_WASM_THREADS
-using System;
using System.Threading;
using System.Threading.Channels;
-using System.Runtime;
-using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
-using QueueType = System.Threading.Channels.Channel<System.Runtime.InteropServices.JavaScript.JSSynchronizationContext.WorkItem>;
+using WorkItemQueueType = System.Threading.Channels.Channel<System.Runtime.InteropServices.JavaScript.JSSynchronizationContext.WorkItem>;
namespace System.Runtime.InteropServices.JavaScript
{
/// <summary>
/// Provides a thread-safe default SynchronizationContext for the browser that will automatically
- /// route callbacks to the main browser thread where they can interact with the DOM and other
+ /// route callbacks to the original browser thread where they can interact with the DOM and other
/// thread-affinity-having APIs like WebSockets, fetch, WebGL, etc.
/// Callbacks are processed during event loop turns via the runtime's background job system.
+ /// See also https://github.com/dotnet/runtime/blob/main/src/mono/wasm/threads.md#JS-interop-on-dedicated-threads
/// </summary>
internal sealed class JSSynchronizationContext : SynchronizationContext
{
- public readonly Thread MainThread;
+ private readonly Action _DataIsAvailable;// don't allocate Action on each call to UnsafeOnCompleted
+ public readonly Thread TargetThread;
+ public readonly IntPtr TargetThreadId;
+ private readonly WorkItemQueueType Queue;
+
+ [ThreadStatic]
+ internal static JSSynchronizationContext? CurrentJSSynchronizationContext;
+ internal SynchronizationContext? previousSynchronizationContext;
+ internal bool isDisposed;
internal readonly struct WorkItem
{
}
}
- private static JSSynchronizationContext? MainThreadSynchronizationContext;
- private readonly QueueType Queue;
- private readonly Action _DataIsAvailable;// don't allocate Action on each call to UnsafeOnCompleted
-
- private JSSynchronizationContext()
+ internal JSSynchronizationContext(Thread targetThread, IntPtr targetThreadId)
: this(
- Thread.CurrentThread,
+ targetThread, targetThreadId,
Channel.CreateUnbounded<WorkItem>(
new UnboundedChannelOptions { SingleWriter = false, SingleReader = true, AllowSynchronousContinuations = true }
)
{
}
- private JSSynchronizationContext(Thread mainThread, QueueType queue)
+ private JSSynchronizationContext(Thread targetThread, IntPtr targetThreadId, WorkItemQueueType queue)
{
- MainThread = mainThread;
+ TargetThread = targetThread;
+ TargetThreadId = targetThreadId;
Queue = queue;
_DataIsAvailable = DataIsAvailable;
}
public override SynchronizationContext CreateCopy()
{
- return new JSSynchronizationContext(MainThread, Queue);
+ return new JSSynchronizationContext(TargetThread, TargetThreadId, Queue);
}
- private void AwaitNewData()
+ internal void AwaitNewData()
{
+ ObjectDisposedException.ThrowIf(isDisposed, this);
+
var vt = Queue.Reader.WaitToReadAsync();
if (vt.IsCompleted)
{
{
// While we COULD pump here, we don't want to. We want the pump to happen on the next event loop turn.
// Otherwise we could get a chain where a pump generates a new work item and that makes us pump again, forever.
- MainThreadScheduleBackgroundJob((void*)(delegate* unmanaged[Cdecl]<void>)&BackgroundJobHandler);
+ TargetThreadScheduleBackgroundJob(TargetThreadId, (void*)(delegate* unmanaged[Cdecl]<void>)&BackgroundJobHandler);
}
public override void Post(SendOrPostCallback d, object? state)
{
+ ObjectDisposedException.ThrowIf(isDisposed, this);
+
var workItem = new WorkItem(d, state, null);
if (!Queue.Writer.TryWrite(workItem))
throw new Exception("Internal error");
public override void Send(SendOrPostCallback d, object? state)
{
- if (Thread.CurrentThread == MainThread)
+ ObjectDisposedException.ThrowIf(isDisposed, this);
+
+ if (Thread.CurrentThread == TargetThread)
{
d(state);
return;
}
}
- internal static void Install()
- {
- MainThreadSynchronizationContext ??= new JSSynchronizationContext();
- SynchronizationContext.SetSynchronizationContext(MainThreadSynchronizationContext);
- MainThreadSynchronizationContext.AwaitNewData();
- }
-
[MethodImplAttribute(MethodImplOptions.InternalCall)]
- internal static extern unsafe void MainThreadScheduleBackgroundJob(void* callback);
+ internal static extern unsafe void TargetThreadScheduleBackgroundJob(IntPtr targetThread, void* callback);
#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
#pragma warning restore CS3016
- // this callback will arrive on the bound thread, called from mono_background_exec
+ // this callback will arrive on the target thread, called from mono_background_exec
private static void BackgroundJobHandler()
{
- MainThreadSynchronizationContext!.Pump();
+ CurrentJSSynchronizationContext!.Pump();
}
private void Pump()
{
+ if (isDisposed)
+ {
+ // FIXME: there could be abandoned work, but here we have no way how to propagate the failure
+ return;
+ }
try
{
while (Queue.Reader.TryRead(out var item))
finally
{
// If an item throws, we want to ensure that the next pump gets scheduled appropriately regardless.
- AwaitNewData();
+ if(!isDisposed) AwaitNewData();
}
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if FEATURE_WASM_THREADS
+
+#pragma warning disable CA1416
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Runtime.InteropServices.JavaScript
+{
+ /// <summary>
+ /// This is draft for possible public API of browser thread (web worker) dedicated to JS interop workloads.
+ /// The method names are unique to make it easy to call them via reflection for now. All of them should be just `RunAsync` probably.
+ /// </summary>
+ internal static class WebWorker
+ {
+ public static Task<T> RunAsync<T>(Func<Task<T>> body, CancellationToken cancellationToken)
+ {
+ var parentContext = SynchronizationContext.Current ?? new SynchronizationContext();
+ var tcs = new TaskCompletionSource<T>();
+ var capturedContext = SynchronizationContext.Current;
+ var t = new Thread(() =>
+ {
+ try
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ PostWhenCancellation(parentContext, tcs);
+ return;
+ }
+
+ JSHostImplementation.InstallWebWorkerInterop(true);
+ var childScheduler = TaskScheduler.FromCurrentSynchronizationContext();
+ Task<T> res = body();
+ // This code is exiting thread main() before all promises are resolved.
+ // the continuation is executed by setTimeout() callback of the thread.
+ res.ContinueWith(t =>
+ {
+ PostWhenDone(parentContext, tcs, res);
+ JSHostImplementation.UninstallWebWorkerInterop();
+ }, childScheduler);
+ }
+ catch (Exception e)
+ {
+ Environment.FailFast("WebWorker.RunAsync failed", e);
+ }
+
+ });
+ JSHostImplementation.SetHasExternalEventLoop(t);
+ t.Start();
+ return tcs.Task;
+ }
+
+ public static Task RunAsyncVoid(Func<Task> body, CancellationToken cancellationToken)
+ {
+ var parentContext = SynchronizationContext.Current ?? new SynchronizationContext();
+ var tcs = new TaskCompletionSource();
+ var capturedContext = SynchronizationContext.Current;
+ var t = new Thread(() =>
+ {
+ try
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ PostWhenCancellation(parentContext, tcs);
+ return;
+ }
+
+ JSHostImplementation.InstallWebWorkerInterop(true);
+ var childScheduler = TaskScheduler.FromCurrentSynchronizationContext();
+ Task res = body();
+ // This code is exiting thread main() before all promises are resolved.
+ // the continuation is executed by setTimeout() callback of the thread.
+ res.ContinueWith(t =>
+ {
+ PostWhenDone(parentContext, tcs, res);
+ JSHostImplementation.UninstallWebWorkerInterop();
+ }, childScheduler);
+ }
+ catch (Exception e)
+ {
+ Environment.FailFast("WebWorker.RunAsync failed", e);
+ }
+
+ });
+ JSHostImplementation.SetHasExternalEventLoop(t);
+ t.Start();
+ return tcs.Task;
+ }
+
+ public static Task Run(Action body, CancellationToken cancellationToken)
+ {
+ var parentContext = SynchronizationContext.Current ?? new SynchronizationContext();
+ var tcs = new TaskCompletionSource();
+ var capturedContext = SynchronizationContext.Current;
+ var t = new Thread(() =>
+ {
+ try
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ PostWhenCancellation(parentContext, tcs);
+ return;
+ }
+
+ JSHostImplementation.InstallWebWorkerInterop(false);
+ try
+ {
+ body();
+ PostWhenDone(parentContext, tcs);
+ }
+ catch (Exception ex)
+ {
+ PostWhenException(parentContext, tcs, ex);
+ }
+ JSHostImplementation.UninstallWebWorkerInterop();
+ }
+ catch (Exception e)
+ {
+ tcs.SetException(e);
+ }
+
+ });
+ JSHostImplementation.SetHasExternalEventLoop(t);
+ t.Start();
+ return tcs.Task;
+ }
+
+ #region posting result to the original thread when handling exception
+
+ private static void PostWhenCancellation(SynchronizationContext ctx, TaskCompletionSource tcs)
+ {
+ try
+ {
+ ctx.Post((_) => tcs.SetCanceled(), null);
+ }
+ catch (Exception e)
+ {
+ Environment.FailFast("WebWorker.RunAsync failed", e);
+ }
+ }
+
+ private static void PostWhenCancellation<T>(SynchronizationContext ctx, TaskCompletionSource<T> tcs)
+ {
+ try
+ {
+ ctx.Post((_) => tcs.SetCanceled(), null);
+ }
+ catch (Exception e)
+ {
+ Environment.FailFast("WebWorker.RunAsync failed", e);
+ }
+ }
+
+ private static void PostWhenDone(SynchronizationContext ctx, TaskCompletionSource tcs, Task done)
+ {
+ try
+ {
+ ctx.Post((_) =>
+ {
+ if (done.IsFaulted)
+ tcs.SetException(done.Exception);
+ else if (done.IsCanceled)
+ tcs.SetCanceled();
+ else
+ tcs.SetResult();
+
+ }, null);
+ }
+ catch (Exception e)
+ {
+ Environment.FailFast("WebWorker.RunAsync failed", e);
+ }
+ }
+
+ private static void PostWhenDone(SynchronizationContext ctx, TaskCompletionSource tcs)
+ {
+ try
+ {
+ ctx.Post((_) => tcs.SetResult(), null);
+ }
+ catch (Exception e)
+ {
+ Environment.FailFast("WebWorker.RunAsync failed", e);
+ }
+ }
+
+ private static void PostWhenException(SynchronizationContext ctx, TaskCompletionSource tcs, Exception ex)
+ {
+ try
+ {
+ ctx.Post((_) => tcs.SetException(ex), null);
+ }
+ catch (Exception e)
+ {
+ Environment.FailFast("WebWorker.RunAsync failed", e);
+ }
+ }
+
+ private static void PostWhenDone<T>(SynchronizationContext ctx, TaskCompletionSource<T> tcs, Task<T> done)
+ {
+ try
+ {
+ ctx.Post((_) =>
+ {
+ if (done.IsFaulted)
+ tcs.SetException(done.Exception);
+ else if (done.IsCanceled)
+ tcs.SetCanceled();
+ else
+ tcs.SetResult(done.Result);
+
+ }, null);
+ }
+ catch (Exception e)
+ {
+ Environment.FailFast("WebWorker.RunAsync failed", e);
+ }
+ }
+
+ #endregion
+
+ }
+}
+
+#endif
}
export function install() {
+ const Module = globalThis.App.runtime.Module;
const measuredCallbackName = "mono_wasm_schedule_timer_tick";
globalThis.registerCount = 0;
globalThis.hitCount = 0;
log("install")
if (!globalThis.originalSetTimeout) {
- globalThis.originalSetTimeout = globalThis.setTimeout;
+ globalThis.originalSetTimeout = Module.safeSetTimeout;
}
- globalThis.setTimeout = (cb, time) => {
+
+ Module.safeSetTimeout = (cb, time) => {
var start = Date.now().valueOf();
if (cb.name === measuredCallbackName) {
globalThis.registerCount++;
globalThis.hitCount++;
log(`hitCount: ${globalThis.hitCount} now:${hit} delay:${time} delta:${hit - start}`)
}
- cb();
+ return cb();
}, time);
};
}
export function cleanup() {
log(`cleanup registerCount: ${globalThis.registerCount} hitCount: ${globalThis.hitCount} `)
- globalThis.setTimeout = globalThis.originalSetTimeout;
+ const Module = globalThis.App.runtime.Module;
+ Module.safeSetTimeout = globalThis.originalSetTimeout;
}
private ThreadState state;
private object? abort_exc;
private int abort_state_handle;
- /* thread_id is only accessed from unmanaged code */
internal long thread_id;
private IntPtr debugger_thread; // FIXME switch to bool as soon as CI testing with corlib version bump works
private UIntPtr static_data; /* GC-tracked */
namespace System.Threading
{
-#if !FEATURE_WASM_THREADS
- [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
+#if FEATURE_WASM_THREADS
+#error when compiled with FEATURE_WASM_THREADS, we use PortableThreadPool.WorkerThread.Browser.Threads.Mono.cs
#endif
+ [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public sealed class RegisteredWaitHandle : MarshalByRefObject
{
internal RegisteredWaitHandle()
#include <mono/utils/mono-error-internals.h>
#include <mono/utils/os-event.h>
#include <mono/utils/mono-threads-debug.h>
+#include <mono/utils/mono-threads-wasm.h>
#include <mono/utils/unlocked.h>
#include <mono/utils/ftnptr.h>
#include <mono/metadata/w32handle.h>
/* Don't need to close the handle to this thread, even though we took a
* reference in mono_thread_attach (), because the GC will do it
- * when the Thread object is finalised.
+ * when the Thread object is finalized.
*/
}
/* if the thread wants to stay alive, don't clean up after it */
if (mono_thread_platform_external_eventloop_keepalive_check ()) {
/* while we wait in the external eventloop, we're GC safe */
- MONO_REQ_GC_SAFE_MODE;
+ MONO_ENTER_GC_SAFE_UNBALANCED;
return 0;
}
}
void
mono_wasm_main_thread_schedule_timer (void *timerHandler, int shortestDueTimeMs)
{
+ // NOTE: here the `timerHandler` callback is [UnmanagedCallersOnly] which wraps it with MONO_ENTER_GC_UNSAFE/MONO_EXIT_GC_UNSAFE
+
g_assert (timerHandler);
timer_handler = timerHandler;
#ifdef HOST_BROWSER
{
#ifdef HOST_BROWSER
mono_add_internal_call_internal ("System.Threading.TimerQueue::MainThreadScheduleTimer", mono_wasm_main_thread_schedule_timer);
+#ifdef DISABLE_THREADS
mono_add_internal_call_internal ("System.Threading.ThreadPool::MainThreadScheduleBackgroundJob", mono_main_thread_schedule_background_job);
-#ifndef DISABLE_THREADS
- mono_add_internal_call_internal ("System.Runtime.InteropServices.JavaScript.JSSynchronizationContext::MainThreadScheduleBackgroundJob", mono_main_thread_schedule_background_job);
+#else
+ mono_add_internal_call_internal ("System.Runtime.InteropServices.JavaScript.JSSynchronizationContext::TargetThreadScheduleBackgroundJob", mono_target_thread_schedule_background_job);
#endif /* DISABLE_THREADS */
#endif /* HOST_BROWSER */
}
gboolean call_timeout_cb = FALSE;
LifoSemaphoreAsyncWaitCallbackFn timeout_cb = NULL;
intptr_t user_data = 0;
+ MONO_ENTER_GC_UNSAFE;
mono_coop_mutex_lock (&sem->base.mutex);
switch (wait_entry->state) {
case LIFO_JS_WAITING:
if (call_timeout_cb) {
timeout_cb (sem, user_data);
}
+ MONO_EXIT_GC_UNSAFE;
}
static void
gboolean call_success_cb = FALSE;
LifoSemaphoreAsyncWaitCallbackFn success_cb = NULL;
intptr_t user_data = 0;
+ MONO_ENTER_GC_UNSAFE;
mono_coop_mutex_lock (&sem->base.mutex);
switch (wait_entry->state) {
case LIFO_JS_SIGNALED:
mono_coop_mutex_unlock (&sem->base.mutex);
g_assert (call_success_cb);
success_cb (sem, user_data);
+ MONO_EXIT_GC_UNSAFE;
}
#endif /* HOST_BROWSER && !DISABLE_THREADS */
* destroyed.
*
* FIXME: should we just always use the mutex to protect the wait entry status+refcount?
- *
- * TODO: when we call emscripten_set_timeout it implicitly calls emscripten_runtime_keepalive_push which is
- * popped when the timeout runs. But emscripten_clear_timeout doesn't pop - we need to pop ourselves
*/
void
mono_lifo_semaphore_asyncwait_prepare_wait (LifoSemaphoreAsyncWait *semaphore, int32_t timeout_ms,
#include <mono/utils/mono-mmap.h>
#include <mono/utils/mono-threads-api.h>
#include <mono/utils/mono-threads-debug.h>
+#include <mono/utils/checked-build.h>
#include <glib.h>
#include <emscripten/threading.h>
#endif
+
#define round_down(addr, val) ((void*)((addr) & ~((val) - 1)))
EMSCRIPTEN_KEEPALIVE
mono_thread_platform_external_eventloop_keepalive_check (void)
{
#if defined(HOST_BROWSER) && !defined(DISABLE_THREADS)
+ MONO_REQ_GC_SAFE_MODE;
/* if someone called emscripten_runtime_keepalive_push (), the
* thread will stay alive in the JS event loop after returning
* from the thread's main function.
#endif /*DISABLE_THREADS*/
}
+#ifndef DISABLE_THREADS
+void
+mono_target_thread_schedule_background_job (MonoNativeThreadId target_thread, background_job_cb cb)
+{
+ THREADS_DEBUG ("worker %p queued job %p to worker %p \n", (gpointer)pthread_self(), (gpointer) cb, (gpointer) target_thread);
+ // NOTE: here the cb is [UnmanagedCallersOnly] which wraps it with MONO_ENTER_GC_UNSAFE/MONO_EXIT_GC_UNSAFE
+ mono_threads_wasm_async_run_in_target_thread_vi ((pthread_t) target_thread, (void*)mono_current_thread_schedule_background_job, (gpointer)cb);
+}
+#endif /*DISABLE_THREADS*/
+
G_EXTERN_C
EMSCRIPTEN_KEEPALIVE void
mono_background_exec (void);
}
#ifndef DISABLE_THREADS
-extern void
-mono_wasm_pthread_on_pthread_attached (gpointer pthread_id);
+extern void mono_wasm_pthread_on_pthread_attached (MonoNativeThreadId pthread_id);
+extern void mono_wasm_pthread_on_pthread_detached (MonoNativeThreadId pthread_id);
#endif
void
#endif
}
+void
+mono_threads_wasm_on_thread_detached (void)
+{
+#ifdef DISABLE_THREADS
+ return;
+#else
+ if (mono_threads_wasm_is_browser_thread ()) {
+ return;
+ }
+ // Notify JS that the pthread attachd to Mono
+ pthread_t id = pthread_self ();
+
+ mono_wasm_pthread_on_pthread_detached (id);
+#endif
+}
#ifndef DISABLE_THREADS
void
void
mono_threads_wasm_on_thread_attached (void);
+void
+mono_threads_wasm_on_thread_detached (void);
+
#endif /* HOST_WASM*/
#endif /* __MONO_THREADS_WASM_H__ */
mono_thread_info_suspend_unlock ();
+#ifdef HOST_BROWSER
+ mono_threads_wasm_on_thread_detached ();
+#endif
+
g_byte_array_free (info->stackdata, /*free_segment=*/TRUE);
/*now it's safe to free the thread info.*/
typedef void (*background_job_cb)(void);
void mono_main_thread_schedule_background_job (background_job_cb cb);
void mono_current_thread_schedule_background_job (background_job_cb cb);
+void mono_target_thread_schedule_background_job (MonoNativeThreadId target_thread, background_job_cb cb);
#endif
#ifdef USE_WINDOWS_BACKEND
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.JavaScript;
using System.Threading;
+using System.Reflection;
using System.Threading.Tasks;
using System.Collections.Generic;
return 0;
}
+ [JSImport("globalThis.setTimeout")]
+ static partial void GlobalThisSetTimeout([JSMarshalAs<JSType.Function>] Action cb, int timeoutMs);
+
+ [JSImport("globalThis.fetch")]
+ private static partial Task<JSObject> GlobalThisFetch(string url);
+
+ [JSImport("globalThis.console.log")]
+ private static partial void GlobalThisConsoleLog(string text);
+
+ const string fetchhelper = "./fetchelper.js";
+
+ [JSImport("responseText", fetchhelper)]
+ private static partial Task<string> FetchHelperResponseText(JSObject response, int delayMs);
+
+ [JSImport("delay", fetchhelper)]
+ private static partial Task Delay(int timeoutMs);
+
+ [JSExport]
+ internal static Task TestHelloWebWorker()
+ {
+ Console.WriteLine($"smoke: TestHelloWebWorker 1 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
+ Task t= WebWorker.RunAsync(() =>
+ {
+ Console.WriteLine($"smoke: TestHelloWebWorker 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
+ GlobalThisConsoleLog($"smoke: TestHelloWebWorker 3 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
+ Console.WriteLine($"smoke: TestHelloWebWorker 4 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
+ });
+ Console.WriteLine($"smoke: TestHelloWebWorker 5 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
+ return t.ContinueWith(Gogo);
+ }
+
+ private static void Gogo(Task t){
+ Console.WriteLine($"smoke: TestHelloWebWorker 6 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
+ }
+
[JSExport]
public static async Task TestCanStartThread()
{
throw new Exception("Child thread ran on same thread as parent");
}
- [JSImport("globalThis.setTimeout")]
- static partial void GlobalThisSetTimeout([JSMarshalAs<JSType.Function>] Action cb, int timeoutMs);
+ static bool _timerDone = false;
- [JSImport("globalThis.fetch")]
- private static partial Task<JSObject> GlobalThisFetch(string url);
+ [JSExport]
+ internal static void StartTimerFromWorker()
+ {
+ Console.WriteLine("smoke: StartTimerFromWorker 1 utc {0}", DateTime.UtcNow.ToUniversalTime());
+ WebWorker.RunAsync(async () =>
+ {
+ while (!_timerDone)
+ {
+ await Task.Delay(1 * 1000);
+ Console.WriteLine("smoke: StartTimerFromWorker 2 utc {0}", DateTime.UtcNow.ToUniversalTime());
+ }
+ Console.WriteLine("smoke: StartTimerFromWorker done utc {0}", DateTime.UtcNow.ToUniversalTime());
+ });
+ }
[JSExport]
- public static async Task TestCallSetTimeoutOnWorker()
+ internal static void StartAllocatorFromWorker()
{
- var t = Task.Run(TimeOutThenComplete);
- await t;
- Console.WriteLine ($"XYZ: Main Thread caught task tid:{Thread.CurrentThread.ManagedThreadId}");
+ Console.WriteLine("smoke: StartAllocatorFromWorker 1 utc {0}", DateTime.UtcNow.ToUniversalTime());
+ WebWorker.RunAsync(async () =>
+ {
+ while (!_timerDone)
+ {
+ await Task.Delay(1 * 100);
+ var x = new List<int[]>();
+ for (int i = 0; i < 1000; i++)
+ {
+ var v=new int[1000];
+ v[i] = i;
+ x.Add(v);
+ }
+ Console.WriteLine("smoke: StartAllocatorFromWorker 2 utc {0} {1} {2}", DateTime.UtcNow.ToUniversalTime(),x[1][1], GC.GetTotalAllocatedBytes());
+ }
+ Console.WriteLine("smoke: StartAllocatorFromWorker done utc {0}", DateTime.UtcNow.ToUniversalTime());
+ });
}
- const string fetchhelper = "./fetchelper.js";
+ [JSExport]
+ internal static void StopTimerFromWorker()
+ {
+ _timerDone = true;
+ }
- [JSImport("responseText", fetchhelper)]
- private static partial Task<string> FetchHelperResponseText(JSObject response, int delayMs);
+ [JSExport]
+ public static async Task TestCallSetTimeoutOnWorker()
+ {
+ await WebWorker.RunAsync(() => TimeOutThenComplete());
+ Console.WriteLine ($"XYZ: Main Thread caught task tid:{Thread.CurrentThread.ManagedThreadId}");
+ }
[JSExport]
public static async Task<string> FetchBackground(string url)
{
Console.WriteLine($"smoke: FetchBackground 1 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
- var t = Task.Run(async () =>
+ var t = WebWorker.RunAsync(async () =>
{
Console.WriteLine($"smoke: FetchBackground 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
var x=JSHost.ImportAsync(fetchhelper, "./fetchhelper.js");
return "not-ok";
});
var r = await t;
- Console.WriteLine($"XYZ: FetchBackground thread:{Thread.CurrentThread.ManagedThreadId} background thread returned");
+ Console.WriteLine($"smoke: FetchBackground thread:{Thread.CurrentThread.ManagedThreadId} background thread returned");
return r;
}
+ [ThreadStatic]
+ public static int meaning = 42;
+
+ [JSExport]
+ public static async Task TestTLS()
+ {
+ Console.WriteLine($"smoke {meaning}: TestTLS 1 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
+ meaning = 40;
+ await WebWorker.RunAsync(async () =>
+ {
+ Console.WriteLine($"smoke {meaning}: TestTLS 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
+ meaning = 41;
+ await JSHost.ImportAsync(fetchhelper, "./fetchhelper.js");
+ Console.WriteLine($"smoke {meaning}: TestTLS 3 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
+ meaning = 43;
+ Console.WriteLine($"smoke {meaning}: TestTLS 4 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
+ await Delay(100);
+ meaning = 44;
+ Console.WriteLine($"smoke {meaning}: TestTLS 5 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
+ });
+ Console.WriteLine($"smoke {meaning}: TestTLS 9 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}");
+ }
+
private static async Task TimeOutThenComplete()
{
var tcs = new TaskCompletionSource();
- Console.WriteLine ($"XYZ: Task running tid:{Thread.CurrentThread.ManagedThreadId}");
+ Console.WriteLine ($"smoke: Task running tid:{Thread.CurrentThread.ManagedThreadId}");
GlobalThisSetTimeout(() => {
tcs.SetResult();
- Console.WriteLine ($"XYZ: Timeout fired tid:{Thread.CurrentThread.ManagedThreadId}");
+ Console.WriteLine ($"smoke: Timeout fired tid:{Thread.CurrentThread.ManagedThreadId}");
}, 250);
- Console.WriteLine ($"XYZ: Task sleeping tid:{Thread.CurrentThread.ManagedThreadId}");
+ Console.WriteLine ($"smoke: Task sleeping tid:{Thread.CurrentThread.ManagedThreadId}");
await tcs.Task;
- Console.WriteLine ($"XYZ: Task resumed tid:{Thread.CurrentThread.ManagedThreadId}");
+ Console.WriteLine ($"smoke: Task resumed tid:{Thread.CurrentThread.ManagedThreadId}");
}
[JSExport]
return rs[0];
}
+ [JSExport]
+ internal static void GCCollect()
+ {
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ }
+
+
public static int CountingCollatzTest()
{
const int limit = 5000;
--- /dev/null
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices.JavaScript;
+using System.Threading;
+using System.Reflection;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Runtime.InteropServices.JavaScript
+{
+ // this is just temporary thin wrapper to expose future public API
+ public partial class WebWorker
+ {
+ private static MethodInfo runAsyncMethod;
+ private static MethodInfo runAsyncVoidMethod;
+ private static MethodInfo runMethod;
+
+ [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.WebWorker", "System.Runtime.InteropServices.JavaScript")]
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", Justification = "work in progress")]
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern", Justification = "work in progress")]
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:UnrecognizedReflectionPattern", Justification = "work in progress")]
+ public static Task<T> RunAsync<T>(Func<Task<T>> body, CancellationToken cancellationToken)
+ {
+ if(runAsyncMethod == null)
+ {
+ var webWorker = typeof(JSObject).Assembly.GetType("System.Runtime.InteropServices.JavaScript.WebWorker");
+ runAsyncMethod = webWorker.GetMethod("RunAsync", BindingFlags.Public|BindingFlags.Static);
+ }
+
+ var genericRunAsyncMethod = runAsyncMethod.MakeGenericMethod(typeof(T));
+ return (Task<T>)genericRunAsyncMethod.Invoke(null, new object[] { body, cancellationToken });
+ }
+
+ public static Task<T> RunAsync<T>(Func<Task<T>> body)
+ {
+ return RunAsync(body, CancellationToken.None);
+ }
+
+ [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.WebWorker", "System.Runtime.InteropServices.JavaScript")]
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", Justification = "work in progress")]
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern", Justification = "work in progress")]
+ public static Task RunAsync(Func<Task> body, CancellationToken cancellationToken)
+ {
+ if(runAsyncVoidMethod == null)
+ {
+ var webWorker = typeof(JSObject).Assembly.GetType("System.Runtime.InteropServices.JavaScript.WebWorker");
+ runAsyncVoidMethod = webWorker.GetMethod("RunAsyncVoid", BindingFlags.Public|BindingFlags.Static);
+ }
+ return (Task)runAsyncVoidMethod.Invoke(null, new object[] { body, cancellationToken });
+ }
+
+ public static Task RunAsync(Func<Task> body)
+ {
+ return RunAsync(body, CancellationToken.None);
+ }
+
+ [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.WebWorker", "System.Runtime.InteropServices.JavaScript")]
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", Justification = "work in progress")]
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern", Justification = "work in progress")]
+ public static Task RunAsync(Action body, CancellationToken cancellationToken)
+ {
+ if(runMethod == null)
+ {
+ var webWorker = typeof(JSObject).Assembly.GetType("System.Runtime.InteropServices.JavaScript.WebWorker");
+ runMethod = webWorker.GetMethod("Run", BindingFlags.Public|BindingFlags.Static);
+ }
+ return (Task)runMethod.Invoke(null, new object[] { body, cancellationToken });
+ }
+
+ public static Task RunAsync(Action body)
+ {
+ return RunAsync(body, CancellationToken.None);
+ }
+ }
+}
\ No newline at end of file
try {
const { setModuleImports, getAssemblyExports, runMain } = await dotnet
- .withEnvironmentVariable("MONO_LOG_LEVEL", "debug")
+ //.withEnvironmentVariable("MONO_LOG_LEVEL", "debug")
+ .withDiagnosticTracing(true)
+ .withConfig({
+ pthreadPoolSize: 6,
+ })
.withElementOnExit()
.withExitCodeLogging()
.create();
const exports = await getAssemblyExports(assemblyName);
+ console.log("smoke: running TestHelloWebWorker");
+ await exports.Sample.Test.TestHelloWebWorker();
+ await exports.Sample.Test.TestHelloWebWorker();
+ await exports.Sample.Test.TestHelloWebWorker();
+ await exports.Sample.Test.TestHelloWebWorker();
+ await exports.Sample.Test.TestHelloWebWorker();
+ await exports.Sample.Test.TestHelloWebWorker();
+ await exports.Sample.Test.TestHelloWebWorker();
+ await exports.Sample.Test.TestHelloWebWorker();
+ console.log("smoke: TestHelloWebWorker done");
+
console.log("smoke: running TestCanStartThread");
await exports.Sample.Test.TestCanStartThread();
console.log("smoke: TestCanStartThread done");
+ console.log("smoke: running TestTLS");
+ await exports.Sample.Test.TestTLS();
+ console.log("smoke: TestTLS done");
+
+ console.log("smoke: running StartTimerFromWorker");
+ exports.Sample.Test.StartTimerFromWorker();
+
console.log("smoke: running TestCallSetTimeoutOnWorker");
await exports.Sample.Test.TestCallSetTimeoutOnWorker();
console.log("smoke: TestCallSetTimeoutOnWorker done");
}
console.log("smoke: TaskRunCompute done");
+ console.log("smoke: running StartAllocatorFromWorker");
+ exports.Sample.Test.StartAllocatorFromWorker();
+
+ await delay(5000);
+
+ console.log("smoke: running GCCollect");
+ exports.Sample.Test.GCCollect();
+
+ await delay(5000);
+
+ console.log("smoke: running GCCollect");
+ exports.Sample.Test.GCCollect();
+
+ console.log("smoke: running StopTimerFromWorker");
+ exports.Sample.Test.StopTimerFromWorker();
let exit_code = await runMain(assemblyName, []);
exit(exit_code);
} catch (err) {
exit(2, err);
}
+
+function delay(timeoutMs) {
+ return new Promise(resolve => setTimeout(resolve, timeoutMs));
+}
+
extern void* mono_wasm_invoke_js_blazor (MonoString **exceptionMessage, void *callInfo, void* arg0, void* arg1, void* arg2);
#endif /* DISABLE_LEGACY_JS_INTEROP */
+#ifndef DISABLE_THREADS
+extern void mono_wasm_install_js_worker_interop (int install_js_synchronization_context);
+extern void mono_wasm_uninstall_js_worker_interop (int uninstall_js_synchronization_context);
+#endif /* DISABLE_THREADS */
+
// HybridGlobalization
extern void mono_wasm_change_case_invariant(const uint16_t* src, int32_t srcLength, uint16_t* dst, int32_t dstLength, mono_bool bToUpper, int *is_exception, MonoObject** ex_result);
extern void mono_wasm_change_case(MonoString **culture, const uint16_t* src, int32_t srcLength, uint16_t* dst, int32_t dstLength, mono_bool bToUpper, int *is_exception, MonoObject** ex_result);
mono_add_internal_call ("Interop/Runtime::MarshalPromise", mono_wasm_marshal_promise);
mono_add_internal_call ("Interop/Runtime::RegisterGCRoot", mono_wasm_register_root);
mono_add_internal_call ("Interop/Runtime::DeregisterGCRoot", mono_wasm_deregister_root);
+
+#ifndef DISABLE_THREADS
+ mono_add_internal_call ("Interop/Runtime::InstallWebWorkerInterop", mono_wasm_install_js_worker_interop);
+ mono_add_internal_call ("Interop/Runtime::UninstallWebWorkerInterop", mono_wasm_uninstall_js_worker_interop);
+#endif /* DISABLE_THREADS */
+
#ifndef DISABLE_LEGACY_JS_INTEROP
// legacy
mono_add_internal_call ("Interop/Runtime::InvokeJSWithArgsRef", mono_wasm_invoke_js_with_args_ref);
//!
//! This is generated file, see src/mono/wasm/runtime/rollup.config.js
-//! This is not considered public API with backward compatibility guarantees.
+//! This is not considered public API with backward compatibility guarantees.
declare interface NativePointer {
__brandNativePointer: "NativePointer";
}
EMSCRIPTEN_KEEPALIVE int
-mono_wasm_invoke_method_bound (MonoMethod *method, void* args /*JSMarshalerArguments*/, MonoObject **_out_exc)
+mono_wasm_invoke_method_bound (MonoMethod *method, void* args /*JSMarshalerArguments*/, MonoString **out_exc)
{
- PPVOLATILE(MonoObject) out_exc = _out_exc;
+ PVOLATILE(MonoObject) temp_exc = NULL;
+
void *invoke_args[1] = { args };
int is_err = 0;
MONO_ENTER_GC_UNSAFE;
- mono_runtime_invoke (method, NULL, invoke_args, (MonoObject **)out_exc);
+ mono_runtime_invoke (method, NULL, invoke_args, (MonoObject **)&temp_exc);
// this failure is unlikely because it would be runtime error, not application exception.
// the application exception is passed inside JSMarshalerArguments `args`
- if (*_out_exc) {
+ if (temp_exc) {
PVOLATILE(MonoObject) exc2 = NULL;
- store_volatile(_out_exc, (MonoObject*)mono_object_to_string (*out_exc, (MonoObject **)&exc2));
+ store_volatile((MonoObject**)out_exc, (MonoObject*)mono_object_to_string ((MonoObject*)temp_exc, (MonoObject **)&exc2));
if (exc2)
- store_volatile(_out_exc, (MonoObject*)mono_string_new (root_domain, "Exception Double Fault"));
+ store_volatile((MonoObject**)out_exc, (MonoObject*)mono_string_new (root_domain, "Exception Double Fault"));
is_err = 1;
}
MONO_EXIT_GC_UNSAFE;
#if USE_PTHREADS
linked_functions = [...linked_functions,
- /// mono-threads-wasm.c
+ // mono-threads-wasm.c
"mono_wasm_pthread_on_pthread_attached",
+ "mono_wasm_pthread_on_pthread_detached",
// threads.c
"mono_wasm_eventloop_has_unsettled_interop_promises",
// diagnostics_server.c
"mono_wasm_diagnostic_server_on_server_thread_created",
"mono_wasm_diagnostic_server_on_runtime_server_init",
"mono_wasm_diagnostic_server_stream_signal_work_available",
+ // corebindings.c
+ "mono_wasm_install_js_worker_interop",
+ "mono_wasm_uninstall_js_worker_interop",
]
#endif
if (!DISABLE_LEGACY_JS_INTEROP) {
import { mono_interp_jit_wasm_jit_call_trampoline, mono_interp_invoke_wasm_jit_call_trampoline, mono_interp_flush_jitcall_queue, mono_jiterp_do_jit_call_indirect } from "./jiterpreter-jit-call";
import { mono_wasm_marshal_promise } from "./marshal-to-js";
import { mono_wasm_eventloop_has_unsettled_interop_promises } from "./pthreads/shared/eventloop";
-import { mono_wasm_pthread_on_pthread_attached } from "./pthreads/worker";
+import { mono_wasm_pthread_on_pthread_attached, mono_wasm_pthread_on_pthread_detached } from "./pthreads/worker";
import { mono_wasm_schedule_timer, schedule_background_exec } from "./scheduling";
import { mono_wasm_asm_loaded } from "./startup";
import { mono_wasm_diagnostic_server_on_server_thread_created } from "./diagnostics/server_pthread";
import { mono_wasm_diagnostic_server_stream_signal_work_available } from "./diagnostics/server_pthread/stream-queue";
import { mono_wasm_trace_logger } from "./logging";
import { mono_wasm_profiler_leave, mono_wasm_profiler_enter } from "./profiler";
-import { mono_wasm_create_cs_owned_object_ref } from "./net6-legacy/cs-to-js";
-import { mono_wasm_typed_array_to_array_ref } from "./net6-legacy/js-to-cs";
-import { mono_wasm_typed_array_from_ref } from "./net6-legacy/buffers";
+import { mono_wasm_change_case, mono_wasm_change_case_invariant } from "./hybrid-globalization/change-case";
+import { mono_wasm_compare_string, mono_wasm_ends_with, mono_wasm_starts_with, mono_wasm_index_of } from "./hybrid-globalization/collations";
+import { mono_wasm_install_js_worker_interop, mono_wasm_uninstall_js_worker_interop } from "./pthreads/shared";
+
import {
mono_wasm_invoke_js_blazor, mono_wasm_invoke_js_with_args_ref, mono_wasm_get_object_property_ref, mono_wasm_set_object_property_ref,
mono_wasm_get_by_index_ref, mono_wasm_set_by_index_ref, mono_wasm_get_global_object_ref
} from "./net6-legacy/method-calls";
-import { mono_wasm_change_case, mono_wasm_change_case_invariant } from "./hybrid-globalization/change-case";
-import { mono_wasm_compare_string, mono_wasm_ends_with, mono_wasm_starts_with, mono_wasm_index_of } from "./hybrid-globalization/collations";
+import { mono_wasm_create_cs_owned_object_ref } from "./net6-legacy/cs-to-js";
+import { mono_wasm_typed_array_to_array_ref } from "./net6-legacy/js-to-cs";
+import { mono_wasm_typed_array_from_ref } from "./net6-legacy/buffers";
// the methods would be visible to EMCC linker
// --- keep in sync with dotnet.cjs.lib.js ---
const mono_wasm_threads_exports = !MonoWasmThreads ? undefined : {
// mono-threads-wasm.c
mono_wasm_pthread_on_pthread_attached,
+ mono_wasm_pthread_on_pthread_detached,
// threads.c
mono_wasm_eventloop_has_unsettled_interop_promises,
// diagnostics_server.c
mono_wasm_diagnostic_server_on_server_thread_created,
mono_wasm_diagnostic_server_on_runtime_server_init,
mono_wasm_diagnostic_server_stream_signal_work_available,
+
+ // corebindings.c
+ mono_wasm_install_js_worker_interop,
+ mono_wasm_uninstall_js_worker_interop,
};
const mono_wasm_legacy_interop_exports = !WasmEnableLegacyJsInterop ? undefined : {
// The .NET Foundation licenses this file to you under the MIT license.
import { runtimeHelpers } from "./globals";
+import { mono_log_warn } from "./logging";
import { GCHandle, GCHandleNull, JSHandle, JSHandleDisposed, JSHandleNull } from "./types/internal";
import { create_weak_ref } from "./weak-ref";
return null;
}
+export function forceDisposeProxies(dump: boolean): void {
+ // dispose all proxies to C# objects
+ const gchandles = [..._js_owned_object_table.keys()];
+ for (const gchandle of gchandles) {
+ const wr = _js_owned_object_table.get(gchandle);
+ const obj = wr.deref();
+ if (obj) {
+ if (dump) {
+ mono_log_warn(`Proxy of C# object with GCHandle ${gchandle} was still alive`);
+ }
+ teardown_managed_proxy(obj, gchandle);
+ }
+ }
+ // TODO: call C# to iterate and release all in JSHostImplementation.ThreadCsOwnedObjects
+
+ // dispose all proxies to JS objects
+ for (const js_obj of _cs_owned_objects_by_js_handle) {
+ if (js_obj) {
+ const js_handle = js_obj[cs_owned_js_handle_symbol];
+ if (js_handle) {
+ mono_log_warn(`Proxy of JS object with JSHandleandle ${js_handle} was still alive`);
+ mono_wasm_release_cs_owned_object(js_handle);
+ }
+ }
+ }
+}
\ No newline at end of file
} from "./marshal";
import { mono_wasm_new_external_root, mono_wasm_new_root } from "./roots";
import { monoStringToString } from "./strings";
-import { MonoObjectRef, MonoStringRef, MonoString, MonoObject, MonoMethod, JSMarshalerArguments, JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs, VoidPtrNull, MonoObjectRefNull, MonoObjectNull } from "./types/internal";
+import { MonoObjectRef, MonoStringRef, MonoString, MonoObject, MonoMethod, JSMarshalerArguments, JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs, VoidPtrNull, MonoObjectRefNull, MonoObjectNull, MarshalerType } from "./types/internal";
import { Int32Ptr } from "./types/emscripten";
import cwraps from "./cwraps";
import { assembly_load } from "./class-loader";
-import { wrap_error_root, wrap_no_error_root } from "./invoke-js";
+import { assert_bindings, wrap_error_root, wrap_no_error_root } from "./invoke-js";
import { startMeasure, MeasuredBlock, endMeasure } from "./profiler";
import { mono_log_debug } from "./logging";
import { assert_synchronization_context } from "./pthreads/shared";
export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, signature_hash: number, signature: JSFunctionSignature, is_exception: Int32Ptr, result_address: MonoObjectRef): void {
- assert_synchronization_context();
+ assert_bindings();
const fqn_root = mono_wasm_new_external_root<MonoString>(fully_qualified_name), resultRoot = mono_wasm_new_external_root<MonoObject>(result_address);
const mark = startMeasure();
try {
for (let index = 0; index < args_count; index++) {
const sig = get_sig(signature, index + 2);
const marshaler_type = get_signature_type(sig);
+ if (marshaler_type == MarshalerType.Task) {
+ assert_synchronization_context();
+ }
const arg_marshaler = bind_arg_marshal_to_cs(sig, marshaler_type, index + 2);
mono_assert(arg_marshaler, "ERR43: argument marshaler must be resolved");
arg_marshalers[index] = arg_marshaler;
const res_sig = get_sig(signature, 1);
const res_marshaler_type = get_signature_type(res_sig);
+ if (res_marshaler_type == MarshalerType.Task) {
+ assert_synchronization_context();
+ }
const res_converter = bind_arg_marshal_to_js(res_sig, res_marshaler_type, 1);
const closure: BindingClosure = {
// this is just to make debugging easier.
// It's not CSP compliant and possibly not performant, that's why it's only enabled in debug builds
// in Release configuration, it would be a trimmed by rollup
- if (BuildConfiguration === "Debug") {
- bound_fn = new Function("fn", "return (function JSExport_" + methodname + "(){ return fn.apply(this, arguments)});")(bound_fn);
+ if (BuildConfiguration === "Debug" && !runtimeHelpers.cspPolicy) {
+ try {
+ bound_fn = new Function("fn", "return (function JSExport_" + methodname + "(){ return fn.apply(this, arguments)});")(bound_fn);
+ }
+ catch (ex) {
+ runtimeHelpers.cspPolicy = true;
+ }
}
(<any>bound_fn)[bound_cs_function_symbol] = true;
}
export function invoke_method_and_handle_exception(method: MonoMethod, args: JSMarshalerArguments): void {
+ assert_bindings();
const fail_root = mono_wasm_new_root<MonoString>();
try {
- assert_synchronization_context();
const fail = cwraps.mono_wasm_invoke_method_bound(method, args, fail_root.address);
if (fail) throw new Error("ERR24: Unexpected error: " + monoStringToString(fail_root));
if (is_args_exception(args)) {
}
export async function mono_wasm_get_assembly_exports(assembly: string): Promise<any> {
- mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "The runtime must be initialized.");
+ assert_bindings();
const result = exportsByAssembly.get(assembly);
if (!result) {
const mark = startMeasure();
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+import MonoWasmThreads from "consts:monoWasmThreads";
import BuildConfiguration from "consts:configuration";
import { marshal_exception_to_cs, bind_arg_marshal_to_cs } from "./marshal-to-cs";
import { monoStringToString, stringToMonoStringRoot } from "./strings";
import { MonoObject, MonoObjectRef, MonoString, MonoStringRef, JSFunctionSignature, JSMarshalerArguments, WasmRoot, BoundMarshalerToJs, JSFnHandle, BoundMarshalerToCs, JSHandle, MarshalerType } from "./types/internal";
import { Int32Ptr } from "./types/emscripten";
-import { INTERNAL, Module } from "./globals";
+import { INTERNAL, Module, runtimeHelpers } from "./globals";
import { bind_arg_marshal_to_js } from "./marshal-to-js";
import { mono_wasm_new_external_root } from "./roots";
import { mono_log_debug, mono_wasm_symbolicate_string } from "./logging";
const fn_wrapper_by_fn_handle: Function[] = <any>[null];// 0th slot is dummy, we never free bound functions
export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_name: MonoStringRef, signature: JSFunctionSignature, function_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void {
- assert_synchronization_context();
+ assert_bindings();
const function_name_root = mono_wasm_new_external_root<MonoString>(function_name),
module_name_root = mono_wasm_new_external_root<MonoString>(module_name),
resultRoot = mono_wasm_new_external_root<MonoObject>(result_address);
};
has_cleanup = true;
}
+ else if (marshaler_type == MarshalerType.Task) {
+ assert_synchronization_context();
+ }
}
const res_sig = get_sig(signature, 1);
const res_marshaler_type = get_signature_type(res_sig);
+ if (res_marshaler_type == MarshalerType.Task) {
+ assert_synchronization_context();
+ }
const res_converter = bind_arg_marshal_to_cs(res_sig, res_marshaler_type, 1);
const closure: BindingClosure = {
// this is just to make debugging easier.
// It's not CSP compliant and possibly not performant, that's why it's only enabled in debug builds
// in Release configuration, it would be a trimmed by rollup
- if (BuildConfiguration === "Debug") {
- bound_fn = new Function("fn", "return (function JSImport_" + js_function_name.replaceAll(".", "_") + "(){ return fn.apply(this, arguments)});")(bound_fn);
+ if (BuildConfiguration === "Debug" && !runtimeHelpers.cspPolicy) {
+ try {
+ bound_fn = new Function("fn", "return (function JSImport_" + js_function_name.replaceAll(".", "_") + "(){ return fn.apply(this, arguments)});")(bound_fn);
+ }
+ catch (ex) {
+ runtimeHelpers.cspPolicy = true;
+ }
}
(<any>bound_fn)[imported_js_function_symbol] = true;
result.clear();
}
}
+
+export function assert_bindings(): void {
+ if (MonoWasmThreads) {
+ mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "Please use dedicated worker for working with JavaScript interop. See https://github.com/dotnet/runtime/blob/main/src/mono/wasm/threads.md#JS-interop-on-dedicated-threads");
+ } else {
+ mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "The runtime must be initialized.");
+ }
+}
import { utf8ToString } from "./strings";
import { CharPtr, VoidPtr } from "./types/emscripten";
-const prefix = "MONO_WASM: ";
+let prefix = "MONO_WASM: ";
+
+export function mono_set_thread_id(tid: string) {
+ prefix = `MONO_WASM [${tid}]: `;
+}
export function mono_log_debug(msg: string, ...data: any) {
if (runtimeHelpers.diagnosticTracing) {
const get_managed_stack_trace_method = get_method("GetManagedStackTrace");
mono_assert(get_managed_stack_trace_method, "Can't find GetManagedStackTrace method");
- runtimeHelpers.javaScriptExports.call_entry_point = (entry_point: MonoMethod, program_args?: string[]) => {
+ runtimeHelpers.javaScriptExports.call_entry_point = async (entry_point: MonoMethod, program_args?: string[]): Promise<number> => {
const sp = Module.stackSave();
try {
+ Module.runtimeKeepalivePush();
const args = alloc_stack_frame(4);
const res = get_arg(args, 1);
const arg1 = get_arg(args, 2);
}
marshal_array_to_cs_impl(arg2, program_args, MarshalerType.String);
invoke_method_and_handle_exception(call_entry_point, args);
- const promise = marshal_task_to_js(res, undefined, marshal_int32_to_js);
- if (!promise) {
- return Promise.resolve(0);
+ let promise = marshal_task_to_js(res, undefined, marshal_int32_to_js);
+ if (promise === null || promise === undefined) {
+ promise = Promise.resolve(0);
}
- return promise;
+ return await promise;
} finally {
+ Module.runtimeKeepalivePop();// after await promise !
Module.stackRestore(sp);
}
};
import { legacyManagedExports } from "./corebindings";
import { legacyHelpers } from "./globals";
import { js_to_mono_obj_root } from "./js-to-cs";
-import { mono_bind_method, mono_method_get_call_signature_ref } from "./method-binding";
+import { assert_legacy_interop, mono_bind_method, mono_method_get_call_signature_ref } from "./method-binding";
import { createPromiseController } from "../globals";
-import { assert_legacy_interop } from "../pthreads/shared";
import { monoStringToStringUnsafe } from "./strings";
const delegate_invoke_symbol = Symbol.for("wasm delegate_invoke");
import { legacyManagedExports } from "./corebindings";
import { get_js_owned_object_by_gc_handle_ref } from "./cs-to-js";
import { legacyHelpers, wasm_type_symbol } from "./globals";
-import { assert_legacy_interop } from "../pthreads/shared";
+import { assert_legacy_interop } from "./method-binding";
export function _js_to_mono_uri_root(should_add_in_flight: boolean, js_obj: any, result: WasmRoot<MonoObject>): void {
switch (true) {
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+import MonoWasmThreads from "consts:monoWasmThreads";
+
import { legacy_c_functions as cwraps } from "../cwraps";
-import { Module } from "../globals";
+import { ENVIRONMENT_IS_PTHREAD, Module } from "../globals";
import { parseFQN } from "../invoke-cs";
import { setI32, setU32, setF32, setF64, setU52, setI52, setB32, setI32_unchecked, setU32_unchecked, _zero_region, _create_temp_frame, getB32, getI32, getU32, getF32, getF64 } from "../memory";
import { mono_wasm_new_external_root, mono_wasm_new_root } from "../roots";
import { js_to_mono_obj_root, _js_to_mono_uri_root, js_to_mono_enum } from "./js-to-cs";
import { _teardown_after_call } from "./method-calls";
import { mono_log_warn } from "../logging";
-import { assert_legacy_interop } from "../pthreads/shared";
+import { assert_bindings } from "../invoke-js";
const escapeRE = /[^A-Za-z0-9_$]/g;
export function mono_method_get_call_signature_ref(method: MonoMethod, mono_obj?: WasmRoot<MonoObject>): string/*ArgsMarshalString*/ {
return legacyManagedExports._get_call_sig_ref(method, mono_obj ? mono_obj.address : legacyHelpers._null_root.address);
}
+
+export function assert_legacy_interop(): void {
+ if (MonoWasmThreads) {
+ mono_assert(!ENVIRONMENT_IS_PTHREAD, "Legacy interop is not supported with WebAssembly threads.");
+ }
+ assert_bindings();
+}
\ No newline at end of file
// The .NET Foundation licenses this file to you under the MIT license.
import { get_js_obj, mono_wasm_get_jsobj_from_js_handle } from "../gc-handles";
-import { Module, runtimeHelpers, INTERNAL } from "../globals";
+import { Module, INTERNAL } from "../globals";
import { wrap_error_root, wrap_no_error_root } from "../invoke-js";
import { _release_temp_frame } from "../memory";
import { mono_wasm_new_external_root, mono_wasm_new_root } from "../roots";
import { Int32Ptr, VoidPtr } from "../types/emscripten";
import { mono_array_root_to_js_array, unbox_mono_obj_root } from "./cs-to-js";
import { js_array_to_mono_array, js_to_mono_obj_root } from "./js-to-cs";
-import { Converter, BoundMethodToken, mono_method_resolve, mono_method_get_call_signature_ref, mono_bind_method } from "./method-binding";
-import { assert_legacy_interop } from "../pthreads/shared";
+import { Converter, BoundMethodToken, mono_method_resolve, mono_method_get_call_signature_ref, mono_bind_method, assert_legacy_interop } from "./method-binding";
const boundMethodsByFqn: Map<string, Function> = new Map();
}
export function mono_bind_static_method(fqn: string, signature?: string/*ArgsMarshalString*/): Function {
- mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "The runtime must be initialized.");
assert_legacy_interop();
const key = `${fqn}-${signature}`;
}
export function mono_call_assembly_entry_point(assembly: string, args?: any[], signature?: string/*ArgsMarshalString*/): number {
- mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "The runtime must be initialized.");
assert_legacy_interop();
if (!args) {
args = [[]];
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { assert_legacy_interop } from "../pthreads/shared";
import { mono_wasm_new_root } from "../roots";
import { interned_string_table, monoStringToString, mono_wasm_empty_string, stringToInternedMonoStringRoot, stringToMonoStringRoot } from "../strings";
import { MonoString, MonoStringNull, is_nullish } from "../types/internal";
let mono_wasm_string_root: any;
+import { assert_legacy_interop } from "./method-binding";
/**
* @deprecated Not GC or thread safe
root.release();
}
}
-
/* @deprecated not GC safe, use monoStringToString */
export function monoStringToStringUnsafe(mono_string: MonoString): string | null {
if (mono_string === MonoStringNull)
return null;
+ assert_legacy_interop();
if (!mono_wasm_string_root)
mono_wasm_string_root = mono_wasm_new_root();
// The .NET Foundation licenses this file to you under the MIT license.
import MonoWasmThreads from "consts:monoWasmThreads";
+import BuildConfiguration from "consts:configuration";
-import { ENVIRONMENT_IS_PTHREAD, Module, runtimeHelpers } from "../../globals";
+import { Module, runtimeHelpers } from "../../globals";
import { MonoConfig } from "../../types";
import { pthreadPtr } from "./types";
+import { mono_log_debug } from "../../logging";
+import { bindings_init } from "../../startup";
+import { forceDisposeProxies } from "../../gc-handles";
+import { pthread_self } from "../worker";
export interface PThreadInfo {
readonly pthreadId: pthreadPtr;
return false;
}
-let synchronization_context_installed = false;
-export function install_synchronization_context(): void {
- if (MonoWasmThreads && !synchronization_context_installed) {
- runtimeHelpers.javaScriptExports.install_synchronization_context();
- synchronization_context_installed = true;
+let worker_js_synchronization_context_installed = false;
+
+export function mono_wasm_install_js_worker_interop(install_js_synchronization_context: number): void {
+ if (!MonoWasmThreads) return;
+ bindings_init();
+ if (install_js_synchronization_context && !worker_js_synchronization_context_installed) {
+ worker_js_synchronization_context_installed = true;
+ mono_log_debug("Installed JSSynchronizationContext");
+ }
+ if (install_js_synchronization_context) {
+ Module.runtimeKeepalivePush();
}
+
+ set_thread_info(pthread_self ? pthread_self.pthreadId : 0, true, true, !!install_js_synchronization_context);
}
-export function assert_synchronization_context(): void {
- if (MonoWasmThreads) {
- // TODO mono_assert(synchronization_context_installed, "Synchronization context not installed on the current worker. Please use dedicated worker for working with JavaScript interop.");
+export function mono_wasm_uninstall_js_worker_interop(uninstall_js_synchronization_context: number): void {
+ if (!MonoWasmThreads) return;
+ mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "JS interop is not installed on this worker.");
+ mono_assert(!uninstall_js_synchronization_context || worker_js_synchronization_context_installed, "JSSynchronizationContext is not installed on this worker.");
+
+ forceDisposeProxies(false);
+ if (uninstall_js_synchronization_context) {
+ Module.runtimeKeepalivePop();
}
+
+ worker_js_synchronization_context_installed = false;
+ runtimeHelpers.mono_wasm_bindings_is_ready = false;
+ set_thread_info(pthread_self ? pthread_self.pthreadId : 0, true, false, false);
}
-export function assert_legacy_interop(): void {
+export function assert_synchronization_context(): void {
if (MonoWasmThreads) {
- mono_assert(!ENVIRONMENT_IS_PTHREAD, "Legacy interop is not supported with WebAssembly threads.");
+ mono_assert(worker_js_synchronization_context_installed, "Please use dedicated worker for working with JavaScript interop. See https://github.com/dotnet/runtime/blob/main/src/mono/wasm/threads.md#JS-interop-on-dedicated-threads");
}
}
+// this is just for Debug build of the runtime, making it easier to debug worker threads
+export function set_thread_info(pthread_ptr: number, isAttached: boolean, hasInterop: boolean, hasSynchronization: boolean): void {
+ if (MonoWasmThreads && BuildConfiguration === "Debug" && !runtimeHelpers.cspPolicy) {
+ try {
+ (globalThis as any).monoThreadInfo = new Function(`//# sourceURL=https://WorkerInfo/\r\nconsole.log("tid:0x${pthread_ptr.toString(16)} isAttached:${isAttached} hasInterop:${!!hasInterop} hasSynchronization:${hasSynchronization}" );`);
+ }
+ catch (ex) {
+ runtimeHelpers.cspPolicy = true;
+ }
+ }
+}
/// <reference lib="webworker" />
import MonoWasmThreads from "consts:monoWasmThreads";
+
import { Module, ENVIRONMENT_IS_PTHREAD } from "../../globals";
-import { makeChannelCreatedMonoMessage } from "../shared";
+import { makeChannelCreatedMonoMessage, set_thread_info } from "../shared";
import type { pthreadPtr } from "../shared/types";
import { is_nullish } from "../../types/internal";
import type { MonoThreadMessage } from "../shared";
} from "./events";
import { preRunWorker } from "../../startup";
import { mono_log_debug } from "../../logging";
+import { mono_set_thread_id } from "../../logging";
// re-export some of the events types
export {
/// This is an implementation detail function.
-/// Called in the worker thread from mono when a pthread becomes attached to the mono runtime.
-export function mono_wasm_pthread_on_pthread_attached(pthread_id: pthreadPtr): void {
+/// Called in the worker thread (not main thread) from mono when a pthread becomes attached to the mono runtime.
+export function mono_wasm_pthread_on_pthread_attached(pthread_id: number): void {
const self = pthread_self;
mono_assert(self !== null && self.pthreadId == pthread_id, "expected pthread_self to be set already when attaching");
- mono_log_debug("attaching pthread to runtime 0x" + pthread_id.toString(16));
+ mono_set_thread_id("0x" + pthread_id.toString(16));
+ mono_log_debug("attaching pthread to mono runtime 0x" + pthread_id.toString(16));
preRunWorker();
+ set_thread_info(pthread_id, true, false, false);
currentWorkerThreadEvents.dispatchEvent(makeWorkerThreadEvent(dotnetPthreadAttached, self));
}
+/// Called in the worker thread (not main thread) from mono when a pthread becomes detached from the mono runtime.
+export function mono_wasm_pthread_on_pthread_detached(pthread_id: number): void {
+ mono_log_debug("detaching pthread from mono runtime 0x" + pthread_id.toString(16));
+ set_thread_info(pthread_id, false, false, false);
+ mono_set_thread_id("");
+}
+
/// This is an implementation detail function.
/// Called by emscripten when a pthread is setup to run on a worker. Can be called multiple times
/// for the same worker, since emscripten can reuse workers. This is an implementation detail, that shouldn't be used directly.
import cwraps from "./cwraps";
import { assembly_load } from "./class-loader";
import { mono_log_info } from "./logging";
+import { assert_bindings } from "./invoke-js";
/**
* Possible signatures are described here https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/main-command-line
}
export function find_entry_point(assembly: string) {
- mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "The runtime must be initialized.");
+ assert_bindings();
const asm = assembly_load(assembly);
if (!asm)
throw new Error("Could not find assembly: " + assembly);
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+import MonoWasmThreads from "consts:monoWasmThreads";
+
import cwraps from "./cwraps";
-import { loaderHelpers } from "./globals";
+import { Module, loaderHelpers } from "./globals";
let spread_timers_maximum = 0;
let pump_count = 0;
}
function prevent_timer_throttling_tick() {
+ Module.maybeExit();
cwraps.mono_wasm_execute_timer();
pump_count++;
mono_background_exec_until_done();
export function schedule_background_exec(): void {
++pump_count;
- globalThis.setTimeout(mono_background_exec_until_done, 0);
+ Module.safeSetTimeout(mono_background_exec_until_done, 0);
}
let lastScheduledTimeoutId: any = undefined;
if (lastScheduledTimeoutId) {
globalThis.clearTimeout(lastScheduledTimeoutId);
lastScheduledTimeoutId = undefined;
+ // NOTE: Multi-threaded Module.safeSetTimeout() does the runtimeKeepalivePush()
+ // and non-Multi-threaded Module.safeSetTimeout does not runtimeKeepalivePush()
+ // but clearTimeout does not runtimeKeepalivePop() so we need to do it here in MT only.
+ if (MonoWasmThreads) Module.runtimeKeepalivePop();
}
- lastScheduledTimeoutId = globalThis.setTimeout(mono_wasm_schedule_timer_tick, shortestDueTimeMs);
+ lastScheduledTimeoutId = Module.safeSetTimeout(mono_wasm_schedule_timer_tick, shortestDueTimeMs);
}
function mono_wasm_schedule_timer_tick() {
+ lastScheduledTimeoutId = undefined;
cwraps.mono_wasm_execute_timer();
}
import { export_linker } from "./exports-linker";
import { endMeasure, MeasuredBlock, startMeasure } from "./profiler";
import { getMemorySnapshot, storeMemorySnapshot, getMemorySnapshotSize } from "./snapshot";
+import { mono_log_debug, mono_log_warn, mono_set_thread_id } from "./logging";
+import { getBrowserThreadID } from "./pthreads/shared";
// legacy
import { init_legacy_exports } from "./net6-legacy/corebindings";
import { cwraps_binding_api, cwraps_mono_api } from "./net6-legacy/exports-legacy";
import { BINDING, MONO } from "./net6-legacy/globals";
-import { mono_log_debug, mono_log_warn } from "./logging";
-import { install_synchronization_context } from "./pthreads/shared";
import { localHeapViewU8 } from "./memory";
-
// default size if MonoConfig.pthreadPoolSize is undefined
const MONO_PTHREAD_POOL_SIZE = 4;
}
export function preRunWorker() {
- const mark = startMeasure();
- try {
- bindings_init();
- endMeasure(mark, MeasuredBlock.preRunWorker);
- } catch (err) {
- loaderHelpers.abort_startup(err, true);
- throw err;
- }
// signal next stage
runtimeHelpers.afterPreRun.promise_control.resolve();
}
// we could enable diagnostics after the snapshot is taken
await mono_wasm_init_diagnostics();
}
+ const tid = getBrowserThreadID();
+ mono_set_thread_id(`0x${tid.toString(16)}-main`);
await instantiateWasmPThreadWorkerPool();
}
bindings_init();
+ if (MonoWasmThreads) {
+ runtimeHelpers.javaScriptExports.install_synchronization_context();
+ }
+
if (!runtimeHelpers.mono_wasm_runtime_is_ready) mono_wasm_runtime_ready();
if (runtimeHelpers.config.startupOptions && INTERNAL.resourceLoader) {
if (WasmEnableLegacyJsInterop && !disableLegacyJsInterop && !ENVIRONMENT_IS_PTHREAD) {
init_legacy_exports();
}
- if (MonoWasmThreads && !ENVIRONMENT_IS_PTHREAD) {
- install_synchronization_context();
- }
initialize_marshalers_to_js();
initialize_marshalers_to_cs();
runtimeHelpers._i52_error_scratch_buffer = <any>Module._malloc(4);
subtle: SubtleCrypto | null,
updateMemoryViews: () => void
runtimeReady: boolean,
+ cspPolicy: boolean,
runtimeModuleUrl: string
nativeModuleUrl: string
removeRunDependency(id: string): void;
addRunDependency(id: string): void;
onConfigLoaded?: (config: MonoConfig, api: RuntimeAPI) => void | Promise<void>;
+ safeSetTimeout(func: Function, timeout: number): number;
+ runtimeKeepalivePush(): void;
+ runtimeKeepalivePop(): void;
+ maybeExit(): void;
}
/// A PromiseController encapsulates a Promise together with easy access to its resolve and reject functions.
To run the debugger tests in the runtime [built with enabled support for multi-threading](#building-the-runtime) we use:
```
dotnet test src/mono/wasm/debugger/DebuggerTestSuite -e RuntimeConfiguration=Debug -e Configuration=Debug -e DebuggerHost=chrome -e WasmEnableThreads=true -e WASM_TESTS_USING_VARIANT=multithreaded
-```
\ No newline at end of file
+```
+
+## JS interop on dedicated threads ##
+FIXME: better documentation, better public API.
+The JavaScript objects have thread (web worker) affinity. You can't use DOM, WebSocket or their promises on any other web worker than the original one.
+Therefore we have JSSynchronizationContext which is helping the user code to stay on that thread. Instead of finishing the `await` continuation on any threadpool thread.
+Because browser events (for example incoming web socket message) could be fired after any synchronous code of the thread finished, we have to treat threads (web workers) which want to do JS interop as un-managed resource. It's lifetime should be managed by the user.
+As we are prototyping it, we have [WebWorker](..\..\libraries\System.Runtime.InteropServices.JavaScript\src\System\Runtime\InteropServices\JavaScript\WebWorker.cs) as tentative API which should be used to start such dedicated threads.
<EmccExportedRuntimeMethod Include="removeRunDependency" />
<EmccExportedRuntimeMethod Include="addRunDependency" />
<EmccExportedRuntimeMethod Include="addFunction" />
+ <EmccExportedRuntimeMethod Include="safeSetTimeout" />
+ <EmccExportedRuntimeMethod Include="runtimeKeepalivePush" />
+ <EmccExportedRuntimeMethod Include="runtimeKeepalivePop" />
+ <EmccExportedRuntimeMethod Include="maybeExit" />
<EmccExportedFunction Include="_free" />
<EmccExportedFunction Include="_htons" />
<ItemGroup Condition="'$(MonoWasmThreads)' == 'true'">
<EmccExportedFunction Include="_emscripten_main_runtime_thread_id" />
</ItemGroup>
+ <ItemGroup Condition="'$(MonoWasmThreads)' == 'true'">
+ <EmccExportedFunction Include="_emscripten_main_runtime_thread_id" />
+ </ItemGroup>
<PropertyGroup>
<_EmccExportedLibraryFunction>"[@(EmccExportedLibraryFunction -> '%27%(Identity)%27', ',')]"</_EmccExportedLibraryFunction>
<_EmccExportedRuntimeMethods>"[@(EmccExportedRuntimeMethod -> '%27%(Identity)%27', ',')]"</_EmccExportedRuntimeMethods>
double SystemNative_GetCpuUtilization(ProcessCpuInformation* previousCpuInfo)
{
-#ifdef HAVE_GETRUSAGE
+#if defined(HAVE_GETRUSAGE) && !defined(HOST_BROWSER)
uint64_t kernelTime = 0;
uint64_t userTime = 0;