Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[browser][MT] mono_wasm_schedule_synchronization_context #100251

Merged
merged 14 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ internal static unsafe partial class Runtime

#if FEATURE_WASM_MANAGED_THREADS
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void InstallWebWorkerInterop(nint proxyContextGCHandle, void* beforeSyncJSImport, void* afterSyncJSImport);
public static extern void InstallWebWorkerInterop(nint proxyContextGCHandle, void* beforeSyncJSImport, void* afterSyncJSImport, void* pumpHandler);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void UninstallWebWorkerInterop();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ public static unsafe JSSynchronizationContext InstallWebWorkerInterop(bool isMai

Interop.Runtime.InstallWebWorkerInterop(proxyContext.ContextHandle,
(delegate* unmanaged[Cdecl]<JSMarshalerArgument*, void>)&JavaScriptExports.BeforeSyncJSExport,
(delegate* unmanaged[Cdecl]<JSMarshalerArgument*, void>)&JavaScriptExports.AfterSyncJSExport);
(delegate* unmanaged[Cdecl]<JSMarshalerArgument*, void>)&JavaScriptExports.AfterSyncJSExport,
(delegate* unmanaged[Cdecl]<void>)&PumpHandler);

return ctx;
}
Expand Down Expand Up @@ -170,7 +171,7 @@ private unsafe void ScheduleJSPump()
{
// 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.
TargetThreadScheduleBackgroundJob(ProxyContext.NativeTID, (delegate* unmanaged[Cdecl]<void>)&BackgroundJobHandler);
ScheduleSynchronizationContext(ProxyContext.NativeTID);
}

public override void Post(SendOrPostCallback d, object? state)
Expand Down Expand Up @@ -236,13 +237,13 @@ public override void Send(SendOrPostCallback d, object? state)
}

