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] preparation for deputy #98298

Merged
merged 10 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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 @@ -45,7 +45,7 @@ public static void CancelPromise(Task promise)
// FIXME: race condition
// we know that holder.GCHandle is still valid because we hold the ProxyContext lock
// but the message may arrive to the target thread after it was resolved, making GCHandle invalid
Interop.Runtime.CancelPromisePost(holder.ProxyContext.NativeTID, holder.GCHandle);
Interop.Runtime.CancelPromisePost(holder.ProxyContext.JSNativeTID, holder.GCHandle);
}
}
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,11 +296,11 @@ public static void GetManagedStackTrace(JSMarshalerArgument* arguments_buffer)

// this is here temporarily, until JSWebWorker becomes public API
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker", "System.Runtime.InteropServices.JavaScript")]
// the marshaled signature is:
// void InstallMainSynchronizationContext()
public static void InstallMainSynchronizationContext()
public static void InstallMainSynchronizationContext(nint* nativeTIDAndProxyContextGCHandlePtr)
{
JSSynchronizationContext.InstallWebWorkerInterop(true, CancellationToken.None);
var jsSynchronizationContext = JSSynchronizationContext.InstallWebWorkerInterop(true, CancellationToken.None);
jsSynchronizationContext.ProxyContext.JSNativeTID = *nativeTIDAndProxyContextGCHandlePtr;
*nativeTIDAndProxyContextGCHandlePtr = jsSynchronizationContext.ProxyContext.ContextHandle;
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
}

#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ internal static unsafe void DispatchJSFunctionSync(JSObject jsFunction, Span<JSM
// we also don't throw PNSE here, because we know that the target has JS interop installed and that it could not block
// so it could take some time, while target is CPU busy, but not forever
// see also https://github.com/dotnet/runtime/issues/76958#issuecomment-1921418290
Interop.Runtime.InvokeJSFunctionSend(jsFunction.ProxyContext.NativeTID, functionHandle, args);
Interop.Runtime.InvokeJSFunctionSend(jsFunction.ProxyContext.JSNativeTID, functionHandle, args);

