From 43f6941d316f3648c7151b605bf242322bc6b226 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 23 May 2023 14:55:50 +0200 Subject: [PATCH 1/8] threading cleanup --- .../JavaScript/JSSynchronizationContext.cs | 102 +++++++++++------- .../Threading/ThreadPool.Browser.Mono.cs | 13 +-- .../Threading/TimerQueue.Browser.Mono.cs | 22 ++-- src/mono/mono/metadata/gc.c | 2 +- src/mono/mono/metadata/sgen-mono.c | 2 +- src/mono/mono/mini/mini-wasm.c | 97 ++++------------- src/mono/mono/mini/mini-wasm.h | 2 +- src/mono/mono/utils/mono-threads-wasm.c | 67 ++++++++++-- src/mono/mono/utils/mono-threads-wasm.h | 10 ++ src/mono/mono/utils/mono-threads.c | 15 ++- src/mono/mono/utils/mono-threads.h | 4 +- .../wasm/browser-threads-minimal/Program.cs | 19 +++- .../browser-threads-minimal/fetchhelper.js | 8 +- .../wasm/browser-threads-minimal/main.js | 20 ++-- src/mono/wasm/runtime/corebindings.c | 5 +- src/mono/wasm/runtime/cwraps.ts | 4 +- src/mono/wasm/runtime/es6/dotnet.es6.lib.js | 2 +- src/mono/wasm/runtime/exports-linker.ts | 4 +- src/mono/wasm/runtime/invoke-cs.ts | 6 +- src/mono/wasm/runtime/invoke-js.ts | 3 + src/mono/wasm/runtime/managed-exports.ts | 14 ++- src/mono/wasm/runtime/net6-legacy/cs-to-js.ts | 14 ++- .../runtime/net6-legacy/exports-legacy.ts | 4 +- src/mono/wasm/runtime/net6-legacy/js-to-cs.ts | 4 + .../runtime/net6-legacy/method-binding.ts | 34 ++---- .../wasm/runtime/net6-legacy/method-calls.ts | 20 ++-- .../wasm/runtime/pthreads/shared/index.ts | 25 ++++- src/mono/wasm/runtime/scheduling.ts | 27 +++-- src/mono/wasm/runtime/startup.ts | 6 +- src/mono/wasm/runtime/strings.ts | 10 +- src/mono/wasm/threads.md | 4 +- 31 files changed, 318 insertions(+), 251 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs index 2752225c03265..6abfa0550635a 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs @@ -11,22 +11,26 @@ using System.Runtime.CompilerServices; using QueueType = System.Threading.Channels.Channel; -namespace System.Runtime.InteropServices.JavaScript { +namespace System.Runtime.InteropServices.JavaScript +{ /// /// 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 /// thread-affinity-having APIs like WebSockets, fetch, WebGL, etc. /// Callbacks are processed during event loop turns via the runtime's background job system. /// - internal sealed unsafe class JSSynchronizationContext : SynchronizationContext { + internal sealed unsafe class JSSynchronizationContext : SynchronizationContext + { public readonly Thread MainThread; - internal readonly struct WorkItem { + internal readonly struct WorkItem + { public readonly SendOrPostCallback Callback; public readonly object? Data; public readonly ManualResetEventSlim? Signal; - public WorkItem (SendOrPostCallback callback, object? data, ManualResetEventSlim? signal) { + public WorkItem(SendOrPostCallback callback, object? data, ManualResetEventSlim? signal) + { Callback = callback; Data = data; Signal = signal; @@ -35,11 +39,10 @@ public WorkItem (SendOrPostCallback callback, object? data, ManualResetEventSlim private static JSSynchronizationContext? MainThreadSynchronizationContext; private readonly QueueType Queue; - private readonly Action _DataIsAvailable; - private JSSynchronizationContext (Thread mainThread) - : this ( - mainThread, + private JSSynchronizationContext() + : this( + Thread.CurrentThread, Channel.CreateUnbounded( new UnboundedChannelOptions { SingleWriter = false, SingleReader = true, AllowSynchronousContinuations = true } ) @@ -47,19 +50,22 @@ private JSSynchronizationContext (Thread mainThread) { } - private JSSynchronizationContext (Thread mainThread, QueueType queue) { + private JSSynchronizationContext(Thread mainThread, QueueType queue) + { MainThread = mainThread; Queue = queue; - _DataIsAvailable = DataIsAvailable; } - public override SynchronizationContext CreateCopy () { + public override SynchronizationContext CreateCopy() + { return new JSSynchronizationContext(MainThread, Queue); } - private void AwaitNewData () { + private void AwaitNewData() + { var vt = Queue.Reader.WaitToReadAsync(); - if (vt.IsCompleted) { + if (vt.IsCompleted) + { DataIsAvailable(); return; } @@ -69,31 +75,34 @@ private void AwaitNewData () { // fire a callback that will schedule a background job to pump the queue on the main thread. var awaiter = vt.AsTask().ConfigureAwait(false).GetAwaiter(); // UnsafeOnCompleted avoids spending time flowing the execution context (we don't need it.) - awaiter.UnsafeOnCompleted(_DataIsAvailable); + awaiter.UnsafeOnCompleted(DataIsAvailable); } - private void DataIsAvailable () { - // 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. - ScheduleBackgroundJob((void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler); + private void DataIsAvailable() + { + MainThreadScheduleBackgroundJob((void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler); } - public override void Post (SendOrPostCallback d, object? state) { + public override void Post(SendOrPostCallback d, object? state) + { var workItem = new WorkItem(d, state, null); if (!Queue.Writer.TryWrite(workItem)) throw new Exception("Internal error"); } // This path can only run when threading is enabled - #pragma warning disable CA1416 +#pragma warning disable CA1416 - public override void Send (SendOrPostCallback d, object? state) { - if (Thread.CurrentThread == MainThread) { + public override void Send(SendOrPostCallback d, object? state) + { + if (Thread.CurrentThread == MainThread) + { d(state); return; } - using (var signal = new ManualResetEventSlim(false)) { + using (var signal = new ManualResetEventSlim(false)) + { var workItem = new WorkItem(d, state, signal); if (!Queue.Writer.TryWrite(workItem)) throw new Exception("Internal error"); @@ -102,40 +111,51 @@ public override void Send (SendOrPostCallback d, object? state) { } } - internal static void Install () { - MainThreadSynchronizationContext ??= new JSSynchronizationContext(Thread.CurrentThread); - - SynchronizationContext.SetSynchronizationContext(MainThreadSynchronizationContext); - MainThreadSynchronizationContext.AwaitNewData(); + internal static void Install() + { + var ctx = Current as JSSynchronizationContext; + if (ctx == null) + { + ctx = new JSSynchronizationContext(); + MainThreadSynchronizationContext = ctx; + SetSynchronizationContext(ctx); + } + ctx.AwaitNewData(); } [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern unsafe void ScheduleBackgroundJob(void* callback); + internal static extern unsafe void MainThreadScheduleBackgroundJob(void* callback); #pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] - private static unsafe void BackgroundJobHandler () { +#pragma warning restore CS3016 + // this callback will arrive on the bound thread + private static unsafe void BackgroundJobHandler() + { MainThreadSynchronizationContext!.Pump(); } - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] - private static unsafe void RequestPumpCallback () { - ScheduleBackgroundJob((void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler); - } - - private void Pump () { - try { - while (Queue.Reader.TryRead(out var item)) { - try { + private void Pump() + { + try + { + while (Queue.Reader.TryRead(out var item)) + { + try + { item.Callback(item.Data); // While we would ideally have a catch block here and do something to dispatch/forward unhandled // exceptions, the standard threadpool (and thus standard synchronizationcontext) have zero // error handling, so for consistency with them we do nothing. Don't throw in SyncContext callbacks. - } finally { + } + finally + { item.Signal?.Set(); } } - } finally { + } + finally + { // If an item throws, we want to ensure that the next pump gets scheduled appropriately regardless. AwaitNewData(); } diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs index a5e54288a2920..6821cac9e224f 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs @@ -28,7 +28,7 @@ public bool Unregister(WaitHandle? waitObject) } } - public static partial class ThreadPool + public static unsafe partial class ThreadPool { // Indicates whether the thread pool should yield the thread from the dispatch loop to the runtime periodically so that // the runtime may use the thread for processing other work @@ -79,7 +79,7 @@ internal static void RequestWorkerThread() if (_callbackQueued) return; _callbackQueued = true; - QueueCallback(); + MainThreadScheduleBackgroundJob((void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler); } internal static void NotifyWorkItemProgress() @@ -110,12 +110,13 @@ private static RegisteredWaitHandle RegisterWaitForSingleObject( throw new PlatformNotSupportedException(); } - [DynamicDependency("Callback")] [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern void QueueCallback(); + internal static extern unsafe void MainThreadScheduleBackgroundJob(void* callback); - private static void Callback() - { +#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#pragma warning restore CS3016 + private static unsafe void BackgroundJobHandler () { _callbackQueued = false; ThreadPoolWorkQueue.Dispatch(); } 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 ba5e678e87427..cc90b9d2145a6 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 @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; namespace System.Threading { @@ -13,7 +14,7 @@ namespace System.Threading // Based on TimerQueue.Portable.cs // Not thread safe // - internal partial class TimerQueue + internal unsafe partial class TimerQueue { private static List? s_scheduledTimers; private static List? s_scheduledTimersToFire; @@ -27,19 +28,20 @@ private TimerQueue(int _) { } - [DynamicDependency("TimeoutCallback")] // This replaces the current pending setTimeout with shorter one [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern void SetTimeout(int timeout); + private static extern unsafe void MainThreadScheduleTimer(void* callback, int shortestDueTimeMs); - // Called by mini-wasm.c:mono_set_timeout_exec - private static void TimeoutCallback() - { +#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#pragma warning restore CS3016 + // Called by mini-wasm.c:mono_wasm_execute_timer + private static unsafe void TimerHandler () { // always only have one scheduled at a time s_shortestDueTimeMs = long.MaxValue; long currentTimeMs = TickCount64; - ReplaceNextSetTimeout(PumpTimerQueue(currentTimeMs), currentTimeMs); + ReplaceNextTimer(PumpTimerQueue(currentTimeMs), currentTimeMs); } // this is called with shortest of timers scheduled on the particular TimerQueue @@ -57,13 +59,13 @@ private bool SetTimer(uint actualDuration) _scheduledDueTimeMs = currentTimeMs + (int)actualDuration; - ReplaceNextSetTimeout(ShortestDueTime(), currentTimeMs); + ReplaceNextTimer(ShortestDueTime(), currentTimeMs); return true; } // shortest time of all TimerQueues - private static void ReplaceNextSetTimeout(long shortestDueTimeMs, long currentTimeMs) + private static void ReplaceNextTimer(long shortestDueTimeMs, long currentTimeMs) { if (shortestDueTimeMs == long.MaxValue) { @@ -77,7 +79,7 @@ private static void ReplaceNextSetTimeout(long shortestDueTimeMs, long currentTi int shortestWait = Math.Max((int)(shortestDueTimeMs - currentTimeMs), 0); // this would cancel the previous schedule and create shorter one // it is expensive call - SetTimeout(shortestWait); + MainThreadScheduleTimer((void*)(delegate* unmanaged[Cdecl])&TimerHandler, shortestWait); } } diff --git a/src/mono/mono/metadata/gc.c b/src/mono/mono/metadata/gc.c index f5f74cfd5073c..9f8f4918214da 100644 --- a/src/mono/mono/metadata/gc.c +++ b/src/mono/mono/metadata/gc.c @@ -695,7 +695,7 @@ mono_wasm_gc_finalize_notify (void) /* use this if we are going to start the finalizer thread on wasm. */ mono_coop_sem_post (&finalizer_sem); #else - mono_threads_schedule_background_job (mono_runtime_do_background_work); + mono_main_thread_schedule_background_job (mono_runtime_do_background_work); #endif } diff --git a/src/mono/mono/metadata/sgen-mono.c b/src/mono/mono/metadata/sgen-mono.c index d23f4f57ab08c..cb1e1ba8a2158 100644 --- a/src/mono/mono/metadata/sgen-mono.c +++ b/src/mono/mono/metadata/sgen-mono.c @@ -2897,7 +2897,7 @@ sgen_client_binary_protocol_collection_end (int minor_gc_count, int generation, void sgen_client_schedule_background_job (void (*cb)(void)) { - mono_threads_schedule_background_job (cb); + mono_main_thread_schedule_background_job (cb); } #endif diff --git a/src/mono/mono/mini/mini-wasm.c b/src/mono/mono/mini/mini-wasm.c index c0a64db680403..b0b923818e00e 100644 --- a/src/mono/mono/mini/mini-wasm.c +++ b/src/mono/mono/mini/mini-wasm.c @@ -436,11 +436,10 @@ mono_arch_get_delegate_invoke_impl (MonoMethodSignature *sig, gboolean has_targe //functions exported to be used by JS G_BEGIN_DECLS -EMSCRIPTEN_KEEPALIVE void mono_set_timeout_exec (void); +EMSCRIPTEN_KEEPALIVE void mono_wasm_execute_timer (void); //JS functions imported that we use -extern void mono_set_timeout (int t); -extern void mono_wasm_queue_tp_cb (void); +extern void mono_wasm_schedule_timer (int shortestDueTimeMs); G_END_DECLS void mono_background_exec (void); @@ -558,6 +557,8 @@ mono_init_native_crash_info (void) #endif +static void *timer_handler; + #ifdef HOST_BROWSER void @@ -582,102 +583,40 @@ mono_thread_state_init_from_handle (MonoThreadUnwindState *tctx, MonoThreadInfo } EMSCRIPTEN_KEEPALIVE void -mono_set_timeout_exec (void) +mono_wasm_execute_timer (void) { - MONO_ENTER_GC_UNSAFE; - ERROR_DECL (error); - - static MonoMethod *method = NULL; - if (method == NULL) { - MonoClass *klass = mono_class_load_from_name (mono_defaults.corlib, "System.Threading", "TimerQueue"); - g_assert (klass); - - method = mono_class_get_method_from_name_checked (klass, "TimeoutCallback", -1, 0, error); - mono_error_assert_ok (error); - g_assert (method); - } - - MonoObject *exc = NULL; - - mono_runtime_try_invoke (method, NULL, NULL, &exc, error); - - //YES we swallow exceptions cuz there's nothing much we can do from here. - //FIXME Maybe call the unhandled exception function? - if (!is_ok (error)) { - g_printerr ("timeout callback failed due to %s\n", mono_error_get_message (error)); - mono_error_cleanup (error); - } - - if (exc) { - char *type_name = mono_type_get_full_name (mono_object_class (exc)); - g_printerr ("timeout callback threw a %s\n", type_name); - g_free (type_name); - } - MONO_EXIT_GC_UNSAFE; + background_job_cb cb = timer_handler; + cb (); } + #endif void -mono_wasm_set_timeout (int timeout) +mono_wasm_main_thread_schedule_timer (void *timerHandler, int shortestDueTimeMs) { + timer_handler = timerHandler; #ifdef HOST_BROWSER #ifndef DISABLE_THREADS if (!mono_threads_wasm_is_browser_thread ()) { - mono_threads_wasm_async_run_in_main_thread_vi ((void (*)(gpointer))mono_wasm_set_timeout, GINT_TO_POINTER(timeout)); + mono_threads_wasm_async_run_in_main_thread_vi ((void (*)(gpointer))mono_wasm_schedule_timer, GINT_TO_POINTER(shortestDueTimeMs)); return; } #endif - mono_set_timeout (timeout); + mono_wasm_schedule_timer (shortestDueTimeMs); #endif } -static void -tp_cb (void) -{ - ERROR_DECL (error); - - static MonoMethod *method = NULL; - if (method == NULL) { - MonoClass *klass = mono_class_load_from_name (mono_defaults.corlib, "System.Threading", "ThreadPool"); - g_assert (klass); - - method = mono_class_get_method_from_name_checked (klass, "Callback", -1, 0, error); - mono_error_assert_ok (error); - g_assert (method); - } - - MonoObject *exc = NULL; - - mono_runtime_try_invoke (method, NULL, NULL, &exc, error); - - if (!is_ok (error)) { - g_printerr ("ThreadPool Callback failed due to error: %s\n", mono_error_get_message (error)); - mono_error_cleanup (error); - } - - if (exc) { - char *type_name = mono_type_get_full_name (mono_object_class (exc)); - g_printerr ("ThreadPool Callback threw an unhandled exception of type %s\n", type_name); - g_free (type_name); - } -} - -#ifdef HOST_BROWSER -void -mono_wasm_queue_tp_cb (void) -{ - mono_threads_schedule_background_job (tp_cb); -} -#endif - void mono_arch_register_icall (void) { #ifdef HOST_BROWSER - mono_add_internal_call_internal ("System.Threading.TimerQueue::SetTimeout", mono_wasm_set_timeout); - mono_add_internal_call_internal ("System.Threading.ThreadPool::QueueCallback", mono_wasm_queue_tp_cb); -#endif + mono_add_internal_call_internal ("System.Threading.TimerQueue::MainThreadScheduleTimer", mono_wasm_main_thread_schedule_timer); + 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); +#endif /* DISABLE_THREADS */ +#endif /* HOST_BROWSER */ } void diff --git a/src/mono/mono/mini/mini-wasm.h b/src/mono/mono/mini/mini-wasm.h index 61e81e8a81fbd..9de72fdd840e8 100644 --- a/src/mono/mono/mini/mini-wasm.h +++ b/src/mono/mono/mini/mini-wasm.h @@ -96,7 +96,7 @@ typedef struct { // sdks/wasm/driver.c is C and uses this G_EXTERN_C void mono_wasm_enable_debugging (int log_level); -void mono_wasm_set_timeout (int timeout); +void mono_wasm_main_thread_schedule_timer (void *timerHandler, int shortestDueTimeMs); int mono_wasm_assembly_already_added (const char *assembly_name); const unsigned char *mono_wasm_get_assembly_bytes (const char *name, unsigned int *size); diff --git a/src/mono/mono/utils/mono-threads-wasm.c b/src/mono/mono/utils/mono-threads-wasm.c index 6a33dfa0d5bc2..7485c3729a965 100644 --- a/src/mono/mono/utils/mono-threads-wasm.c +++ b/src/mono/mono/utils/mono-threads-wasm.c @@ -345,27 +345,55 @@ mono_memory_barrier_process_wide (void) G_EXTERN_C extern void schedule_background_exec (void); -/* jobs is not protected by a mutex, only access from a single thread! */ -static GSList *jobs; - void -mono_threads_schedule_background_job (background_job_cb cb) +mono_main_thread_schedule_background_job (background_job_cb cb) { #ifndef DISABLE_THREADS if (!mono_threads_wasm_is_browser_thread ()) { - THREADS_DEBUG ("worker %p queued job %p\n", (gpointer)pthread_self(), (gpointer) cb); - mono_threads_wasm_async_run_in_main_thread_vi ((void (*)(gpointer))mono_threads_schedule_background_job, cb); + THREADS_DEBUG ("mono_main_thread_schedule_background_job1: thread %p queued job %p to main thread\n", (gpointer)pthread_self(), (gpointer) cb); + mono_threads_wasm_async_run_in_main_thread_vi ((void (*)(gpointer))mono_current_thread_schedule_background_job, cb); return; } -#endif +#endif /*DISABLE_THREADS*/ + THREADS_DEBUG ("mono_main_thread_schedule_background_job2: thread %p queued job %p to current thread\n", (gpointer)pthread_self(), (gpointer) cb); + mono_current_thread_schedule_background_job (cb); +} + +#ifndef DISABLE_THREADS +MonoNativeTlsKey jobs_key; +#else /* DISABLE_THREADS */ +GSList *jobs; +#endif /* DISABLE_THREADS */ - THREADS_DEBUG ("main thread queued job %p\n", (gpointer) cb); +void +mono_current_thread_schedule_background_job (background_job_cb cb) +{ +#ifdef DISABLE_THREADS if (!jobs) schedule_background_exec (); if (!g_slist_find (jobs, (gconstpointer)cb)) jobs = g_slist_prepend (jobs, (gpointer)cb); + +#else /*DISABLE_THREADS*/ + + GSList *jobs = mono_native_tls_get_value (jobs_key); + THREADS_DEBUG ("mono_current_thread_schedule_background_job1: thread %p queuing job %p into %p\n", (gpointer)pthread_self(), (gpointer) cb, (gpointer) jobs); + if (!jobs) + { + THREADS_DEBUG ("mono_current_thread_schedule_background_job2: thread %p calling schedule_background_exec before job %p\n", (gpointer)pthread_self(), (gpointer) cb); + schedule_background_exec (); + } + + if (!g_slist_find (jobs, (gconstpointer)cb)) + { + jobs = g_slist_prepend (jobs, (gpointer)cb); + mono_native_tls_set_value (jobs_key, jobs); + THREADS_DEBUG ("mono_current_thread_schedule_background_job3: thread %p queued job %p\n", (gpointer)pthread_self(), (gpointer) cb); + } + +#endif /*DISABLE_THREADS*/ } G_EXTERN_C @@ -377,15 +405,21 @@ EMSCRIPTEN_KEEPALIVE void mono_background_exec (void) { MONO_ENTER_GC_UNSAFE; -#ifndef DISABLE_THREADS - g_assert (mono_threads_wasm_is_browser_thread ()); -#endif +#ifdef DISABLE_THREADS GSList *j = jobs, *cur; jobs = NULL; +#else /* DISABLE_THREADS */ + THREADS_DEBUG ("mono_background_exec on thread %p started\n", (gpointer)pthread_self()); + GSList *jobs = mono_native_tls_get_value (jobs_key); + GSList *j = jobs, *cur; + mono_native_tls_set_value (jobs_key, NULL); +#endif /* DISABLE_THREADS */ for (cur = j; cur; cur = cur->next) { background_job_cb cb = (background_job_cb)cur->data; + THREADS_DEBUG ("mono_background_exec on thread %p running job %p \n", (gpointer)pthread_self(), (gpointer)cb); cb (); + THREADS_DEBUG ("mono_background_exec on thread %p done job %p \n", (gpointer)pthread_self(), (gpointer)cb); } g_slist_free (j); MONO_EXIT_GC_UNSAFE; @@ -463,6 +497,17 @@ mono_threads_wasm_async_run_in_main_thread_vii (void (*func) (gpointer, gpointer emscripten_async_run_in_main_runtime_thread (EM_FUNC_SIG_VII, func, user_data1, user_data2); } +void +mono_threads_wasm_async_run_in_target_thread (pthread_t target_thread, void (*func) (void)) +{ + emscripten_dispatch_to_thread_async (target_thread, EM_FUNC_SIG_V, func, NULL); +} + +void +mono_threads_wasm_async_run_in_target_thread_vi (pthread_t target_thread, void (*func) (gpointer), gpointer user_data) +{ + emscripten_dispatch_to_thread_async (target_thread, EM_FUNC_SIG_VI, func, NULL, user_data); +} #endif /* DISABLE_THREADS */ diff --git a/src/mono/mono/utils/mono-threads-wasm.h b/src/mono/mono/utils/mono-threads-wasm.h index c06d8501e1ec3..95f8e6392906b 100644 --- a/src/mono/mono/utils/mono-threads-wasm.h +++ b/src/mono/mono/utils/mono-threads-wasm.h @@ -43,6 +43,12 @@ mono_threads_wasm_async_run_in_main_thread_vi (void (*func)(gpointer), gpointer void mono_threads_wasm_async_run_in_main_thread_vii (void (*func)(gpointer, gpointer), gpointer user_data1, gpointer user_data2); +void +mono_threads_wasm_async_run_in_target_thread (pthread_t target_thread, void (*func) (void)); + +void +mono_threads_wasm_async_run_in_target_thread_vi (pthread_t target_thread, void (*func) (gpointer), gpointer user_data); + static inline int32_t mono_wasm_atomic_wait_i32 (volatile int32_t *addr, int32_t expected, int32_t timeout_ns) @@ -59,6 +65,10 @@ mono_wasm_atomic_wait_i32 (volatile int32_t *addr, int32_t expected, int32_t tim // 2 == "timed-out", timeout expired before thread was woken up return __builtin_wasm_memory_atomic_wait32((int32_t*)addr, expected, timeout_ns); } + +extern MonoNativeTlsKey jobs_key; +#else /* DISABLE_THREADS */ +extern GSList *jobs; #endif /* DISABLE_THREADS */ // Called from register_thread when a pthread attaches to the runtime diff --git a/src/mono/mono/utils/mono-threads.c b/src/mono/mono/utils/mono-threads.c index 026c290fb3224..b364d9467b1d5 100644 --- a/src/mono/mono/utils/mono-threads.c +++ b/src/mono/mono/utils/mono-threads.c @@ -515,7 +515,14 @@ register_thread (MonoThreadInfo *info) /* for wasm, the stack can be placed at the start of the linear memory */ #ifndef TARGET_WASM g_assert (staddr); -#endif +#endif /* TARGET_WASM */ + +#ifdef HOST_WASM +#ifndef DISABLE_THREADS + mono_native_tls_set_value (jobs_key, NULL); +#endif /* DISABLE_THREADS */ +#endif /* HOST_WASM */ + g_assert (stsize); info->stack_start_limit = staddr; info->stack_end = staddr + stsize; @@ -962,6 +969,12 @@ mono_thread_info_init (size_t info_size) mono_threads_suspend_policy_init (); +#ifdef HOST_WASM +#ifndef DISABLE_THREADS + res = mono_native_tls_alloc (&jobs_key, NULL); +#endif /* DISABLE_THREADS */ +#endif /* HOST_BROWSER */ + #ifdef HOST_WIN32 res = mono_native_tls_alloc (&thread_info_key, NULL); res = mono_native_tls_alloc (&thread_exited_key, NULL); diff --git a/src/mono/mono/utils/mono-threads.h b/src/mono/mono/utils/mono-threads.h index cdfc9f6133671..5a96c598e15d9 100644 --- a/src/mono/mono/utils/mono-threads.h +++ b/src/mono/mono/utils/mono-threads.h @@ -846,7 +846,9 @@ void mono_threads_join_unlock (void); #ifdef HOST_WASM typedef void (*background_job_cb)(void); -void mono_threads_schedule_background_job (background_job_cb cb); +void mono_main_thread_schedule_background_job (background_job_cb cb); +void mono_current_thread_schedule_background_job (background_job_cb cb); +void mono_bound_thread_schedule_background_job (int bound_thread, background_job_cb cb); #endif #ifdef USE_WINDOWS_BACKEND diff --git a/src/mono/sample/wasm/browser-threads-minimal/Program.cs b/src/mono/sample/wasm/browser-threads-minimal/Program.cs index 4379c9092bf61..f5c50bd97849d 100644 --- a/src/mono/sample/wasm/browser-threads-minimal/Program.cs +++ b/src/mono/sample/wasm/browser-threads-minimal/Program.cs @@ -21,15 +21,21 @@ public static int Main(string[] args) [JSExport] public static async Task TestCanStartThread() { + Console.WriteLine($"smoke: TestCanStartThread 1 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); var tcs = new TaskCompletionSource(); var t = new Thread(() => { + Console.WriteLine($"smoke: TestCanStartThread 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); var childTid = Thread.CurrentThread.ManagedThreadId; tcs.SetResult(childTid); + Console.WriteLine($"smoke: TestCanStartThread 3 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); }); t.Start(); + Console.WriteLine($"smoke: TestCanStartThread 4 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); var childTid = await tcs.Task; + Console.WriteLine($"smoke: TestCanStartThread 5 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); t.Join(); + Console.WriteLine($"smoke: TestCanStartThread 6 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); if (childTid == Thread.CurrentThread.ManagedThreadId) throw new Exception("Child thread ran on same thread as parent"); } @@ -56,19 +62,26 @@ public static async Task TestCallSetTimeoutOnWorker() [JSExport] public static async Task FetchBackground(string url) { + Console.WriteLine($"smoke: FetchBackground 1 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); var t = Task.Run(async () => { - using var import = await JSHost.ImportAsync(fetchhelper, "./fetchhelper.js"); + Console.WriteLine($"smoke: FetchBackground 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); + var x=JSHost.ImportAsync(fetchhelper, "./fetchhelper.js"); + Console.WriteLine($"smoke: FetchBackground 3A ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); + using var import = await x; + Console.WriteLine($"smoke: FetchBackground 3B ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); var r = await GlobalThisFetch(url); + Console.WriteLine($"smoke: FetchBackground 4 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); var ok = (bool)r.GetPropertyAsBoolean("ok"); - Console.WriteLine($"XYZ: FetchBackground fetch returned to thread:{Thread.CurrentThread.ManagedThreadId}, ok: {ok}"); + Console.WriteLine($"smoke: FetchBackground 5 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); if (ok) { var text = await FetchHelperResponseText(r); - Console.WriteLine($"XYZ: FetchBackground fetch returned to thread:{Thread.CurrentThread.ManagedThreadId}, text: {text}"); + Console.WriteLine($"smoke: FetchBackground 6 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); return text; } + Console.WriteLine($"smoke: FetchBackground 7 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); return "not-ok"; }); var r = await t; diff --git a/src/mono/sample/wasm/browser-threads-minimal/fetchhelper.js b/src/mono/sample/wasm/browser-threads-minimal/fetchhelper.js index 928492378fc6c..ad31e74dabb87 100644 --- a/src/mono/sample/wasm/browser-threads-minimal/fetchhelper.js +++ b/src/mono/sample/wasm/browser-threads-minimal/fetchhelper.js @@ -1,11 +1,11 @@ -function delay(timeoutMs) { - return new Promise(resolve => setTimeout(resolve, timeoutMs)); +export function delay(timeoutMs) { + return new Promise(resolve => setTimeout(resolve, timeoutMs)); } export async function responseText(response) /* Promise */ { - console.log("artificially waiting for response for 25 seconds"); - await delay(25000); + console.log("artificially waiting for response for 5 seconds"); + await delay(5000); console.log("artificial waiting done"); return await response.text(); } diff --git a/src/mono/sample/wasm/browser-threads-minimal/main.js b/src/mono/sample/wasm/browser-threads-minimal/main.js index 68b7227876909..b90abdbdf5104 100644 --- a/src/mono/sample/wasm/browser-threads-minimal/main.js +++ b/src/mono/sample/wasm/browser-threads-minimal/main.js @@ -19,36 +19,36 @@ try { await exports.Sample.Test.TestCanStartThread(); console.log("smoke: TestCanStartThread done"); - console.log ("smoke: running TestCallSetTimeoutOnWorker"); + console.log("smoke: running TestCallSetTimeoutOnWorker"); await exports.Sample.Test.TestCallSetTimeoutOnWorker(); - console.log ("smoke: TestCallSetTimeoutOnWorker done"); + console.log("smoke: TestCallSetTimeoutOnWorker done"); - console.log ("smoke: running FetchBackground(blurst.txt)"); + console.log("smoke: running FetchBackground(blurst.txt)"); let s = await exports.Sample.Test.FetchBackground("./blurst.txt"); - console.log ("smoke: FetchBackground(blurst.txt) done"); + console.log("smoke: FetchBackground(blurst.txt) done"); if (!s.startsWith("It was the best of times, it was the blurst of times.")) { const msg = `Unexpected FetchBackground result ${s}`; document.getElementById("out").innerHTML = msg; - throw new Error (msg); + throw new Error(msg); } - console.log ("smoke: running FetchBackground(missing)"); + console.log("smoke: running FetchBackground(missing)"); s = await exports.Sample.Test.FetchBackground("./missing.txt"); - console.log ("smoke: FetchBackground(missing) done"); + console.log("smoke: FetchBackground(missing) done"); if (s !== "not-ok") { const msg = `Unexpected FetchBackground(missing) result ${s}`; document.getElementById("out").innerHTML = msg; - throw new Error (msg); + throw new Error(msg); } - console.log ("smoke: running TaskRunCompute"); + console.log("smoke: running TaskRunCompute"); const r1 = await exports.Sample.Test.RunBackgroundTaskRunCompute(); if (r1 !== 524) { const msg = `Unexpected result ${r1} from RunBackgorundTaskRunCompute()`; document.getElementById("out").innerHTML = msg; throw new Error(msg); } - console.log ("smoke: TaskRunCompute done"); + console.log("smoke: TaskRunCompute done"); let exit_code = await runMain(assemblyName, []); diff --git a/src/mono/wasm/runtime/corebindings.c b/src/mono/wasm/runtime/corebindings.c index 8d92fdc5e0ea7..4d6394610bdab 100644 --- a/src/mono/wasm/runtime/corebindings.c +++ b/src/mono/wasm/runtime/corebindings.c @@ -25,7 +25,8 @@ extern void mono_wasm_bind_cs_function(MonoString **fully_qualified_name, int si extern void mono_wasm_marshal_promise(void *data); typedef void (*background_job_cb)(void); -void mono_threads_schedule_background_job (background_job_cb cb); +void mono_main_thread_schedule_background_job (background_job_cb cb); +void mono_current_thread_schedule_background_job (background_job_cb cb); #ifndef DISABLE_LEGACY_JS_INTEROP extern void mono_wasm_invoke_js_with_args_ref (int js_handle, MonoString **method, MonoArray **args, int *is_exception, MonoObject **result); @@ -52,8 +53,6 @@ extern int mono_wasm_index_of(MonoString **exceptionMessage, MonoString **cultur void bindings_initialize_internals (void) { - mono_add_internal_call ("System.Runtime.InteropServices.JavaScript.JSSynchronizationContext::ScheduleBackgroundJob", mono_threads_schedule_background_job); - mono_add_internal_call ("Interop/Runtime::ReleaseCSOwnedObject", mono_wasm_release_cs_owned_object); mono_add_internal_call ("Interop/Runtime::BindJSFunction", mono_wasm_bind_js_function); mono_add_internal_call ("Interop/Runtime::InvokeJSFunction", mono_wasm_invoke_bound_function); diff --git a/src/mono/wasm/runtime/cwraps.ts b/src/mono/wasm/runtime/cwraps.ts index 715a3936cec07..f18a313504155 100644 --- a/src/mono/wasm/runtime/cwraps.ts +++ b/src/mono/wasm/runtime/cwraps.ts @@ -42,7 +42,7 @@ const fn_signatures: SigLine[] = [ [true, "mono_wasm_parse_runtime_options", null, ["number", "number"]], [true, "mono_wasm_strdup", "number", ["string"]], [true, "mono_background_exec", null, []], - [true, "mono_set_timeout_exec", null, []], + [true, "mono_wasm_execute_timer", null, []], [true, "mono_wasm_load_icu_data", "number", ["number"]], [false, "mono_wasm_add_assembly", "number", ["string", "number", "number"]], [true, "mono_wasm_add_satellite_assembly", "void", ["string", "string", "number", "number"]], @@ -162,7 +162,7 @@ export interface t_Cwraps { mono_wasm_strdup(value: string): number; mono_wasm_parse_runtime_options(length: number, argv: VoidPtr): void; mono_background_exec(): void; - mono_set_timeout_exec(): void; + mono_wasm_execute_timer(): void; mono_wasm_load_icu_data(offset: VoidPtr): number; mono_wasm_add_assembly(name: string, data: VoidPtr, size: number): number; mono_wasm_add_satellite_assembly(name: string, culture: string, data: VoidPtr, size: number): void; diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js index ab293f667aea8..3f5fcabf62656 100644 --- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js @@ -70,7 +70,7 @@ const DotnetSupportLib = { // --- keep in sync with exports.ts --- let linked_functions = [ // mini-wasm.c - "mono_set_timeout", + "mono_wasm_schedule_timer", // mini-wasm-debugger.c "mono_wasm_asm_loaded", diff --git a/src/mono/wasm/runtime/exports-linker.ts b/src/mono/wasm/runtime/exports-linker.ts index f4be31cfa0912..4da8fbd496401 100644 --- a/src/mono/wasm/runtime/exports-linker.ts +++ b/src/mono/wasm/runtime/exports-linker.ts @@ -13,7 +13,7 @@ import { mono_interp_jit_wasm_jit_call_trampoline, mono_interp_invoke_wasm_jit_c 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_set_timeout, schedule_background_exec } from "./scheduling"; +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_on_runtime_server_init, mono_wasm_event_pipe_early_startup_callback } from "./diagnostics"; @@ -62,7 +62,7 @@ const mono_wasm_legacy_interop_exports = !WasmEnableLegacyJsInterop ? undefined export function export_linker(): any { return { // mini-wasm.c - mono_set_timeout, + mono_wasm_schedule_timer, // mini-wasm-debugger.c mono_wasm_asm_loaded, diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index bae3872e35b45..ece4b093fdbb7 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -10,7 +10,7 @@ import { bound_cs_function_symbol, get_signature_version, alloc_stack_frame, get_signature_type, } from "./marshal"; import { mono_wasm_new_external_root, mono_wasm_new_root } from "./roots"; -import { conv_string, conv_string_root } from "./strings"; +import { conv_string_root, string_decoder } from "./strings"; import { MonoObjectRef, MonoStringRef, MonoString, MonoObject, MonoMethod, JSMarshalerArguments, JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs, VoidPtrNull, MonoObjectRefNull, MonoObjectNull } from "./types/internal"; import { Int32Ptr } from "./types/emscripten"; import cwraps from "./cwraps"; @@ -18,8 +18,10 @@ import { assembly_load } from "./class-loader"; import { 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(); const fqn_root = mono_wasm_new_external_root(fully_qualified_name), resultRoot = mono_wasm_new_external_root(result_address); const mark = startMeasure(); try { @@ -234,7 +236,7 @@ type BindingClosure = { export function invoke_method_and_handle_exception(method: MonoMethod, args: JSMarshalerArguments): void { const fail = cwraps.mono_wasm_invoke_method_bound(method, args); - if (fail) throw new Error("ERR24: Unexpected error: " + conv_string(fail)); + if (fail) throw new Error("ERR24: Unexpected error: " + string_decoder.copy(fail)); if (is_args_exception(args)) { const exc = get_arg(args, 0); throw marshal_exception_to_js(exc); diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index ee1220b8d9ab3..829ae25350e48 100644 --- a/src/mono/wasm/runtime/invoke-js.ts +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -14,10 +14,12 @@ import { mono_log_debug, mono_wasm_symbolicate_string } from "./logging"; import { mono_wasm_get_jsobj_from_js_handle } from "./gc-handles"; import { endMeasure, MeasuredBlock, startMeasure } from "./profiler"; import { wrap_as_cancelable_promise } from "./cancelable-promise"; +import { assert_synchronization_context } from "./pthreads/shared"; const fn_wrapper_by_fn_handle: Function[] = [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(); const function_name_root = mono_wasm_new_external_root(function_name), module_name_root = mono_wasm_new_external_root(module_name), resultRoot = mono_wasm_new_external_root(result_address); @@ -315,6 +317,7 @@ export const importedModules: Map> = new Map(); export function dynamic_import(module_name: string, module_url: string): Promise { mono_assert(module_name, "Invalid module_name"); mono_assert(module_url, "Invalid module_name"); + assert_synchronization_context(); let promise = importedModulesPromises.get(module_name); const newPromise = !promise; if (newPromise) { diff --git a/src/mono/wasm/runtime/managed-exports.ts b/src/mono/wasm/runtime/managed-exports.ts index bc54e51b85bdd..0dbe70b410608 100644 --- a/src/mono/wasm/runtime/managed-exports.ts +++ b/src/mono/wasm/runtime/managed-exports.ts @@ -1,9 +1,11 @@ // 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 { GCHandle, MarshalerToCs, MarshalerToJs, MarshalerType, MonoMethod } from "./types/internal"; import cwraps from "./cwraps"; -import { runtimeHelpers, ENVIRONMENT_IS_PTHREAD, Module } from "./globals"; +import { runtimeHelpers, Module } from "./globals"; import { alloc_stack_frame, get_arg, get_arg_gc_handle, set_arg_type, set_gc_handle } from "./marshal"; import { invoke_method_and_handle_exception } from "./invoke-cs"; import { marshal_array_to_cs_impl, marshal_exception_to_cs, marshal_intptr_to_cs } from "./marshal-to-cs"; @@ -21,8 +23,8 @@ export function init_managed_exports(): void { if (!runtimeHelpers.runtime_interop_exports_class) throw "Can't find " + runtimeHelpers.runtime_interop_namespace + "." + runtimeHelpers.runtime_interop_exports_classname + " class"; - const install_sync_context = cwraps.mono_wasm_assembly_find_method(runtimeHelpers.runtime_interop_exports_class, "InstallSynchronizationContext", -1); - // mono_assert(install_sync_context, "Can't find InstallSynchronizationContext method"); + const install_sync_context = MonoWasmThreads ? get_method("InstallSynchronizationContext") : undefined; + mono_assert(!MonoWasmThreads || install_sync_context, "Can't find InstallSynchronizationContext method"); const call_entry_point = get_method("CallEntrypoint"); mono_assert(call_entry_point, "Can't find CallEntrypoint method"); const release_js_owned_object_by_gc_handle_method = get_method("ReleaseJSOwnedObjectByGCHandle"); @@ -153,7 +155,7 @@ export function init_managed_exports(): void { } }; - if (install_sync_context) { + if (MonoWasmThreads && install_sync_context) { runtimeHelpers.javaScriptExports.install_synchronization_context = () => { const sp = Module.stackSave(); try { @@ -163,10 +165,6 @@ export function init_managed_exports(): void { Module.stackRestore(sp); } }; - - if (!ENVIRONMENT_IS_PTHREAD) - // Install our sync context so that async continuations will migrate back to this thread (the main thread) automatically - runtimeHelpers.javaScriptExports.install_synchronization_context(); } } diff --git a/src/mono/wasm/runtime/net6-legacy/cs-to-js.ts b/src/mono/wasm/runtime/net6-legacy/cs-to-js.ts index 34730933952aa..b3c9fb29a6b6b 100644 --- a/src/mono/wasm/runtime/net6-legacy/cs-to-js.ts +++ b/src/mono/wasm/runtime/net6-legacy/cs-to-js.ts @@ -10,17 +10,20 @@ import { wrap_error_root, wrap_no_error_root } from "../invoke-js"; import { ManagedObject } from "../marshal"; import { getU32, getI32, getF32, getF64, setI32_unchecked } from "../memory"; import { mono_wasm_new_root, mono_wasm_new_external_root } from "../roots"; -import { conv_string_root } from "../strings"; +import { conv_string_root, string_decoder } from "../strings"; 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 { createPromiseController } from "../globals"; +import { assert_legacy_interop } from "../pthreads/shared"; const delegate_invoke_symbol = Symbol.for("wasm delegate_invoke"); // this is only used from Blazor export function unbox_mono_obj(mono_obj: MonoObject): any { + assert_legacy_interop(); + if (mono_obj === MonoObjectNull) return undefined; @@ -132,6 +135,7 @@ export function mono_array_to_js_array(mono_array: MonoArray): any[] | null { if (mono_array === MonoArrayNull) return null; + assert_legacy_interop(); const arrayRoot = mono_wasm_new_root(mono_array); try { return mono_array_root_to_js_array(arrayRoot); @@ -341,3 +345,11 @@ export function get_js_owned_object_by_gc_handle_ref(gc_handle: GCHandle, result // this is always strong gc_handle legacyManagedExports._get_js_owned_object_by_gc_handle_ref(gc_handle, result); } + +/** + * @deprecated Not GC or thread safe + */ +export function conv_string(mono_obj: MonoString): string | null { + assert_legacy_interop(); + return string_decoder.copy(mono_obj); +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/net6-legacy/exports-legacy.ts b/src/mono/wasm/runtime/net6-legacy/exports-legacy.ts index d73cc4e060bdc..9be5f370399c1 100644 --- a/src/mono/wasm/runtime/net6-legacy/exports-legacy.ts +++ b/src/mono/wasm/runtime/net6-legacy/exports-legacy.ts @@ -8,8 +8,8 @@ import { mono_wasm_load_bytes_into_heap, setB32, setI8, setI16, setI32, setI52, import { mono_wasm_new_root_buffer, mono_wasm_new_root, mono_wasm_new_external_root, mono_wasm_release_roots } from "../roots"; import { mono_run_main, mono_run_main_and_exit } from "../run"; import { mono_wasm_setenv } from "../startup"; -import { js_string_to_mono_string, conv_string, js_string_to_mono_string_root, conv_string_root } from "../strings"; -import { mono_array_to_js_array, unbox_mono_obj, unbox_mono_obj_root, mono_array_root_to_js_array } from "./cs-to-js"; +import { js_string_to_mono_string, js_string_to_mono_string_root, conv_string_root } from "../strings"; +import { mono_array_to_js_array, unbox_mono_obj, unbox_mono_obj_root, mono_array_root_to_js_array, conv_string } from "./cs-to-js"; import { js_typed_array_to_array, js_to_mono_obj, js_typed_array_to_array_root, js_to_mono_obj_root } from "./js-to-cs"; import { mono_bind_static_method, mono_call_assembly_entry_point } from "./method-calls"; import { mono_wasm_load_runtime } from "../startup"; diff --git a/src/mono/wasm/runtime/net6-legacy/js-to-cs.ts b/src/mono/wasm/runtime/net6-legacy/js-to-cs.ts index 945640afba8a7..290dd4f68b08c 100644 --- a/src/mono/wasm/runtime/net6-legacy/js-to-cs.ts +++ b/src/mono/wasm/runtime/net6-legacy/js-to-cs.ts @@ -15,6 +15,7 @@ import { has_backing_array_buffer } from "./buffers"; 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"; export function _js_to_mono_uri_root(should_add_in_flight: boolean, js_obj: any, result: WasmRoot): void { switch (true) { @@ -37,6 +38,7 @@ export function _js_to_mono_uri_root(should_add_in_flight: boolean, js_obj: any, * @deprecated Not GC or thread safe. For blazor use only */ export function js_to_mono_obj(js_obj: any): MonoObject { + assert_legacy_interop(); const temp = mono_wasm_new_root(); try { js_to_mono_obj_root(js_obj, temp, false); @@ -60,6 +62,8 @@ export function _js_to_mono_obj_unsafe(should_add_in_flight: boolean, js_obj: an } export function js_to_mono_obj_root(js_obj: any, result: WasmRoot, should_add_in_flight: boolean): void { + assert_legacy_interop(); + if (is_nullish(result)) throw new Error("Expected (value, WasmRoot, boolean)"); diff --git a/src/mono/wasm/runtime/net6-legacy/method-binding.ts b/src/mono/wasm/runtime/net6-legacy/method-binding.ts index 8434bcfe6dc9b..d75ca3ce5feb7 100644 --- a/src/mono/wasm/runtime/net6-legacy/method-binding.ts +++ b/src/mono/wasm/runtime/net6-legacy/method-binding.ts @@ -1,14 +1,13 @@ // 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 { ENVIRONMENT_IS_PTHREAD, Module } from "../globals"; +import { 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_string_to_mono_string_root, js_string_to_mono_string_interned_root, conv_string_root } from "../strings"; -import { MonoMethod, MonoObject, MonoType, MonoClass, VoidPtrNull, MarshalType, MonoString, MonoObjectNull, WasmRootBuffer, WasmRoot } from "../types/internal"; +import { MonoMethod, MonoObject, VoidPtrNull, MarshalType, MonoString, MonoObjectNull, WasmRootBuffer, WasmRoot } from "../types/internal"; import { VoidPtr } from "../types/emscripten"; import { legacyManagedExports } from "./corebindings"; import { get_js_owned_object_by_gc_handle_ref, _unbox_mono_obj_root_with_known_nonprimitive_type } from "./cs-to-js"; @@ -16,6 +15,7 @@ import { legacyHelpers } from "./globals"; 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"; const escapeRE = /[^A-Za-z0-9_$]/g; @@ -23,25 +23,7 @@ const primitiveConverters = new Map(); const _signature_converters = new Map(); const boundMethodsByMethod: Map = new Map(); -export function _get_type_name(typePtr: MonoType): string { - if (!typePtr) - return ""; - return cwraps.mono_wasm_get_type_name(typePtr); -} - -export function _get_type_aqn(typePtr: MonoType): string { - if (!typePtr) - return ""; - return cwraps.mono_wasm_get_type_aqn(typePtr); -} - -export function _get_class_name(classPtr: MonoClass): string { - if (!classPtr) - return ""; - return cwraps.mono_wasm_get_type_name(cwraps.mono_wasm_class_get_type(classPtr)); -} - -export function _create_named_function(name: string, argumentNames: string[], body: string, closure: any): Function { +function _create_named_function(name: string, argumentNames: string[], body: string, closure: any): Function { let result = null; let closureArgumentList: any[] | null = null; let closureArgumentNames = null; @@ -60,7 +42,7 @@ export function _create_named_function(name: string, argumentNames: string[], bo return result; } -export function _create_rebindable_named_function(name: string, argumentNames: string[], body: string, closureArgNames: string[] | null): Function { +function _create_rebindable_named_function(name: string, argumentNames: string[], body: string, closureArgNames: string[] | null): Function { const strictPrefix = "\"use strict\";\r\n"; let uriPrefix = "", escapedFunctionIdentifier = ""; @@ -176,7 +158,7 @@ function _get_converter_for_marshal_string(args_marshal: string/*ArgsMarshalStri return converter; } -export function _compile_converter_for_marshal_string(args_marshal: string/*ArgsMarshalString*/): Converter { +function _compile_converter_for_marshal_string(args_marshal: string/*ArgsMarshalString*/): Converter { const converter = _get_converter_for_marshal_string(args_marshal); if (typeof (converter.args_marshal) !== "string") throw new Error("Corrupt converter for '" + args_marshal + "'"); @@ -383,9 +365,7 @@ export function _decide_if_result_is_marshaled(converter: Converter, argc: numbe export function mono_bind_method(method: MonoMethod, args_marshal: string/*ArgsMarshalString*/, has_this_arg: boolean, friendly_name?: string): Function { - if (MonoWasmThreads && ENVIRONMENT_IS_PTHREAD) { - throw new Error("Legacy interop is not supported with WebAssembly threads."); - } + assert_legacy_interop(); if (typeof (args_marshal) !== "string") throw new Error("args_marshal argument invalid, expected string"); diff --git a/src/mono/wasm/runtime/net6-legacy/method-calls.ts b/src/mono/wasm/runtime/net6-legacy/method-calls.ts index 235e45c8641cd..61388647a837b 100644 --- a/src/mono/wasm/runtime/net6-legacy/method-calls.ts +++ b/src/mono/wasm/runtime/net6-legacy/method-calls.ts @@ -1,9 +1,8 @@ // 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 { get_js_obj, mono_wasm_get_jsobj_from_js_handle } from "../gc-handles"; -import { Module, runtimeHelpers, INTERNAL, ENVIRONMENT_IS_PTHREAD } from "../globals"; +import { Module, runtimeHelpers, 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"; @@ -14,6 +13,7 @@ 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"; const boundMethodsByFqn: Map = new Map(); @@ -53,6 +53,7 @@ export function _teardown_after_call( 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}`; let js_method = boundMethodsByFqn.get(key); @@ -69,6 +70,7 @@ export function mono_bind_static_method(fqn: string, signature?: string/*ArgsMar } export function mono_bind_assembly_entry_point(assembly: string, signature?: string/*ArgsMarshalString*/): Function { + assert_legacy_interop(); const method = find_entry_point(assembly); if (typeof (signature) !== "string") signature = mono_method_get_call_signature_ref(method, undefined); @@ -84,6 +86,7 @@ export function mono_bind_assembly_entry_point(assembly: string, signature?: str 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 = [[]]; } @@ -91,9 +94,7 @@ export function mono_call_assembly_entry_point(assembly: string, args?: any[], s } export function mono_wasm_invoke_js_with_args_ref(js_handle: JSHandle, method_name: MonoStringRef, args: MonoObjectRef, is_exception: Int32Ptr, result_address: MonoObjectRef): any { - if (MonoWasmThreads && ENVIRONMENT_IS_PTHREAD) { - throw new Error("Legacy interop is not supported with WebAssembly threads."); - } + assert_legacy_interop(); const argsRoot = mono_wasm_new_external_root(args), nameRoot = mono_wasm_new_external_root(method_name), resultRoot = mono_wasm_new_external_root(result_address); @@ -131,6 +132,7 @@ export function mono_wasm_invoke_js_with_args_ref(js_handle: JSHandle, method_na } export function mono_wasm_get_object_property_ref(js_handle: JSHandle, property_name: MonoStringRef, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + assert_legacy_interop(); const nameRoot = mono_wasm_new_external_root(property_name), resultRoot = mono_wasm_new_external_root(result_address); try { @@ -158,6 +160,7 @@ export function mono_wasm_get_object_property_ref(js_handle: JSHandle, property_ } export function mono_wasm_set_object_property_ref(js_handle: JSHandle, property_name: MonoStringRef, value: MonoObjectRef, createIfNotExist: boolean, hasOwnProperty: boolean, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + assert_legacy_interop(); const valueRoot = mono_wasm_new_external_root(value), nameRoot = mono_wasm_new_external_root(property_name), resultRoot = mono_wasm_new_external_root(result_address); @@ -206,6 +209,7 @@ export function mono_wasm_set_object_property_ref(js_handle: JSHandle, property_ } export function mono_wasm_get_by_index_ref(js_handle: JSHandle, property_index: number, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + assert_legacy_interop(); const resultRoot = mono_wasm_new_external_root(result_address); try { const obj = mono_wasm_get_jsobj_from_js_handle(js_handle); @@ -225,6 +229,7 @@ export function mono_wasm_get_by_index_ref(js_handle: JSHandle, property_index: } export function mono_wasm_set_by_index_ref(js_handle: JSHandle, property_index: number, value: MonoObjectRef, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + assert_legacy_interop(); const valueRoot = mono_wasm_new_external_root(value), resultRoot = mono_wasm_new_external_root(result_address); try { @@ -246,6 +251,7 @@ export function mono_wasm_set_by_index_ref(js_handle: JSHandle, property_index: } export function mono_wasm_get_global_object_ref(global_name: MonoStringRef, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + assert_legacy_interop(); const nameRoot = mono_wasm_new_external_root(global_name), resultRoot = mono_wasm_new_external_root(result_address); try { @@ -285,9 +291,7 @@ export function mono_wasm_get_global_object_ref(global_name: MonoStringRef, is_e // Blazor specific custom routine export function mono_wasm_invoke_js_blazor(exceptionMessage: Int32Ptr, callInfo: any, arg0: any, arg1: any, arg2: any): void | number { try { - if (MonoWasmThreads) { - throw new Error("Legacy interop is not supported with WebAssembly threads."); - } + assert_legacy_interop(); const blazorExports = (globalThis).Blazor; if (!blazorExports) { throw new Error("The blazor.webassembly.js library is not loaded."); diff --git a/src/mono/wasm/runtime/pthreads/shared/index.ts b/src/mono/wasm/runtime/pthreads/shared/index.ts index 430fefff34c41..1562ddbbeb7c6 100644 --- a/src/mono/wasm/runtime/pthreads/shared/index.ts +++ b/src/mono/wasm/runtime/pthreads/shared/index.ts @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { Module } from "../../globals"; +import MonoWasmThreads from "consts:monoWasmThreads"; + +import { ENVIRONMENT_IS_PTHREAD, Module, runtimeHelpers } from "../../globals"; import { MonoConfig } from "../../types"; import { pthreadPtr } from "./types"; @@ -128,3 +130,24 @@ export function isMonoWorkerMessagePreload(message: MonoWorkerMessage 0) { - --pump_count; - cwraps.mono_background_exec(); - } -} - export function prevent_timer_throttling(): void { if (!isChromium) { return; @@ -38,27 +31,31 @@ export function prevent_timer_throttling(): void { for (let schedule = next_reach_time; schedule < desired_reach_time; schedule += light_throttling_frequency) { const delay = schedule - now; setTimeout(() => { - cwraps.mono_set_timeout_exec(); + cwraps.mono_wasm_execute_timer(); pump_count++; - pump_message(); + mono_background_exec_until_done(); }, delay); } spread_timers_maximum = desired_reach_time; } +function mono_background_exec_until_done() { + while (pump_count > 0) { + --pump_count; + cwraps.mono_background_exec(); + } +} + export function schedule_background_exec(): void { ++pump_count; - setTimeout(pump_message, 0); + setTimeout(mono_background_exec_until_done, 0); } let lastScheduledTimeoutId: any = undefined; -export function mono_set_timeout(timeout: number): void { - function mono_wasm_set_timeout_exec() { - cwraps.mono_set_timeout_exec(); - } +export function mono_wasm_schedule_timer(shortestDueTimeMs: number): void { if (lastScheduledTimeoutId) { clearTimeout(lastScheduledTimeoutId); lastScheduledTimeoutId = undefined; } - lastScheduledTimeoutId = setTimeout(mono_wasm_set_timeout_exec, timeout); + lastScheduledTimeoutId = setTimeout(cwraps.mono_wasm_execute_timer, shortestDueTimeMs); } diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 5a3c395afd61c..cb17220e64852 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -5,7 +5,7 @@ import MonoWasmThreads from "consts:monoWasmThreads"; import WasmEnableLegacyJsInterop from "consts:WasmEnableLegacyJsInterop"; import { DotnetModuleInternal, CharPtrNull } from "./types/internal"; -import { disableLegacyJsInterop, exportedRuntimeAPI, INTERNAL, loaderHelpers, Module, runtimeHelpers } from "./globals"; +import { disableLegacyJsInterop, ENVIRONMENT_IS_PTHREAD, exportedRuntimeAPI, INTERNAL, loaderHelpers, Module, runtimeHelpers } from "./globals"; import cwraps, { init_c_exports } from "./cwraps"; import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug"; import { toBase64StringImpl } from "./base64"; @@ -30,6 +30,7 @@ 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"; // default size if MonoConfig.pthreadPoolSize is undefined @@ -588,6 +589,9 @@ export function bindings_init(): void { if (WasmEnableLegacyJsInterop && !disableLegacyJsInterop) { init_legacy_exports(); } + if (MonoWasmThreads && !ENVIRONMENT_IS_PTHREAD) { + install_synchronization_context(); + } initialize_marshalers_to_js(); initialize_marshalers_to_cs(); runtimeHelpers._i52_error_scratch_buffer = Module._malloc(4); diff --git a/src/mono/wasm/runtime/strings.ts b/src/mono/wasm/runtime/strings.ts index 52f6c8dbbe373..3fd4e012d7de9 100644 --- a/src/mono/wasm/runtime/strings.ts +++ b/src/mono/wasm/runtime/strings.ts @@ -8,6 +8,7 @@ import cwraps from "./cwraps"; import { mono_wasm_new_root } from "./roots"; import { getI32, getU32 } from "./memory"; import { NativePointer, CharPtr } from "./types/emscripten"; +import { assert_legacy_interop } from "./pthreads/shared"; export class StringDecoder { @@ -102,13 +103,6 @@ let _interned_string_current_root_buffer_count = 0; export const string_decoder = new StringDecoder(); export const mono_wasm_empty_string = ""; -/** - * @deprecated Not GC or thread safe - */ -export function conv_string(mono_obj: MonoString): string | null { - return string_decoder.copy(mono_obj); -} - export function conv_string_root(root: WasmRoot): string | null { return string_decoder.copy_root(root); } @@ -255,6 +249,7 @@ export function js_string_to_mono_string_interned(string: string | symbol): Mono * @deprecated Not GC or thread safe */ export function js_string_to_mono_string(string: string): MonoString { + assert_legacy_interop(); const temp = mono_wasm_new_root(); try { js_string_to_mono_string_root(string, temp); @@ -268,6 +263,7 @@ export function js_string_to_mono_string(string: string): MonoString { * @deprecated Not GC or thread safe */ export function js_string_to_mono_string_new(string: string): MonoString { + assert_legacy_interop(); const temp = mono_wasm_new_root(); try { js_string_to_mono_string_new_root(string, temp); diff --git a/src/mono/wasm/threads.md b/src/mono/wasm/threads.md index ef2152865c031..697c8c2dc4bb3 100644 --- a/src/mono/wasm/threads.md +++ b/src/mono/wasm/threads.md @@ -79,10 +79,10 @@ Mono exposes these functions as `mono_threads_wasm_async_run_in_main_thread`, et ## Background tasks ## -The runtime has a number of tasks that are scheduled with `mono_threads_schedule_background_job` +The runtime has a number of tasks that are scheduled with `mono_main_thread_schedule_background_job` (pumping the threadpool task queue, running GC finalizers, etc). -The background tasks will run on the main thread. Calling `mono_threads_schedule_background_job` on +The background tasks will run on the main thread. Calling `mono_main_thread_schedule_background_job` on a worker thread will use `async_run_in_main_thread` to queue up work for the main thread. ## Debugger tests ## From 1ee8a9200b99055293e9e1d9f90b11183dbbd410 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 23 May 2023 18:56:05 +0200 Subject: [PATCH 2/8] fix --- src/mono/wasm/runtime/pthreads/shared/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/pthreads/shared/index.ts b/src/mono/wasm/runtime/pthreads/shared/index.ts index 1562ddbbeb7c6..6fa9789bd6865 100644 --- a/src/mono/wasm/runtime/pthreads/shared/index.ts +++ b/src/mono/wasm/runtime/pthreads/shared/index.ts @@ -147,7 +147,7 @@ export function assert_synchronization_context(): void { export function assert_legacy_interop(): void { if (MonoWasmThreads) { - mono_assert(ENVIRONMENT_IS_PTHREAD, "Legacy interop is not supported with WebAssembly threads."); + mono_assert(!ENVIRONMENT_IS_PTHREAD, "Legacy interop is not supported with WebAssembly threads."); } } From 2c61c78ee22873018c14dd97c40f932c1e7ccca4 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 23 May 2023 18:59:06 +0200 Subject: [PATCH 3/8] feedback --- .../InteropServices/JavaScript/JSSynchronizationContext.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs index 6abfa0550635a..0437f83b87a8a 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs @@ -80,6 +80,8 @@ private void AwaitNewData() private void DataIsAvailable() { + // 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])&BackgroundJobHandler); } From 2eefd11d53992e3f8726f1fdfacc9dca6ca2e972 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 23 May 2023 19:05:42 +0200 Subject: [PATCH 4/8] feedback --- .../JavaScript/JSSynchronizationContext.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs index 0437f83b87a8a..eceae898287e4 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs @@ -115,14 +115,9 @@ public override void Send(SendOrPostCallback d, object? state) internal static void Install() { - var ctx = Current as JSSynchronizationContext; - if (ctx == null) - { - ctx = new JSSynchronizationContext(); - MainThreadSynchronizationContext = ctx; - SetSynchronizationContext(ctx); - } - ctx.AwaitNewData(); + MainThreadSynchronizationContext ??= new JSSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(MainThreadSynchronizationContext); + MainThreadSynchronizationContext.AwaitNewData(); } [MethodImplAttribute(MethodImplOptions.InternalCall)] From a4e69619d2cbc6055c163f27dc534a6cce422dee Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 24 May 2023 15:30:22 +0200 Subject: [PATCH 5/8] small tweaks --- src/mono/wasm/runtime/invoke-cs.ts | 10 ++++++++++ src/mono/wasm/runtime/invoke-js.ts | 9 +++++++++ src/mono/wasm/runtime/loader/worker.ts | 3 ++- src/mono/wasm/runtime/startup.ts | 2 +- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index ece4b093fdbb7..fa9099d73bf4b 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import BuildConfiguration from "consts:configuration"; + import MonoWasmThreads from "consts:monoWasmThreads"; import { Module, runtimeHelpers } from "./globals"; import { bind_arg_marshal_to_cs } from "./marshal-to-cs"; @@ -86,6 +88,13 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, bound_fn = bind_fn(closure); } + // 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); + } + (bound_fn)[bound_cs_function_symbol] = true; _walk_exports_to_set_function(assembly, namespace, classname, methodname, signature_hash, bound_fn); @@ -235,6 +244,7 @@ type BindingClosure = { } export function invoke_method_and_handle_exception(method: MonoMethod, args: JSMarshalerArguments): void { + assert_synchronization_context(); const fail = cwraps.mono_wasm_invoke_method_bound(method, args); if (fail) throw new Error("ERR24: Unexpected error: " + string_decoder.copy(fail)); if (is_args_exception(args)) { diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index 829ae25350e48..c77a483f85095 100644 --- a/src/mono/wasm/runtime/invoke-js.ts +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import BuildConfiguration from "consts:configuration"; + import { marshal_exception_to_cs, bind_arg_marshal_to_cs } from "./marshal-to-cs"; import { get_signature_argument_count, bound_js_function_symbol, get_sig, get_signature_version, get_signature_type, imported_js_function_symbol } from "./marshal"; import { setI32_unchecked } from "./memory"; @@ -83,6 +85,13 @@ export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_ bound_fn = bind_fn(closure); } + // 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); + } + (bound_fn)[imported_js_function_symbol] = true; const fn_handle = fn_wrapper_by_fn_handle.length; fn_wrapper_by_fn_handle.push(bound_fn); diff --git a/src/mono/wasm/runtime/loader/worker.ts b/src/mono/wasm/runtime/loader/worker.ts index a46e7075caea5..2f2024140d8f4 100644 --- a/src/mono/wasm/runtime/loader/worker.ts +++ b/src/mono/wasm/runtime/loader/worker.ts @@ -1,6 +1,6 @@ import { MonoConfig } from "../types"; import { MonoConfigInternal } from "../types/internal"; -import { deep_merge_config } from "./config"; +import { deep_merge_config, normalizeConfig } from "./config"; import { ENVIRONMENT_IS_WEB, loaderHelpers } from "./globals"; import { mono_log_debug } from "./logging"; @@ -30,6 +30,7 @@ function onMonoConfigReceived(config: MonoConfigInternal): void { } deep_merge_config(loaderHelpers.config, config); + normalizeConfig(); mono_log_debug("mono config received"); workerMonoConfigReceived = true; loaderHelpers.afterConfigLoaded.promise_control.resolve(loaderHelpers.config); diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index cb17220e64852..f0bf309b32fd8 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -586,7 +586,7 @@ export function bindings_init(): void { try { const mark = startMeasure(); init_managed_exports(); - if (WasmEnableLegacyJsInterop && !disableLegacyJsInterop) { + if (WasmEnableLegacyJsInterop && !disableLegacyJsInterop && !ENVIRONMENT_IS_PTHREAD) { init_legacy_exports(); } if (MonoWasmThreads && !ENVIRONMENT_IS_PTHREAD) { From dcb858a046c702d5b2cdb2f7584bb38d88e7dc64 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 24 May 2023 15:58:46 +0200 Subject: [PATCH 6/8] optimized pointers to managed handlers added comments added missing export of _emscripten_main_runtime_thread_id --- .../InteropServices/JavaScript/JSSynchronizationContext.cs | 3 ++- .../src/System/Threading/ThreadPool.Browser.Mono.cs | 3 ++- .../src/System/Threading/TimerQueue.Browser.Mono.cs | 6 +++--- src/mono/mono/mini/mini-wasm.c | 1 + src/mono/mono/utils/mono-threads-wasm.c | 4 ++++ src/mono/mono/utils/mono-threads.h | 1 - src/mono/wasm/runtime/net6-legacy/cs-to-js.ts | 2 +- src/mono/wasm/wasm.proj | 3 +++ 8 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs index eceae898287e4..7daabec2af882 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs @@ -39,6 +39,7 @@ public WorkItem(SendOrPostCallback callback, object? data, ManualResetEventSlim? private static JSSynchronizationContext? MainThreadSynchronizationContext; private readonly QueueType Queue; + private static void* BackgroundJobHandlerPtr = (void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler; private JSSynchronizationContext() : this( @@ -82,7 +83,7 @@ private void DataIsAvailable() { // 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])&BackgroundJobHandler); + MainThreadScheduleBackgroundJob(BackgroundJobHandlerPtr); } public override void Post(SendOrPostCallback d, object? state) diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs index 6821cac9e224f..969f2c2ecb262 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs @@ -37,6 +37,7 @@ public static unsafe partial class ThreadPool private const bool IsWorkerTrackingEnabledInConfig = false; private static bool _callbackQueued; + private static void* BackgroundJobHandlerPtr = (void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler; public static bool SetMaxThreads(int workerThreads, int completionPortThreads) { @@ -79,7 +80,7 @@ internal static void RequestWorkerThread() if (_callbackQueued) return; _callbackQueued = true; - MainThreadScheduleBackgroundJob((void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler); + MainThreadScheduleBackgroundJob(BackgroundJobHandlerPtr); } internal static void NotifyWorkItemProgress() 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 cc90b9d2145a6..6628cdefbe5bb 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 @@ -19,6 +19,7 @@ internal unsafe partial class TimerQueue private static List? s_scheduledTimers; private static List? s_scheduledTimersToFire; private static long s_shortestDueTimeMs = long.MaxValue; + private static void* TimerHandlerPtr = (void*)(delegate* unmanaged[Cdecl])&TimerHandler; // this means that it's in the s_scheduledTimers collection, not that it's the one which would run on the next TimeoutCallback private bool _isScheduled; @@ -77,9 +78,8 @@ private static void ReplaceNextTimer(long shortestDueTimeMs, long currentTimeMs) { s_shortestDueTimeMs = shortestDueTimeMs; int shortestWait = Math.Max((int)(shortestDueTimeMs - currentTimeMs), 0); - // this would cancel the previous schedule and create shorter one - // it is expensive call - MainThreadScheduleTimer((void*)(delegate* unmanaged[Cdecl])&TimerHandler, shortestWait); + // this would cancel the previous schedule and create shorter one, it is expensive callback + MainThreadScheduleTimer(TimerHandlerPtr, shortestWait); } } diff --git a/src/mono/mono/mini/mini-wasm.c b/src/mono/mono/mini/mini-wasm.c index b0b923818e00e..53f1062a46044 100644 --- a/src/mono/mono/mini/mini-wasm.c +++ b/src/mono/mono/mini/mini-wasm.c @@ -557,6 +557,7 @@ mono_init_native_crash_info (void) #endif +// this points to System.Threading.TimerQueue.TimerHandler C# method static void *timer_handler; #ifdef HOST_BROWSER diff --git a/src/mono/mono/utils/mono-threads-wasm.c b/src/mono/mono/utils/mono-threads-wasm.c index 7485c3729a965..a1203e838d202 100644 --- a/src/mono/mono/utils/mono-threads-wasm.c +++ b/src/mono/mono/utils/mono-threads-wasm.c @@ -345,6 +345,10 @@ mono_memory_barrier_process_wide (void) G_EXTERN_C extern void schedule_background_exec (void); +// when this is called from ThreadPool, the cb would be System.Threading.ThreadPool.BackgroundJobHandler +// when this is called from JSSynchronizationContext, the cb would be System.Runtime.InteropServices.JavaScript.JSSynchronizationContext.BackgroundJobHandler +// when this is called from sgen it would be wrapper of sgen_perform_collection_inner +// when this is called from gc, it would be mono_runtime_do_background_work void mono_main_thread_schedule_background_job (background_job_cb cb) { diff --git a/src/mono/mono/utils/mono-threads.h b/src/mono/mono/utils/mono-threads.h index 5a96c598e15d9..80d6f198a16b9 100644 --- a/src/mono/mono/utils/mono-threads.h +++ b/src/mono/mono/utils/mono-threads.h @@ -848,7 +848,6 @@ void mono_threads_join_unlock (void); 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_bound_thread_schedule_background_job (int bound_thread, background_job_cb cb); #endif #ifdef USE_WINDOWS_BACKEND diff --git a/src/mono/wasm/runtime/net6-legacy/cs-to-js.ts b/src/mono/wasm/runtime/net6-legacy/cs-to-js.ts index b3c9fb29a6b6b..28a1294df9b7e 100644 --- a/src/mono/wasm/runtime/net6-legacy/cs-to-js.ts +++ b/src/mono/wasm/runtime/net6-legacy/cs-to-js.ts @@ -132,10 +132,10 @@ export function unbox_mono_obj_root(root: WasmRoot): any { } export function mono_array_to_js_array(mono_array: MonoArray): any[] | null { + assert_legacy_interop(); if (mono_array === MonoArrayNull) return null; - assert_legacy_interop(); const arrayRoot = mono_wasm_new_root(mono_array); try { return mono_array_root_to_js_array(arrayRoot); diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index a63ceb8adae6b..ac55816083da0 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -269,6 +269,9 @@ + + + <_EmccExportedLibraryFunction>"[@(EmccExportedLibraryFunction -> '%27%(Identity)%27', ',')]" <_EmccExportedRuntimeMethods>"[@(EmccExportedRuntimeMethod -> '%27%(Identity)%27', ',')]" From 68b57a25551ce79ba70b86b97ab2ad51e238f697 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 24 May 2023 17:29:16 +0200 Subject: [PATCH 7/8] feedback --- .../JavaScript/JSSynchronizationContext.cs | 15 ++++++++------- .../System/Threading/ThreadPool.Browser.Mono.cs | 10 +++++----- .../System/Threading/TimerQueue.Browser.Mono.cs | 11 +++++------ .../wasm/browser-threads-minimal/Program.cs | 8 ++++++-- .../wasm/browser-threads-minimal/fetchhelper.js | 6 +++--- src/mono/wasm/runtime/invoke-js.ts | 2 +- 6 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs index 7daabec2af882..3cbbdf4ef8dca 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs @@ -19,7 +19,7 @@ namespace System.Runtime.InteropServices.JavaScript /// thread-affinity-having APIs like WebSockets, fetch, WebGL, etc. /// Callbacks are processed during event loop turns via the runtime's background job system. /// - internal sealed unsafe class JSSynchronizationContext : SynchronizationContext + internal sealed class JSSynchronizationContext : SynchronizationContext { public readonly Thread MainThread; @@ -39,7 +39,7 @@ public WorkItem(SendOrPostCallback callback, object? data, ManualResetEventSlim? private static JSSynchronizationContext? MainThreadSynchronizationContext; private readonly QueueType Queue; - private static void* BackgroundJobHandlerPtr = (void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler; + private readonly Action _DataIsAvailable;// don't allocate Action on each call to UnsafeOnCompleted private JSSynchronizationContext() : this( @@ -55,6 +55,7 @@ private JSSynchronizationContext(Thread mainThread, QueueType queue) { MainThread = mainThread; Queue = queue; + _DataIsAvailable = DataIsAvailable; } public override SynchronizationContext CreateCopy() @@ -76,14 +77,14 @@ private void AwaitNewData() // fire a callback that will schedule a background job to pump the queue on the main thread. var awaiter = vt.AsTask().ConfigureAwait(false).GetAwaiter(); // UnsafeOnCompleted avoids spending time flowing the execution context (we don't need it.) - awaiter.UnsafeOnCompleted(DataIsAvailable); + awaiter.UnsafeOnCompleted(_DataIsAvailable); } - private void DataIsAvailable() + private unsafe void DataIsAvailable() { // 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(BackgroundJobHandlerPtr); + MainThreadScheduleBackgroundJob((void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler); } public override void Post(SendOrPostCallback d, object? state) @@ -127,8 +128,8 @@ internal static void Install() #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 - private static unsafe void BackgroundJobHandler() + // this callback will arrive on the bound thread, called from mono_background_exec + private static void BackgroundJobHandler() { MainThreadSynchronizationContext!.Pump(); } diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs index 969f2c2ecb262..da4fe29b58de2 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs @@ -28,7 +28,7 @@ public bool Unregister(WaitHandle? waitObject) } } - public static unsafe partial class ThreadPool + public static partial class ThreadPool { // Indicates whether the thread pool should yield the thread from the dispatch loop to the runtime periodically so that // the runtime may use the thread for processing other work @@ -37,7 +37,6 @@ public static unsafe partial class ThreadPool private const bool IsWorkerTrackingEnabledInConfig = false; private static bool _callbackQueued; - private static void* BackgroundJobHandlerPtr = (void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler; public static bool SetMaxThreads(int workerThreads, int completionPortThreads) { @@ -75,12 +74,12 @@ public static void GetAvailableThreads(out int workerThreads, out int completion public static long CompletedWorkItemCount => 0; - internal static void RequestWorkerThread() + internal static unsafe void RequestWorkerThread() { if (_callbackQueued) return; _callbackQueued = true; - MainThreadScheduleBackgroundJob(BackgroundJobHandlerPtr); + MainThreadScheduleBackgroundJob((void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler); } internal static void NotifyWorkItemProgress() @@ -117,7 +116,8 @@ private static RegisteredWaitHandle RegisterWaitForSingleObject( #pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] #pragma warning restore CS3016 - private static unsafe void BackgroundJobHandler () { + // this callback will arrive on the bound thread, called from mono_background_exec + private static void BackgroundJobHandler () { _callbackQueued = false; ThreadPoolWorkQueue.Dispatch(); } 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 6628cdefbe5bb..067a70dc2eb1d 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 @@ -14,12 +14,11 @@ namespace System.Threading // Based on TimerQueue.Portable.cs // Not thread safe // - internal unsafe partial class TimerQueue + internal partial class TimerQueue { private static List? s_scheduledTimers; private static List? s_scheduledTimersToFire; private static long s_shortestDueTimeMs = long.MaxValue; - private static void* TimerHandlerPtr = (void*)(delegate* unmanaged[Cdecl])&TimerHandler; // this means that it's in the s_scheduledTimers collection, not that it's the one which would run on the next TimeoutCallback private bool _isScheduled; @@ -36,8 +35,8 @@ private TimerQueue(int _) #pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] #pragma warning restore CS3016 - // Called by mini-wasm.c:mono_wasm_execute_timer - private static unsafe void TimerHandler () { + // this callback will arrive on the main thread, called from mono_wasm_execute_timer + private static void TimerHandler () { // always only have one scheduled at a time s_shortestDueTimeMs = long.MaxValue; @@ -66,7 +65,7 @@ private bool SetTimer(uint actualDuration) } // shortest time of all TimerQueues - private static void ReplaceNextTimer(long shortestDueTimeMs, long currentTimeMs) + private static unsafe void ReplaceNextTimer(long shortestDueTimeMs, long currentTimeMs) { if (shortestDueTimeMs == long.MaxValue) { @@ -79,7 +78,7 @@ private static void ReplaceNextTimer(long shortestDueTimeMs, long currentTimeMs) s_shortestDueTimeMs = shortestDueTimeMs; int shortestWait = Math.Max((int)(shortestDueTimeMs - currentTimeMs), 0); // this would cancel the previous schedule and create shorter one, it is expensive callback - MainThreadScheduleTimer(TimerHandlerPtr, shortestWait); + MainThreadScheduleTimer((void*)(delegate* unmanaged[Cdecl])&TimerHandler, shortestWait); } } diff --git a/src/mono/sample/wasm/browser-threads-minimal/Program.cs b/src/mono/sample/wasm/browser-threads-minimal/Program.cs index f5c50bd97849d..0e9e5584c7c9d 100644 --- a/src/mono/sample/wasm/browser-threads-minimal/Program.cs +++ b/src/mono/sample/wasm/browser-threads-minimal/Program.cs @@ -57,7 +57,7 @@ public static async Task TestCallSetTimeoutOnWorker() const string fetchhelper = "./fetchelper.js"; [JSImport("responseText", fetchhelper)] - private static partial Task FetchHelperResponseText(JSObject response); + private static partial Task FetchHelperResponseText(JSObject response, int delayMs); [JSExport] public static async Task FetchBackground(string url) @@ -77,7 +77,11 @@ public static async Task FetchBackground(string url) Console.WriteLine($"smoke: FetchBackground 5 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); if (ok) { - var text = await FetchHelperResponseText(r); + #if DEBUG + var text = await FetchHelperResponseText(r, 5000); + #else + var text = await FetchHelperResponseText(r, 25000); + #endif Console.WriteLine($"smoke: FetchBackground 6 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); return text; } diff --git a/src/mono/sample/wasm/browser-threads-minimal/fetchhelper.js b/src/mono/sample/wasm/browser-threads-minimal/fetchhelper.js index ad31e74dabb87..ac740859b0299 100644 --- a/src/mono/sample/wasm/browser-threads-minimal/fetchhelper.js +++ b/src/mono/sample/wasm/browser-threads-minimal/fetchhelper.js @@ -3,9 +3,9 @@ export function delay(timeoutMs) { return new Promise(resolve => setTimeout(resolve, timeoutMs)); } -export async function responseText(response) /* Promise */ { - console.log("artificially waiting for response for 5 seconds"); - await delay(5000); +export async function responseText(response, delay) /* Promise */ { + console.log(`artificially waiting for response for ${delay} ms`); + await delay(delay); console.log("artificial waiting done"); return await response.text(); } diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index c77a483f85095..a1fc9ae6e42a9 100644 --- a/src/mono/wasm/runtime/invoke-js.ts +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -32,7 +32,7 @@ export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_ const js_function_name = conv_string_root(function_name_root)!; const mark = startMeasure(); const js_module_name = conv_string_root(module_name_root)!; - mono_log_debug(`Binding [JSImport] ${js_function_name} from ${js_module_name}`); + mono_log_debug(`Binding [JSImport] ${js_function_name} from ${js_module_name} module`); const fn = mono_wasm_lookup_function(js_function_name, js_module_name); const args_count = get_signature_argument_count(signature); From 14abe3489ed918d4fce2adf4ee93029f4767486d Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 24 May 2023 19:34:49 +0200 Subject: [PATCH 8/8] fix bug in fetchhelper sample add asserts and exception handlers --- .../JavaScript/JSSynchronizationContext.cs | 4 ++++ .../Threading/ThreadPool.Browser.Mono.cs | 14 +++++++++++--- .../Threading/TimerQueue.Browser.Mono.cs | 18 +++++++++++++----- src/mono/mono/mini/mini-wasm.c | 2 ++ src/mono/mono/utils/mono-threads-wasm.c | 3 +++ .../browser-threads-minimal/fetchhelper.js | 6 +++--- 6 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs index 3cbbdf4ef8dca..6c8a00bd00eeb 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs @@ -153,6 +153,10 @@ private void Pump() } } } + catch (Exception e) + { + Environment.FailFast("JSSynchronizationContext.BackgroundJobHandler failed", e); + } finally { // If an item throws, we want to ensure that the next pump gets scheduled appropriately regardless. diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs index da4fe29b58de2..96ca4d4c939f3 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs @@ -117,9 +117,17 @@ private static RegisteredWaitHandle RegisterWaitForSingleObject( [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] #pragma warning restore CS3016 // this callback will arrive on the bound thread, called from mono_background_exec - private static void BackgroundJobHandler () { - _callbackQueued = false; - ThreadPoolWorkQueue.Dispatch(); + private static void BackgroundJobHandler () + { + try + { + _callbackQueued = false; + ThreadPoolWorkQueue.Dispatch(); + } + catch (Exception e) + { + Environment.FailFast("ThreadPool.BackgroundJobHandler failed", e); + } } private static unsafe void NativeOverlappedCallback(nint overlappedPtr) => 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 067a70dc2eb1d..0682f3ef246c9 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 @@ -36,12 +36,20 @@ private TimerQueue(int _) [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] #pragma warning restore CS3016 // this callback will arrive on the main thread, called from mono_wasm_execute_timer - private static void TimerHandler () { - // always only have one scheduled at a time - s_shortestDueTimeMs = long.MaxValue; + private static void TimerHandler () + { + try + { + // always only have one scheduled at a time + s_shortestDueTimeMs = long.MaxValue; - long currentTimeMs = TickCount64; - ReplaceNextTimer(PumpTimerQueue(currentTimeMs), currentTimeMs); + long currentTimeMs = TickCount64; + ReplaceNextTimer(PumpTimerQueue(currentTimeMs), currentTimeMs); + } + catch (Exception e) + { + Environment.FailFast("TimerQueue.TimerHandler failed", e); + } } // this is called with shortest of timers scheduled on the particular TimerQueue diff --git a/src/mono/mono/mini/mini-wasm.c b/src/mono/mono/mini/mini-wasm.c index 53f1062a46044..8aa7934570b5c 100644 --- a/src/mono/mono/mini/mini-wasm.c +++ b/src/mono/mono/mini/mini-wasm.c @@ -586,6 +586,7 @@ mono_thread_state_init_from_handle (MonoThreadUnwindState *tctx, MonoThreadInfo EMSCRIPTEN_KEEPALIVE void mono_wasm_execute_timer (void) { + g_assert (timer_handler); background_job_cb cb = timer_handler; cb (); } @@ -596,6 +597,7 @@ mono_wasm_execute_timer (void) void mono_wasm_main_thread_schedule_timer (void *timerHandler, int shortestDueTimeMs) { + g_assert (timerHandler); timer_handler = timerHandler; #ifdef HOST_BROWSER #ifndef DISABLE_THREADS diff --git a/src/mono/mono/utils/mono-threads-wasm.c b/src/mono/mono/utils/mono-threads-wasm.c index a1203e838d202..dd49bf4205e3a 100644 --- a/src/mono/mono/utils/mono-threads-wasm.c +++ b/src/mono/mono/utils/mono-threads-wasm.c @@ -352,6 +352,7 @@ extern void schedule_background_exec (void); void mono_main_thread_schedule_background_job (background_job_cb cb) { + g_assert (cb); #ifndef DISABLE_THREADS if (!mono_threads_wasm_is_browser_thread ()) { THREADS_DEBUG ("mono_main_thread_schedule_background_job1: thread %p queued job %p to main thread\n", (gpointer)pthread_self(), (gpointer) cb); @@ -372,6 +373,7 @@ GSList *jobs; void mono_current_thread_schedule_background_job (background_job_cb cb) { + g_assert (cb); #ifdef DISABLE_THREADS if (!jobs) @@ -421,6 +423,7 @@ mono_background_exec (void) for (cur = j; cur; cur = cur->next) { background_job_cb cb = (background_job_cb)cur->data; + g_assert (cb); THREADS_DEBUG ("mono_background_exec on thread %p running job %p \n", (gpointer)pthread_self(), (gpointer)cb); cb (); THREADS_DEBUG ("mono_background_exec on thread %p done job %p \n", (gpointer)pthread_self(), (gpointer)cb); diff --git a/src/mono/sample/wasm/browser-threads-minimal/fetchhelper.js b/src/mono/sample/wasm/browser-threads-minimal/fetchhelper.js index ac740859b0299..74b168a8a8bd7 100644 --- a/src/mono/sample/wasm/browser-threads-minimal/fetchhelper.js +++ b/src/mono/sample/wasm/browser-threads-minimal/fetchhelper.js @@ -3,9 +3,9 @@ export function delay(timeoutMs) { return new Promise(resolve => setTimeout(resolve, timeoutMs)); } -export async function responseText(response, delay) /* Promise */ { - console.log(`artificially waiting for response for ${delay} ms`); - await delay(delay); +export async function responseText(response, timeoutMs) /* Promise */ { + console.log(`artificially waiting for response for ${timeoutMs} ms`); + await delay(timeoutMs); console.log("artificial waiting done"); return await response.text(); }