[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern unsafe void TargetThreadScheduleBackgroundJob(IntPtr targetTID, void* callback);
internal static extern unsafe void ScheduleSynchronizationContext(IntPtr targetTID);

#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 target thread, called from mono_background_exec
private static void BackgroundJobHandler()
private static void PumpHandler()
{
var ctx = JSProxyContext.AssertIsInteropThread();
ctx.SynchronizationContext.Pump();
Expand Down Expand Up @@ -286,7 +287,7 @@ private void Pump()
}
catch (Exception e)
{
Environment.FailFast($"JSSynchronizationContext.BackgroundJobHandler failed, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {e.StackTrace}");
Environment.FailFast($"JSSynchronizationContext.Pump failed, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {e.StackTrace}");
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/mono/browser/runtime/corebindings.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ void mono_wasm_resolve_or_reject_promise_post (pthread_t target_tid, void *args)
void mono_wasm_cancel_promise_post (pthread_t target_tid, int task_holder_gc_handle);

extern void mono_wasm_install_js_worker_interop (int context_gc_handle);
void mono_wasm_install_js_worker_interop_wrapper (int context_gc_handle, void* beforeSyncJSImport, void* afterSyncJSImport);
void mono_wasm_install_js_worker_interop_wrapper (int context_gc_handle, void* beforeSyncJSImport, void* afterSyncJSImport, void* pumpHandler);
extern void mono_wasm_uninstall_js_worker_interop ();
extern void mono_wasm_invoke_jsimport_MT (void* signature, void* args);
void mono_wasm_invoke_jsimport_async_post (pthread_t target_tid, void* signature, void* args);
Expand Down Expand Up @@ -258,11 +258,13 @@ void mono_wasm_get_assembly_export (char *assembly_name, char *namespace, char *

void* before_sync_js_import;
void* after_sync_js_import;
void* synchronization_context_pump_handler;

void mono_wasm_install_js_worker_interop_wrapper (int context_gc_handle, void* beforeSyncJSImport, void* afterSyncJSImport)
void mono_wasm_install_js_worker_interop_wrapper (int context_gc_handle, void* beforeSyncJSImport, void* afterSyncJSImport, void* pumpHandler)
{
before_sync_js_import = beforeSyncJSImport;
after_sync_js_import = afterSyncJSImport;
synchronization_context_pump_handler = pumpHandler;
mono_wasm_install_js_worker_interop (context_gc_handle);
}

Expand Down
2 changes: 2 additions & 0 deletions src/mono/browser/runtime/cwraps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const threading_cwraps: SigLine[] = WasmEnableThreads ? [
[true, "mono_wasm_register_ui_thread", "void", []],
[true, "mono_wasm_register_io_thread", "void", []],
[true, "mono_wasm_print_thread_dump", "void", []],
[true, "mono_wasm_synchronization_context_pump", "void", []],
[true, "mono_threads_wasm_sync_run_in_target_thread_done", "void", ["number"]],
] : [];

Expand Down Expand Up @@ -157,6 +158,7 @@ export interface t_ThreadingCwraps {
mono_wasm_register_ui_thread(): void;
mono_wasm_register_io_thread(): void;
mono_wasm_print_thread_dump(): void;
mono_wasm_synchronization_context_pump(): void;
mono_threads_wasm_sync_run_in_target_thread_done(sem: VoidPtr): void;
}

Expand Down
7 changes: 7 additions & 0 deletions src/mono/browser/runtime/driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,10 @@ mono_wasm_invoke_jsexport_async_post (void* target_thread, MonoMethod *method, v


typedef void (*js_interop_event)(void* args);
typedef void (*sync_context_pump)(void);
extern js_interop_event before_sync_js_import;
extern js_interop_event after_sync_js_import;
extern sync_context_pump synchronization_context_pump_handler;

// this is running on the target thread
EMSCRIPTEN_KEEPALIVE void
Expand All @@ -306,6 +308,11 @@ mono_wasm_invoke_jsexport_sync_send (void* target_thread, MonoMethod *method, vo
mono_threads_wasm_sync_run_in_target_thread_vii (target_thread, (void (*)(gpointer, gpointer))mono_wasm_invoke_jsexport_sync, method, args);
}

EMSCRIPTEN_KEEPALIVE void mono_wasm_synchronization_context_pump (void)
{
synchronization_context_pump_handler ();
}

#endif /* DISABLE_THREADS */

EMSCRIPTEN_KEEPALIVE void
Expand Down
2 changes: 2 additions & 0 deletions src/mono/browser/runtime/exports-binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
mono_wasm_pthread_on_pthread_registered, mono_wasm_pthread_set_name, mono_wasm_install_js_worker_interop, mono_wasm_uninstall_js_worker_interop, mono_wasm_start_io_thread_async
} from "./pthreads";
import { mono_wasm_dump_threads } from "./pthreads/ui-thread";
import { mono_wasm_schedule_synchronization_context } from "./pthreads/shared";


// the JS methods would be visible to EMCC linker and become imports of the WASM module
Expand All @@ -44,6 +45,7 @@ export const mono_wasm_threads_imports = !WasmEnableThreads ? [] : [
mono_wasm_pthread_set_name,
mono_wasm_start_deputy_thread_async,
mono_wasm_start_io_thread_async,
mono_wasm_schedule_synchronization_context,

// mono-threads.c
mono_wasm_dump_threads,
Expand Down
2 changes: 2 additions & 0 deletions src/mono/browser/runtime/pthreads/deputy-thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { monoThreadInfo, postMessageToMain, update_thread_info } from "./shared"
import { Module, loaderHelpers, runtimeHelpers } from "../globals";
import { start_runtime } from "../startup";
import { WorkerToMainMessageType } from "../types/internal";
import { forceThreadMemoryViewRefresh } from "../memory";

export function mono_wasm_start_deputy_thread_async () {
if (!WasmEnableThreads) return;
Expand All @@ -28,6 +29,7 @@ export function mono_wasm_start_deputy_thread_async () {
Module.runtimeKeepalivePush();
Module.safeSetTimeout(async () => {
try {
forceThreadMemoryViewRefresh();

await start_runtime();

Expand Down
15 changes: 15 additions & 0 deletions src/mono/browser/runtime/pthreads/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { set_thread_prefix } from "../logging";
import { bindings_init } from "../startup";
import { forceDisposeProxies } from "../gc-handles";
import { monoMessageSymbol, GCHandleNull, PThreadPtrNull, WorkerToMainMessageType } from "../types/internal";
import { threads_c_functions as tcwraps } from "../cwraps";
import { forceThreadMemoryViewRefresh } from "../memory";

// A duplicate in loader/assets.ts
export const worker_empty_prefix = " - ";
Expand Down Expand Up @@ -105,6 +107,19 @@ export function update_thread_info (): void {
}
}

export function exec_synchronization_context_pump (): void {
if (!loaderHelpers.is_runtime_running()) {
return;
}
forceThreadMemoryViewRefresh();
tcwraps.mono_wasm_synchronization_context_pump();
}

export function mono_wasm_schedule_synchronization_context (): void {
if (!WasmEnableThreads) return;
Module.safeSetTimeout(exec_synchronization_context_pump, 0);
}

export function mono_wasm_pthread_ptr (): PThreadPtr {
if (!WasmEnableThreads) return PThreadPtrNull;
return (<any>Module)["_pthread_self"]();
Expand Down
36 changes: 8 additions & 28 deletions src/mono/browser/runtime/scheduling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
import WasmEnableThreads from "consts:wasmEnableThreads";

import cwraps from "./cwraps";
import { ENVIRONMENT_IS_WORKER, Module, loaderHelpers } from "./globals";
import { Module, loaderHelpers } from "./globals";
import { forceThreadMemoryViewRefresh } from "./memory";
import { is_thread_available } from "./pthreads";

let spread_timers_maximum = 0;
let pump_count = 0;

export function prevent_timer_throttling (): void {
if (WasmEnableThreads) return;
if (!loaderHelpers.isChromium) {
return;
}
Expand All @@ -30,66 +30,46 @@ export function prevent_timer_throttling (): void {
}

function prevent_timer_throttling_tick () {
if (WasmEnableThreads) return;
Module.maybeExit();
if (!loaderHelpers.is_runtime_running()) {
return;
}
if (WasmEnableThreads) {
forceThreadMemoryViewRefresh();
}
cwraps.mono_wasm_execute_timer();
pump_count++;
mono_background_exec_until_done();
}

function mono_background_exec_until_done () {
if (WasmEnableThreads) return;
Module.maybeExit();
if (!loaderHelpers.is_runtime_running()) {
return;
}
if (WasmEnableThreads) {
forceThreadMemoryViewRefresh();
}
while (pump_count > 0) {
--pump_count;
cwraps.mono_background_exec();
}
}

export function schedule_background_exec (): void {
if (WasmEnableThreads) return;
++pump_count;
let max_postpone_count = 10;
function postpone_schedule_background () {
if (max_postpone_count < 0 || is_thread_available()) {
Module.safeSetTimeout(mono_background_exec_until_done, 0);
} else {
max_postpone_count--;
Module.safeSetTimeout(postpone_schedule_background, 10);
}
}

if (WasmEnableThreads && !ENVIRONMENT_IS_WORKER) {
// give threads chance to load before we run more synchronous code on UI thread
postpone_schedule_background();
} else {
Module.safeSetTimeout(mono_background_exec_until_done, 0);
}
Module.safeSetTimeout(mono_background_exec_until_done, 0);
}

let lastScheduledTimeoutId: any = undefined;
export function mono_wasm_schedule_timer (shortestDueTimeMs: number): void {
if (WasmEnableThreads) return;
if (lastScheduledTimeoutId) {
globalThis.clearTimeout(lastScheduledTimeoutId);
lastScheduledTimeoutId = undefined;
// NOTE: Multi-threaded Module.safeSetTimeout() does the runtimeKeepalivePush()
// and non-Multi-threaded Module.safeSetTimeout does not runtimeKeepalivePush()
// but clearTimeout does not runtimeKeepalivePop() so we need to do it here in MT only.
if (WasmEnableThreads) Module.runtimeKeepalivePop();
}
lastScheduledTimeoutId = Module.safeSetTimeout(mono_wasm_schedule_timer_tick, shortestDueTimeMs);
}

function mono_wasm_schedule_timer_tick () {
if (WasmEnableThreads) return;
Module.maybeExit();
if (WasmEnableThreads) {
forceThreadMemoryViewRefresh();
Expand Down
14 changes: 9 additions & 5 deletions src/mono/mono/mini/mini-wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -444,14 +444,17 @@ 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_wasm_execute_timer (void);

//JS functions imported that we use
#ifdef DISABLE_THREADS
EMSCRIPTEN_KEEPALIVE void mono_wasm_execute_timer (void);
EMSCRIPTEN_KEEPALIVE void mono_background_exec (void);
extern void mono_wasm_schedule_timer (int shortestDueTimeMs);
#else
extern void mono_target_thread_schedule_synchronization_context(MonoNativeThreadId target_thread);
#endif // DISABLE_THREADS
G_END_DECLS

void mono_background_exec (void);

#endif // HOST_BROWSER

gpointer
Expand Down Expand Up @@ -588,6 +591,8 @@ mono_thread_state_init_from_handle (MonoThreadUnwindState *tctx, MonoThreadInfo
return FALSE;
}

#ifdef DISABLE_THREADS

// this points to System.Threading.TimerQueue.TimerHandler C# method
static void *timer_handler;

Expand All @@ -605,7 +610,6 @@ mono_wasm_execute_timer (void)
MONO_EXIT_GC_UNSAFE;
}

#ifdef DISABLE_THREADS
void
mono_wasm_main_thread_schedule_timer (void *timerHandler, int shortestDueTimeMs)
{
Expand All @@ -626,7 +630,7 @@ mono_arch_register_icall (void)
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);
#else
mono_add_internal_call_internal ("System.Runtime.InteropServices.JavaScript.JSSynchronizationContext::TargetThreadScheduleBackgroundJob", mono_target_thread_schedule_background_job);
mono_add_internal_call_internal ("System.Runtime.InteropServices.JavaScript.JSSynchronizationContext::ScheduleSynchronizationContext", mono_target_thread_schedule_synchronization_context);
#endif /* DISABLE_THREADS */
#endif /* HOST_BROWSER */
}
Expand Down
7 changes: 7 additions & 0 deletions src/mono/mono/mini/mini-wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,18 @@ typedef struct {
// sdks/wasm/driver.c is C and uses this
G_EXTERN_C void mono_wasm_enable_debugging (int log_level);

#ifdef HOST_BROWSER

//JS functions imported that we use
#ifdef DISABLE_THREADS
void mono_wasm_execute_timer (void);
void mono_wasm_main_thread_schedule_timer (void *timerHandler, int shortestDueTimeMs);
#endif // DISABLE_THREADS

void mono_wasm_print_stack_trace (void);
#endif // HOST_BROWSER



gboolean
mini_wasm_is_scalar_vtype (MonoType *type, MonoType **etype);
Expand Down
Loading
Loading