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] invocation of user JS in UI thread till next event loop tick #100040

Merged
merged 3 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -68,6 +68,9 @@ internal struct JSMarshalerArgumentImpl

[FieldOffset(24)]
internal IntPtr CallerNativeTID;

[FieldOffset(28)]
internal IntPtr SyncDoneSemaphorePtr;
#endif
}

Expand Down
6 changes: 6 additions & 0 deletions src/mono/browser/runtime/cancelable-promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { compareExchangeI32, forceThreadMemoryViewRefresh } from "./memory";
import { mono_log_debug } from "./logging";
import { complete_task } from "./managed-exports";
import { marshal_cs_object_to_cs } from "./marshal-to-cs";
import { invoke_later_when_on_ui_thread_async } from "./invoke-js";

export const _are_promises_supported = ((typeof Promise === "object") || (typeof Promise === "function")) && (typeof Promise.resolve === "function");

Expand All @@ -35,6 +36,11 @@ export function wrap_as_cancelable<T>(inner: Promise<T>): ControllablePromise<T>
}

export function mono_wasm_cancel_promise(task_holder_gc_handle: GCHandle): void {
// cancelation should not arrive earlier than the promise created by marshaling in mono_wasm_invoke_jsimport_MT
invoke_later_when_on_ui_thread_async(() => mono_wasm_cancel_promise_impl(task_holder_gc_handle));
}