ref JSMarshalerArgument exceptionArg = ref arguments[0];
if (exceptionArg.slot.Type != MarshalerType.None)
Expand Down Expand Up @@ -360,7 +360,7 @@ internal static unsafe void DispatchJSImportSyncSend(JSFunctionBinding signature
// we also don't throw PNSE here, because we know that the target has JS interop installed and that it could not block
// so it could take some time, while target is CPU busy, but not forever
// see also https://github.com/dotnet/runtime/issues/76958#issuecomment-1921418290
Interop.Runtime.InvokeJSImportSyncSend(targetContext.NativeTID, args, sig);
Interop.Runtime.InvokeJSImportSyncSend(targetContext.JSNativeTID, args, sig);

ref JSMarshalerArgument exceptionArg = ref arguments[0];
if (exceptionArg.slot.Type != MarshalerType.None)
Expand All @@ -384,7 +384,7 @@ internal static unsafe void DispatchJSImportAsyncPost(JSFunctionBinding signatur
// we already know that we are not on the right thread
// this will return quickly after sending the message
// async
Interop.Runtime.InvokeJSImportAsyncPost(targetContext.NativeTID, (nint)cpy, sig);
Interop.Runtime.InvokeJSImportAsyncPost(targetContext.JSNativeTID, (nint)cpy, sig);

}

Expand Down Expand Up @@ -455,7 +455,7 @@ internal static unsafe void ResolveOrRejectPromise(JSProxyContext targetContext,
Unsafe.CopyBlock(cpy, src, (uint)bytes);

// async
Interop.Runtime.ResolveOrRejectPromisePost(targetContext.NativeTID, (nint)cpy);
Interop.Runtime.ResolveOrRejectPromisePost(targetContext.JSNativeTID, (nint)cpy);

// this never throws directly
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,14 @@ internal sealed class JSProxyContext : IDisposable
private JSProxyContext()
{
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
#pragma warning disable CA1822 // Mark members as static
public bool IsCurrentThread() => true;
#pragma warning restore CA1822 // Mark members as static
#else
public nint ContextHandle;
public nint NativeTID;
public nint JSNativeTID; // target thread where JavaScript is running
public int ManagedTID;
public bool IsMainThread;
public JSSynchronizationContext SynchronizationContext;
Expand All @@ -53,7 +58,7 @@ public static IntPtr GetNativeThreadId()
public JSProxyContext(bool isMainThread, JSSynchronizationContext synchronizationContext)
{
SynchronizationContext = synchronizationContext;
NativeTID = GetNativeThreadId();
JSNativeTID = GetNativeThreadId();
ManagedTID = Environment.CurrentManagedThreadId;
IsMainThread = isMainThread;
ContextHandle = (nint)GCHandle.Alloc(this, GCHandleType.Normal);
Expand Down Expand Up @@ -467,7 +472,7 @@ public static void ReleaseCSOwnedObject(JSObject jso, bool skipJS)

// this is async message, we need to call this as the last thing
// the same jsHandle would not be re-used until JS side considers it free
Interop.Runtime.ReleaseCSOwnedObjectPost(ctx.NativeTID, jsHandle);
Interop.Runtime.ReleaseCSOwnedObjectPost(ctx.JSNativeTID, jsHandle);
}
#else
Interop.Runtime.ReleaseCSOwnedObject(jsHandle);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,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, (void*)(delegate* unmanaged[Cdecl]<void>)&BackgroundJobHandler);
TargetThreadScheduleBackgroundJob(ProxyContext.JSNativeTID, (delegate* unmanaged[Cdecl]<void>)&BackgroundJobHandler);
}

public override void Post(SendOrPostCallback d, object? state)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public JSWebWorkerInstance(Func<Task<T>> body, CancellationToken cancellationTok
// TODO TaskCreationOptions.HideScheduler ?
_taskCompletionSource = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
_thread = new Thread(ThreadMain);
_thread.Name = "JSWebWorker";
_resultTask = null;
_cancellationToken = cancellationToken;
_cancellationRegistration = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,19 @@ static void MarshalResult(ref JSMarshalerArgument arg, object? taskResult)
public void ToJS(Task? value)
{
Task? task = value;
var ctx = ToJSContext;
var isCurrentThread = ctx.IsCurrentThread();

if (task == null)
{
if (!isCurrentThread)
{
Environment.FailFast("Marshalling null task to JS is not supported in MT");
}
slot.Type = MarshalerType.None;
return;
}
if (task.IsCompleted)
if (isCurrentThread && task.IsCompleted)
{
if (task.Exception != null)
{
Expand All @@ -252,8 +258,6 @@ public void ToJS(Task? value)
}
}

var ctx = ToJSContext;

if (slot.Type != MarshalerType.TaskPreCreated)
{
// this path should only happen when the Task is passed as argument of JSImport
Expand Down Expand Up @@ -298,14 +302,20 @@ static void Complete(Task task, object? th)
public void ToJS<T>(Task<T>? value, ArgumentToJSCallback<T> marshaler)
{
Task<T>? task = value;
var ctx = ToJSContext;
var isCurrentThread = ctx.IsCurrentThread();

if (task == null)
{
if (!isCurrentThread)
{
Environment.FailFast("NULL not supported in MT");
}
slot.Type = MarshalerType.None;
return;
}

if (task.IsCompleted)
if (isCurrentThread && task.IsCompleted)
{
if (task.Exception != null)
{
Expand All @@ -325,7 +335,6 @@ public void ToJS<T>(Task<T>? value, ArgumentToJSCallback<T> marshaler)
}
}

var ctx = ToJSContext;
if (slot.Type != MarshalerType.TaskPreCreated)
{
// this path should only happen when the Task is passed as argument of JSImport
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ await executor.Execute(async () =>
[Theory, MemberData(nameof(GetTargetThreadsAndBlockingCalls))]
public async Task WaitAssertsOnJSInteropThreads(Executor executor, NamedCall method)
{
var cts = CreateTestCaseTimeoutSource();
using var cts = CreateTestCaseTimeoutSource();
await executor.Execute(Task () =>
{
Exception? exception = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,18 @@ try {
}
}

// this is fake implementation of legacy `bind_static_method` which uses `mono_wasm_invoke_method_raw`
// this is fake implementation of legacy `bind_static_method` which uses `mono_wasm_invoke_method`
// We have unit tests that stop on unhandled managed exceptions.
// as opposed to [JSExport], the `mono_wasm_invoke_method_raw` doesn't handle managed exceptions.
// as opposed to [JSExport], the `mono_wasm_invoke_method` doesn't handle managed exceptions.
// Same way as old `bind_static_method` didn't
App.bind_static_method_native = (method_name) => {
try {
const monoMethodPtr = App.exports.DebuggerTests.BindStaticMethod.GetMonoMethodPtr(method_name);
// this is only implemented for void methods with no arguments
const invoker = runtime.Module.cwrap("mono_wasm_invoke_method_raw", "number", ["number", "number"]);
const invoker = runtime.Module.cwrap("mono_wasm_invoke_method", "number", ["number", "number", "number"]);
return function () {
try {
return invoker(monoMethodPtr);
return invoker(monoMethodPtr, 0, 0);
}
catch (err) {
console.error(err);
Expand Down
8 changes: 3 additions & 5 deletions src/mono/browser/runtime/cwraps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads";
import type {
MonoAssembly, MonoClass,
MonoMethod, MonoObject,
MonoType, MonoObjectRef, MonoStringRef, JSMarshalerArguments
MonoType, MonoObjectRef, MonoStringRef
} from "./types/internal";
import type { VoidPtr, CharPtrPtr, Int32Ptr, CharPtr, ManagedPointer } from "./types/emscripten";
import { Module, runtimeHelpers } from "./globals";
Expand Down Expand Up @@ -64,8 +64,7 @@ const fn_signatures: SigLine[] = [
[() => !runtimeHelpers.emscriptenBuildOptions.enableBrowserProfiler, "mono_wasm_profiler_init_aot", "void", ["string"]],
[true, "mono_wasm_profiler_init_browser", "void", ["number"]],
[false, "mono_wasm_exec_regression", "number", ["number", "string"]],
[false, "mono_wasm_invoke_method_bound", "number", ["number", "number", "number"]],
[false, "mono_wasm_invoke_method_raw", "number", ["number", "number"]],
[false, "mono_wasm_invoke_method", "number", ["number", "number", "number"]],
[true, "mono_wasm_write_managed_pointer_unsafe", "void", ["number", "number"]],
[true, "mono_wasm_copy_managed_pointer", "void", ["number", "number"]],
[true, "mono_wasm_i52_to_f64", "number", ["number", "number"]],
Expand Down Expand Up @@ -182,8 +181,7 @@ export interface t_Cwraps {
mono_wasm_getenv(name: string): CharPtr;
mono_wasm_set_main_args(argc: number, argv: VoidPtr): void;
mono_wasm_exec_regression(verbose_level: number, image: string): number;
mono_wasm_invoke_method_bound(method: MonoMethod, args: JSMarshalerArguments, fail: MonoStringRef): number;
mono_wasm_invoke_method_raw(method: MonoMethod, fail: MonoStringRef): number;
mono_wasm_invoke_method(method: MonoMethod, args: VoidPtr, fail: MonoStringRef): number;
mono_wasm_write_managed_pointer_unsafe(destination: VoidPtr | MonoObjectRef, pointer: ManagedPointer): void;
mono_wasm_copy_managed_pointer(destination: VoidPtr | MonoObjectRef, source: VoidPtr | MonoObjectRef): void;
mono_wasm_i52_to_f64(source: VoidPtr, error: Int32Ptr): number;
Expand Down
20 changes: 10 additions & 10 deletions src/mono/browser/runtime/dotnet.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,15 @@ type SingleAssetBehaviors =
/**
* Typically blazor.boot.json
*/
| "manifest";
| "manifest"
/**
* The debugging symbols
*/
| "symbols"
/**
* Load segmentation rules file for Hybrid Globalization.
*/
| "segmentation-rules";
type AssetBehaviors = SingleAssetBehaviors |
/**
* Load asset as a managed resource assembly.
Expand Down Expand Up @@ -381,15 +389,7 @@ type AssetBehaviors = SingleAssetBehaviors |
/**
* The javascript module that came from nuget package .
*/
| "js-module-library-initializer"
/**
* The javascript module for threads.
*/
| "symbols"
/**
* Load segmentation rules file for Hybrid Globalization.
*/
| "segmentation-rules";
| "js-module-library-initializer";
declare const enum GlobalizationMode {
/**
* Load sharded ICU data.
Expand Down
25 changes: 2 additions & 23 deletions src/mono/browser/runtime/driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -228,15 +228,15 @@ mono_wasm_load_runtime (const char *unused, int debug_level)
}

EMSCRIPTEN_KEEPALIVE int
mono_wasm_invoke_method_bound (MonoMethod *method, void* args /*JSMarshalerArguments*/, MonoString **out_exc)
mono_wasm_invoke_method (MonoMethod *method, void* args, MonoString **out_exc)
{
PVOLATILE(MonoObject) temp_exc = NULL;

void *invoke_args[1] = { args };
int is_err = 0;

MONO_ENTER_GC_UNSAFE;
mono_runtime_invoke (method, NULL, invoke_args, (MonoObject **)&temp_exc);
mono_runtime_invoke (method, NULL, args ? invoke_args : NULL, (MonoObject **)&temp_exc);

// this failure is unlikely because it would be runtime error, not application exception.
// the application exception is passed inside JSMarshalerArguments `args`
Expand All @@ -251,27 +251,6 @@ mono_wasm_invoke_method_bound (MonoMethod *method, void* args /*JSMarshalerArgum
return is_err;
}

EMSCRIPTEN_KEEPALIVE int
mono_wasm_invoke_method_raw (MonoMethod *method, MonoString **out_exc)
{
PVOLATILE(MonoObject) temp_exc = NULL;

int is_err = 0;

MONO_ENTER_GC_UNSAFE;
mono_runtime_invoke (method, NULL, NULL, (MonoObject **)&temp_exc);

if (temp_exc && out_exc) {
PVOLATILE(MonoObject) exc2 = NULL;
store_volatile((MonoObject**)out_exc, (MonoObject*)mono_object_to_string ((MonoObject*)temp_exc, (MonoObject **)&exc2));
if (exc2)
store_volatile((MonoObject**)out_exc, (MonoObject*)mono_string_new (root_domain, "Exception Double Fault"));
is_err = 1;
}
MONO_EXIT_GC_UNSAFE;
return is_err;
}

EMSCRIPTEN_KEEPALIVE MonoMethod*
mono_wasm_assembly_get_entry_point (MonoAssembly *assembly, int auto_insert_breakpoint)
{
Expand Down
5 changes: 3 additions & 2 deletions src/mono/browser/runtime/gc-handles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
import WasmEnableThreads from "consts:wasmEnableThreads";
import BuildConfiguration from "consts:configuration";

import { loaderHelpers, mono_assert, runtimeHelpers } from "./globals";
import { loaderHelpers, mono_assert } from "./globals";
import { assert_js_interop, js_import_wrapper_by_fn_handle } from "./invoke-js";
import { mono_log_info, mono_log_warn } from "./logging";
import { bound_cs_function_symbol, imported_js_function_symbol, proxy_debug_symbol } from "./marshal";
import { GCHandle, GCHandleNull, JSHandle, WeakRefInternal } from "./types/internal";
import { _use_weak_ref, create_weak_ref } from "./weak-ref";
import { exportsByAssembly } from "./invoke-cs";
import { release_js_owned_object_by_gc_handle } from "./managed-exports";

const _use_finalization_registry = typeof globalThis.FinalizationRegistry === "function";
let _js_owned_object_registry: FinalizationRegistry<any>;
Expand Down Expand Up @@ -152,7 +153,7 @@ export function teardown_managed_proxy(owner: any, gc_handle: GCHandle, skipMana
}
if (gc_handle !== GCHandleNull && _js_owned_object_table.delete(gc_handle) && !skipManaged) {
if (loaderHelpers.is_runtime_running()) {
runtimeHelpers.javaScriptExports.release_js_owned_object_by_gc_handle(gc_handle);
release_js_owned_object_by_gc_handle(gc_handle);
}
}
if (is_gcv_handle(gc_handle)) {
Expand Down
16 changes: 7 additions & 9 deletions src/mono/browser/runtime/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import gitHash from "consts:gitHash";

import { RuntimeAPI } from "./types/index";
import type { GlobalObjects, EmscriptenInternals, RuntimeHelpers, LoaderHelpers, DotnetModuleInternal, PromiseAndController, EmscriptenBuildOptions } from "./types/internal";
import type { GlobalObjects, EmscriptenInternals, RuntimeHelpers, LoaderHelpers, DotnetModuleInternal, PromiseAndController, EmscriptenBuildOptions, GCHandle } from "./types/internal";
import { mono_log_error } from "./logging";

// these are our public API (except internal)
Expand Down Expand Up @@ -55,7 +55,7 @@ export function setRuntimeGlobals(globalObjects: GlobalObjects) {
loaderHelpers = globalObjects.loaderHelpers;
exportedRuntimeAPI = globalObjects.api;

Object.assign(runtimeHelpers, {
const rh: Partial<RuntimeHelpers> = {
gitHash,
allAssetsInMemory: createPromiseController<void>(),
dotnetReady: createPromiseController<any>(),
Expand All @@ -64,15 +64,13 @@ export function setRuntimeGlobals(globalObjects: GlobalObjects) {
afterPreInit: createPromiseController<void>(),
afterPreRun: createPromiseController<void>(),
beforeOnRuntimeInitialized: createPromiseController<void>(),
afterMonoStarted: createPromiseController<GCHandle | undefined>(),
afterOnRuntimeInitialized: createPromiseController<void>(),
afterPostRun: createPromiseController<void>(),
mono_wasm_exit: () => {
throw new Error("Mono shutdown");
},
abort: (reason: any) => {
throw reason;
}
});
nativeAbort: (reason: any) => { throw reason || new Error("abort"); },
nativeExit: (code: number) => { throw new Error("exit:" + code); }
};
Object.assign(runtimeHelpers, rh);

Object.assign(globalObjects.module.config!, {}) as any;
Object.assign(globalObjects.api, {
Expand Down
Loading