export function mono_wasm_cancel_promise_impl(task_holder_gc_handle: GCHandle): void {
if (!loaderHelpers.is_runtime_running()) {
mono_log_debug("This promise can't be canceled, mono runtime already exited.");
return;
Expand Down
10 changes: 5 additions & 5 deletions src/mono/browser/runtime/corebindings.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ void mono_wasm_cancel_promise_post (pthread_t target_tid, int task_holder_gc_han
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);
extern void mono_wasm_uninstall_js_worker_interop ();
extern void mono_wasm_invoke_jsimport (void* signature, void* args);
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);
void mono_wasm_invoke_jsimport_sync_send (pthread_t target_tid, void* signature, void* args);
void mono_wasm_invoke_js_function_send (pthread_t target_tid, int function_js_handle, void *args);
extern void mono_threads_wasm_async_run_in_target_thread_vi (pthread_t target_thread, void (*func) (gpointer), gpointer user_data1);
extern void mono_threads_wasm_async_run_in_target_thread_vii (pthread_t target_thread, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer user_data2);
extern void mono_threads_wasm_sync_run_in_target_thread_vii (pthread_t target_thread, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer user_data2);
extern void mono_threads_wasm_sync_run_in_target_thread_vii (pthread_t target_thread, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer args);
#else
extern void mono_wasm_bind_js_import (void *signature, int *is_exception, MonoObject **result);
extern void mono_wasm_invoke_jsimport_ST (int function_handle, void *args);
Expand Down Expand Up @@ -80,7 +80,7 @@ void bindings_initialize_internals (void)
mono_add_internal_call ("Interop/Runtime::ResolveOrRejectPromisePost", mono_wasm_resolve_or_reject_promise_post);
mono_add_internal_call ("Interop/Runtime::InstallWebWorkerInterop", mono_wasm_install_js_worker_interop_wrapper);
mono_add_internal_call ("Interop/Runtime::UninstallWebWorkerInterop", mono_wasm_uninstall_js_worker_interop);
mono_add_internal_call ("Interop/Runtime::InvokeJSImportSync", mono_wasm_invoke_jsimport);
mono_add_internal_call ("Interop/Runtime::InvokeJSImportSync", mono_wasm_invoke_jsimport_MT);
mono_add_internal_call ("Interop/Runtime::InvokeJSImportSyncSend", mono_wasm_invoke_jsimport_sync_send);
mono_add_internal_call ("Interop/Runtime::InvokeJSImportAsyncPost", mono_wasm_invoke_jsimport_async_post);
mono_add_internal_call ("Interop/Runtime::InvokeJSFunctionSend", mono_wasm_invoke_js_function_send);
Expand Down Expand Up @@ -285,13 +285,13 @@ void mono_wasm_cancel_promise_post (pthread_t target_tid, int task_holder_gc_han
// async
void mono_wasm_invoke_jsimport_async_post (pthread_t target_tid, void* signature, void* args)
{
mono_threads_wasm_async_run_in_target_thread_vii (target_tid, (void (*) (gpointer, gpointer))mono_wasm_invoke_jsimport, (gpointer)signature, (gpointer)args);
mono_threads_wasm_async_run_in_target_thread_vii (target_tid, (void (*) (gpointer, gpointer))mono_wasm_invoke_jsimport_MT, (gpointer)signature, (gpointer)args);
}

// sync
void mono_wasm_invoke_jsimport_sync_send (pthread_t target_tid, void* signature, void* args)
{
mono_threads_wasm_sync_run_in_target_thread_vii (target_tid, (void (*) (gpointer, gpointer))mono_wasm_invoke_jsimport, (gpointer)signature, (gpointer)args);
mono_threads_wasm_sync_run_in_target_thread_vii (target_tid, (void (*) (gpointer, gpointer))mono_wasm_invoke_jsimport_MT, (gpointer)signature, (gpointer)args);
}

// sync
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_threads_wasm_sync_run_in_target_thread_done", "void", ["number"]],
] : [];

// when the method is assigned/cached at usage, instead of being invoked directly from cwraps, it can't be marked lazy, because it would be re-bound on each call
Expand Down Expand Up @@ -156,6 +157,7 @@ export interface t_ThreadingCwraps {
mono_wasm_register_ui_thread(): void;
mono_wasm_register_io_thread(): void;
mono_wasm_print_thread_dump(): void;
mono_threads_wasm_sync_run_in_target_thread_done(sem: VoidPtr): void;
}

export interface t_ProfilerCwraps {
Expand Down
4 changes: 2 additions & 2 deletions src/mono/browser/runtime/driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ mono_wasm_invoke_jsexport (MonoMethod *method, void* args)
#ifndef DISABLE_THREADS

extern void mono_threads_wasm_async_run_in_target_thread_vii (void* target_thread, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer user_data2);
extern void mono_threads_wasm_sync_run_in_target_thread_vii (void* target_thread, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer user_data2);
extern void mono_threads_wasm_sync_run_in_target_thread_vii (void* target_thread, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer args);
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
extern void mono_print_thread_dump (void *sigctx);

EMSCRIPTEN_KEEPALIVE void
Expand Down Expand Up @@ -303,7 +303,7 @@ mono_wasm_invoke_jsexport_sync (MonoMethod *method, void* args)
EMSCRIPTEN_KEEPALIVE void
mono_wasm_invoke_jsexport_sync_send (void* target_thread, MonoMethod *method, void* args /*JSMarshalerArguments*/)
{
mono_threads_wasm_sync_run_in_target_thread_vii(target_thread, (void (*)(gpointer, gpointer))mono_wasm_invoke_jsexport_sync, method, args);
mono_threads_wasm_sync_run_in_target_thread_vii (target_thread, (void (*)(gpointer, gpointer))mono_wasm_invoke_jsexport_sync, method, args);
}

#endif /* DISABLE_THREADS */
Expand Down
4 changes: 2 additions & 2 deletions src/mono/browser/runtime/exports-binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads";

import { mono_wasm_debugger_log, mono_wasm_add_dbg_command_received, mono_wasm_set_entrypoint_breakpoint, mono_wasm_fire_debugger_agent_message_with_data, mono_wasm_fire_debugger_agent_message_with_data_to_pause } from "./debug";
import { mono_wasm_release_cs_owned_object } from "./gc-handles";
import { mono_wasm_bind_js_import, mono_wasm_invoke_js_function, mono_wasm_invoke_jsimport, mono_wasm_invoke_jsimport_ST } from "./invoke-js";
import { mono_wasm_bind_js_import, mono_wasm_invoke_js_function, mono_wasm_invoke_jsimport_MT, mono_wasm_invoke_jsimport_ST } from "./invoke-js";
import { mono_interp_tier_prepare_jiterpreter, mono_jiterp_free_method_data_js } from "./jiterpreter";
import { mono_interp_jit_wasm_entry_trampoline, mono_interp_record_interp_entry } from "./jiterpreter-interp-entry";
import { mono_interp_jit_wasm_jit_call_trampoline, mono_interp_invoke_wasm_jit_call_trampoline, mono_interp_flush_jitcall_queue } from "./jiterpreter-jit-call";
Expand Down Expand Up @@ -55,7 +55,7 @@ export const mono_wasm_threads_imports = !WasmEnableThreads ? [] : [
// corebindings.c
mono_wasm_install_js_worker_interop,
mono_wasm_uninstall_js_worker_interop,
mono_wasm_invoke_jsimport,
mono_wasm_invoke_jsimport_MT,
];

export const mono_wasm_imports = [
Expand Down
62 changes: 54 additions & 8 deletions src/mono/browser/runtime/invoke-js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads";
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, get_signature_handle, get_signature_function_name, get_signature_module_name, is_receiver_should_free, get_caller_native_tid } from "./marshal";
import { get_signature_argument_count, bound_js_function_symbol, get_sig, get_signature_version, get_signature_type, imported_js_function_symbol, get_signature_handle, get_signature_function_name, get_signature_module_name, is_receiver_should_free, get_caller_native_tid, get_sync_done_semaphore_ptr } from "./marshal";
import { setI32_unchecked, receiveWorkerHeapViews, forceThreadMemoryViewRefresh } from "./memory";
import { stringToMonoStringRoot } from "./strings";
import { MonoObject, MonoObjectRef, JSFunctionSignature, JSMarshalerArguments, WasmRoot, BoundMarshalerToJs, JSFnHandle, BoundMarshalerToCs, JSHandle, MarshalerType } from "./types/internal";
Expand All @@ -17,6 +17,8 @@ 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 { threads_c_functions as tcwraps } from "./cwraps";
import { monoThreadInfo } from "./pthreads";

export const js_import_wrapper_by_fn_handle: Function[] = <any>[null];// 0th slot is dummy, main thread we free them on shutdown. On web worker thread we free them when worker is detached.

Expand All @@ -35,7 +37,7 @@ export function mono_wasm_bind_js_import(signature: JSFunctionSignature, is_exce
}
}

export function mono_wasm_invoke_jsimport(signature: JSFunctionSignature, args: JSMarshalerArguments) {
export function mono_wasm_invoke_jsimport_MT(signature: JSFunctionSignature, args: JSMarshalerArguments) {
if (!WasmEnableThreads) return;
assert_js_interop();

Expand Down Expand Up @@ -137,7 +139,6 @@ function bind_js_import(signature: JSFunctionSignature): Function {
forceThreadMemoryViewRefresh();
bound_fn(args);
}

function sync_bound_fn(args: JSMarshalerArguments): void {
const previous = runtimeHelpers.isPendingSynchronousCall;
try {
Expand All @@ -150,14 +151,29 @@ function bind_js_import(signature: JSFunctionSignature): Function {
runtimeHelpers.isPendingSynchronousCall = previous;
}
}
function async_bound_fn_ui(args: JSMarshalerArguments): void {
invoke_later_when_on_ui_thread_async(() => async_bound_fn(args));
}
function sync_bound_fn_ui(args: JSMarshalerArguments): void {
invoke_later_when_on_ui_thread_sync(() => sync_bound_fn(args), args);
}

let wrapped_fn: WrappedJSFunction = bound_fn;
if (WasmEnableThreads) {
if (is_async || is_discard_no_wait) {
wrapped_fn = async_bound_fn;
}
else {
wrapped_fn = sync_bound_fn;
if (monoThreadInfo.isUI) {
if (is_async || is_discard_no_wait) {
wrapped_fn = async_bound_fn_ui;
}
else {
wrapped_fn = sync_bound_fn_ui;
}
} else {
if (is_async || is_discard_no_wait) {
wrapped_fn = async_bound_fn;
}
else {
wrapped_fn = sync_bound_fn;
}
}
}

Expand Down Expand Up @@ -337,6 +353,10 @@ type BindingClosure = {
}

export function mono_wasm_invoke_js_function(bound_function_js_handle: JSHandle, args: JSMarshalerArguments): void {
invoke_later_when_on_ui_thread_sync(() => mono_wasm_invoke_js_function_impl(bound_function_js_handle, args), args);
}

export function mono_wasm_invoke_js_function_impl(bound_function_js_handle: JSHandle, args: JSMarshalerArguments): void {
loaderHelpers.assert_runtime_running();
const bound_fn = mono_wasm_get_jsobj_from_js_handle(bound_function_js_handle);
mono_assert(bound_fn && typeof (bound_fn) === "function" && bound_fn[bound_js_function_symbol], () => `Bound function handle expected ${bound_function_js_handle}`);
Expand Down Expand Up @@ -493,3 +513,29 @@ export function assert_c_interop(): void {
mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "The runtime must be initialized.");
}
}

// make sure we are not blocking em_task_queue_execute up the call stack
// so that when we call back to managed, the FS calls could still be processed by the UI thread
// see also emscripten_yield which can process the FS calls inside the spin wait
export function invoke_later_when_on_ui_thread_sync(fn: Function, args: JSMarshalerArguments) {
if (WasmEnableThreads && monoThreadInfo.isUI) {
Module.safeSetTimeout(() => {
fn();
// see also mono_threads_wasm_sync_run_in_target_thread_vii_cb
const done_semaphore = get_sync_done_semaphore_ptr(args);
tcwraps.mono_threads_wasm_sync_run_in_target_thread_done(done_semaphore);
}, 0);
} else {
fn();
}
}

// make sure we are not blocking em_task_queue_execute up the call stack
// so that when we call back to managed, the FS calls could still be processed by the UI thread
export function invoke_later_when_on_ui_thread_async(fn: Function) {
if (WasmEnableThreads && monoThreadInfo.isUI) {
Module.safeSetTimeout(fn, 0);
} else {
fn();
}
}
5 changes: 5 additions & 0 deletions src/mono/browser/runtime/marshal-to-js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory";
import { call_delegate } from "./managed-exports";
import { gc_locked } from "./gc-lock";
import { mono_log_debug } from "./logging";
import { invoke_later_when_on_ui_thread_async } from "./invoke-js";

export function initialize_marshalers_to_js(): void {
if (cs_to_js_marshalers.size == 0) {
Expand Down Expand Up @@ -338,6 +339,10 @@ function create_task_holder(res_converter?: MarshalerToJs) {
}

export function mono_wasm_resolve_or_reject_promise(args: JSMarshalerArguments): void {
// rejection/resolution should not arrive earlier than the promise created by marshaling in mono_wasm_invoke_jsimport_MT
invoke_later_when_on_ui_thread_async(() => mono_wasm_resolve_or_reject_promise_impl(args));
}
export function mono_wasm_resolve_or_reject_promise_impl(args: JSMarshalerArguments): void {
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
if (!loaderHelpers.is_runtime_running()) {
mono_log_debug("This promise resolution/rejection can't be propagated to managed code, mono runtime already exited.");
return;
Expand Down
9 changes: 8 additions & 1 deletion src/mono/browser/runtime/marshal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { js_owned_gc_handle_symbol, teardown_managed_proxy } from "./gc-handles"
import { Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals";
import { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8, localHeapViewF64, localHeapViewI32, localHeapViewU8, _zero_region, getB32, setB32, forceThreadMemoryViewRefresh } from "./memory";
import { mono_wasm_new_external_root } from "./roots";
import { GCHandle, JSHandle, MonoObject, MonoString, GCHandleNull, JSMarshalerArguments, JSFunctionSignature, JSMarshalerType, JSMarshalerArgument, MarshalerToJs, MarshalerToCs, WasmRoot, MarshalerType, PThreadPtr, PThreadPtrNull } from "./types/internal";
import { GCHandle, JSHandle, MonoObject, MonoString, GCHandleNull, JSMarshalerArguments, JSFunctionSignature, JSMarshalerType, JSMarshalerArgument, MarshalerToJs, MarshalerToCs, WasmRoot, MarshalerType, PThreadPtr, PThreadPtrNull, VoidPtrNull } from "./types/internal";
import { TypedArray, VoidPtr } from "./types/emscripten";
import { utf16ToString } from "./strings";
import { get_managed_stack_trace } from "./managed-exports";
Expand Down Expand Up @@ -39,6 +39,7 @@ const enum JSMarshalerArgumentOffsets {
ContextHandle = 16,
ReceiverShouldFree = 20,
CallerNativeTID = 24,
SyncDoneSemaphorePtr = 28,
}
export const JSMarshalerTypeSize = 32;
// keep in sync with JSFunctionBinding.JSBindingType
Expand Down Expand Up @@ -91,6 +92,12 @@ export function is_receiver_should_free(args: JSMarshalerArguments): boolean {
return getB32(<any>args + JSMarshalerArgumentOffsets.ReceiverShouldFree);
}

export function get_sync_done_semaphore_ptr(args: JSMarshalerArguments): VoidPtr {
if (!WasmEnableThreads) return VoidPtrNull;
mono_assert(args, "Null args");
return getI32(<any>args + JSMarshalerArgumentOffsets.SyncDoneSemaphorePtr) as any;
}

export function get_caller_native_tid(args: JSMarshalerArguments): PThreadPtr {
if (!WasmEnableThreads) return PThreadPtrNull;
mono_assert(args, "Null args");
Expand Down
25 changes: 20 additions & 5 deletions src/mono/mono/utils/mono-threads-wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -661,18 +661,33 @@ mono_threads_wasm_async_run_in_target_thread_vii (pthread_t target_thread, void
emscripten_dispatch_to_thread_async (target_thread, EM_FUNC_SIG_VII, func, NULL, user_data1, user_data2);
}

static void mono_threads_wasm_sync_run_in_target_thread_vii_cb (MonoCoopSem *done, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer user_data2)
static void mono_threads_wasm_sync_run_in_target_thread_vii_cb (MonoCoopSem *done, void (*func) (gpointer, gpointer), gpointer user_data1, void* args)
{
// in UI thread we postpone the execution via safeSetTimeout so that emscripten_proxy_execute_queue is not blocked by this call
// see invoke_later_on_ui_thread
if (mono_threads_wasm_is_ui_thread()) {
MonoCoopSem **semPtrPtr = (MonoCoopSem **)(args + 28/*JSMarshalerArgumentOffsets.SyncDoneSemaphorePtr*/);
*semPtrPtr = done;
func (user_data1, args);
}
else {
func (user_data1, args);
mono_coop_sem_post (done);
}
}

EMSCRIPTEN_KEEPALIVE void
mono_threads_wasm_sync_run_in_target_thread_done (MonoCoopSem *sem)
{
func (user_data1, user_data2);
mono_coop_sem_post (done);
mono_coop_sem_post (sem);
}

void
mono_threads_wasm_sync_run_in_target_thread_vii (pthread_t target_thread, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer user_data2)
mono_threads_wasm_sync_run_in_target_thread_vii (pthread_t target_thread, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer args)
{
MonoCoopSem sem;
mono_coop_sem_init (&sem, 0);
emscripten_dispatch_to_thread_async (target_thread, EM_FUNC_SIG_VIIII, mono_threads_wasm_sync_run_in_target_thread_vii_cb, NULL, &sem, func, user_data1, user_data2);
emscripten_dispatch_to_thread_async (target_thread, EM_FUNC_SIG_VIIII, mono_threads_wasm_sync_run_in_target_thread_vii_cb, NULL, &sem, func, user_data1, args);

MONO_ENTER_GC_UNSAFE;
mono_coop_sem_wait (&sem, MONO_SEM_FLAGS_NONE);
Expand Down
5 changes: 4 additions & 1 deletion src/mono/mono/utils/mono-threads-wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ void
mono_threads_wasm_async_run_in_target_thread_vii (pthread_t target_thread, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer user_data2);

void
mono_threads_wasm_sync_run_in_target_thread_vii (pthread_t target_thread, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer user_data2);
mono_threads_wasm_sync_run_in_target_thread_vii (pthread_t target_thread, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer args);

void
mono_threads_wasm_sync_run_in_target_thread_done (MonoCoopSem *sem);

static inline
int32_t
Expand Down
Loading
Loading