From 1337d7a18e8c4ab9c244244a359cd2b0b1f60416 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 3 Jan 2024 17:09:00 +0100 Subject: [PATCH] [browser][MT] JSImport dispatch to target thread via JSSynchronizationContext (#96319) --- .../src/Interop/Browser/Interop.Runtime.cs | 28 +- .../JSImportGenerator/JSImportGenerator.cs | 4 +- .../JavaScript/CancelablePromise.cs | 1 + .../JavaScript/JSFunctionBinding.cs | 166 ++++++- .../InteropServices/JavaScript/JSHost.cs | 9 - .../JavaScript/JSHostImplementation.cs | 8 +- .../JavaScript/JSObject.References.cs | 45 +- .../InteropServices/JavaScript/JSObject.cs | 75 +-- .../JavaScript/JSProxyContext.cs | 9 +- .../Legacy/LegacyHostImplementation.cs | 2 +- .../JavaScript/Legacy/Runtime.cs | 11 +- .../JSMarshalerArgument.Exception.cs | 13 +- .../Marshaling/JSMarshalerArgument.Func.cs | 24 - .../JSMarshalerArgument.JSObject.cs | 8 +- .../Marshaling/JSMarshalerArgument.Object.cs | 2 +- .../Marshaling/JSMarshalerArgument.String.cs | 2 +- .../Marshaling/JSMarshalerArgument.Task.cs | 16 +- ...ropServices.JavaScript.Legacy.Tests.csproj | 2 +- .../JavaScript/DelegateTests.cs | 2 +- ...me.InteropServices.JavaScript.Tests.csproj | 12 +- .../JavaScript/JSImportExportTest.cs | 15 + .../JavaScript/JavaScriptTestHelper.cs | 15 +- .../JavaScript/WebWorkerTest.cs | 189 ++++++++ .../JavaScript/WebWorkerTestHelper.cs | 303 ++++++++++++ .../JavaScript/WebWorkerTestHelper.mjs | 11 + src/mono/browser/runtime/corebindings.c | 26 +- src/mono/browser/runtime/exports-binding.ts | 4 +- src/mono/browser/runtime/gc-handles.ts | 6 +- src/mono/browser/runtime/http.ts | 4 +- src/mono/browser/runtime/invoke-cs.ts | 15 +- src/mono/browser/runtime/invoke-js.ts | 59 ++- src/mono/browser/runtime/loader/globals.ts | 3 +- src/mono/browser/runtime/loader/logging.ts | 27 +- src/mono/browser/runtime/loader/run.ts | 4 +- src/mono/browser/runtime/loader/worker.ts | 2 +- src/mono/browser/runtime/logging.ts | 4 +- src/mono/browser/runtime/managed-exports.ts | 1 + src/mono/browser/runtime/marshal-to-cs.ts | 25 +- src/mono/browser/runtime/marshal-to-js.ts | 10 +- .../runtime/net6-legacy/method-binding.ts | 4 +- .../browser/runtime/pthreads/browser/index.ts | 1 - .../pthreads/shared/emscripten-internals.ts | 8 +- .../shared/emscripten-replacements.ts | 2 +- .../browser/runtime/pthreads/shared/index.ts | 26 +- .../browser/runtime/pthreads/worker/index.ts | 26 +- src/mono/browser/runtime/run.ts | 4 +- src/mono/browser/runtime/startup.ts | 13 +- src/mono/browser/runtime/types/internal.ts | 1 + src/mono/browser/runtime/web-socket.ts | 4 +- .../browser-threads-minimal/JSWebWorker.cs | 59 --- .../wasm/browser-threads-minimal/Makefile | 13 - .../wasm/browser-threads-minimal/Program.cs | 445 ------------------ ...Wasm.Browser.Threads.Minimal.Sample.csproj | 22 - .../wasm/browser-threads-minimal/blurst.txt | 1 - .../browser-threads-minimal/fetchhelper.js | 11 - .../wasm/browser-threads-minimal/index.html | 18 - .../wasm/browser-threads-minimal/main.js | 134 ------ 57 files changed, 956 insertions(+), 998 deletions(-) create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.mjs delete mode 100644 src/mono/sample/wasm/browser-threads-minimal/JSWebWorker.cs delete mode 100644 src/mono/sample/wasm/browser-threads-minimal/Makefile delete mode 100644 src/mono/sample/wasm/browser-threads-minimal/Program.cs delete mode 100644 src/mono/sample/wasm/browser-threads-minimal/Wasm.Browser.Threads.Minimal.Sample.csproj delete mode 100644 src/mono/sample/wasm/browser-threads-minimal/blurst.txt delete mode 100644 src/mono/sample/wasm/browser-threads-minimal/fetchhelper.js delete mode 100644 src/mono/sample/wasm/browser-threads-minimal/index.html delete mode 100644 src/mono/sample/wasm/browser-threads-minimal/main.js diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs index fc627a32e1fa0..61786b5b33748 100644 --- a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs +++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs @@ -12,31 +12,37 @@ internal static partial class Interop internal static unsafe partial class Runtime { [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern void ReleaseCSOwnedObject(IntPtr jsHandle); + internal static extern void ReleaseCSOwnedObject(nint jsHandle); [MethodImpl(MethodImplOptions.InternalCall)] - public static extern unsafe void BindJSImport(void* signature, out int is_exception, out object result); - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern void InvokeJSFunction(int functionHandle, void* data); - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern void InvokeJSImport(int importHandle, void* data); - + public static extern void InvokeJSFunction(nint functionHandle, nint data); [MethodImpl(MethodImplOptions.InternalCall)] public static extern unsafe void BindCSFunction(in string fully_qualified_name, int signature_hash, void* signature, out int is_exception, out object result); [MethodImpl(MethodImplOptions.InternalCall)] - public static extern void ResolveOrRejectPromise(void* data); + public static extern void ResolveOrRejectPromise(nint data); #if !ENABLE_JS_INTEROP_BY_VALUE [MethodImpl(MethodImplOptions.InternalCall)] - public static extern IntPtr RegisterGCRoot(IntPtr start, int bytesSize, IntPtr name); + public static extern nint RegisterGCRoot(void* start, int bytesSize, IntPtr name); [MethodImpl(MethodImplOptions.InternalCall)] - public static extern void DeregisterGCRoot(IntPtr handle); + public static extern void DeregisterGCRoot(nint handle); #endif #if FEATURE_WASM_THREADS [MethodImpl(MethodImplOptions.InternalCall)] - public static extern void InstallWebWorkerInterop(IntPtr proxyContextGCHandle); + public static extern void InstallWebWorkerInterop(nint proxyContextGCHandle); [MethodImpl(MethodImplOptions.InternalCall)] public static extern void UninstallWebWorkerInterop(); + + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void InvokeJSImportSync(nint data, nint signature); + + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void InvokeJSImportAsync(nint data, nint signature); +#else + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern unsafe void BindJSImport(void* signature, out int is_exception, out object result); + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void InvokeJSImport(int importHandle, nint data); #endif #region Legacy diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportGenerator.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportGenerator.cs index 39b0e5c8d9dd4..bf2c4bd3da32c 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportGenerator.cs @@ -120,9 +120,7 @@ private static MemberDeclarationSyntax PrintGeneratedSource( FieldDeclarationSyntax sigField = FieldDeclaration(VariableDeclaration(IdentifierName(Constants.JSFunctionSignatureGlobal)) .WithVariables(SingletonSeparatedList(VariableDeclarator(Identifier(stub.BindingName))))) - .AddModifiers(Token(SyntaxKind.StaticKeyword)) - .WithAttributeLists(SingletonList(AttributeList(SingletonSeparatedList( - Attribute(IdentifierName(Constants.ThreadStaticGlobal)))))); + .AddModifiers(Token(SyntaxKind.StaticKeyword)); MemberDeclarationSyntax toPrint = containingSyntaxContext.WrapMembersInContainingSyntaxWithUnsafeModifier(stubMethod, sigField); return toPrint; diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CancelablePromise.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CancelablePromise.cs index 240199d470238..795f3311cba97 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CancelablePromise.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CancelablePromise.cs @@ -29,6 +29,7 @@ public static void CancelPromise(Task promise) } _CancelPromise(holder.GCHandle); #else + // this need to e manually dispatched via holder.ProxyContext, because we don't pass JSObject with affinity holder.ProxyContext.SynchronizationContext.Post(static (object? h) => { var holder = (JSHostImplementation.PromiseHolder)h!; diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs index 25c0b74d4a80a..d90a43cddf721 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs @@ -30,6 +30,9 @@ internal JSFunctionBinding() { } internal static volatile uint nextImportHandle = 1; internal int ImportHandle; internal bool IsAsync; +#if DEBUG + internal string? FunctionName; +#endif [StructLayout(LayoutKind.Sequential, Pack = 4)] internal struct JSBindingHeader @@ -197,15 +200,31 @@ public static JSFunctionBinding BindManagedFunction(string fullyQualifiedName, i [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static unsafe void InvokeJSFunction(JSObject jsFunction, Span arguments) { - ObjectDisposedException.ThrowIf(jsFunction.IsDisposed, jsFunction); + jsFunction.AssertNotDisposed(); + #if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(jsFunction); + // if we are on correct thread already, just call it + if (jsFunction.ProxyContext.IsCurrentThread()) + { + InvokeJSFunctionCurrent(jsFunction, arguments); + } + else + { + DispatchJSFunctionSync(jsFunction, arguments); + } + // async functions are not implemented +#else + InvokeJSFunctionCurrent(jsFunction, arguments); #endif + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe void InvokeJSFunctionCurrent(JSObject jsFunction, Span arguments) + { var functionHandle = (int)jsFunction.JSHandle; fixed (JSMarshalerArgument* ptr = arguments) { - Interop.Runtime.InvokeJSFunction(functionHandle, ptr); + Interop.Runtime.InvokeJSFunction(functionHandle, (nint)ptr); ref JSMarshalerArgument exceptionArg = ref arguments[0]; if (exceptionArg.slot.Type != MarshalerType.None) { @@ -214,12 +233,33 @@ internal static unsafe void InvokeJSFunction(JSObject jsFunction, Span arguments) + { + var args = (nint)Unsafe.AsPointer(ref arguments[0]); + var functionHandle = jsFunction.JSHandle; + + jsFunction.ProxyContext.SynchronizationContext.Send(static o => + { + var state = ((nint functionHandle, nint args))o!; + Interop.Runtime.InvokeJSFunction(state.functionHandle, state.args); + }, (functionHandle, args)); + + ref JSMarshalerArgument exceptionArg = ref arguments[0]; + if (exceptionArg.slot.Type != MarshalerType.None) + { + JSHostImplementation.ThrowException(ref exceptionArg); + } + } +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span arguments) { #if FEATURE_WASM_THREADS var targetContext = JSProxyContext.SealJSImportCapturing(); - JSProxyContext.AssertIsInteropThread(); arguments[0].slot.ContextHandle = targetContext.ContextHandle; arguments[1].slot.ContextHandle = targetContext.ContextHandle; #else @@ -229,20 +269,36 @@ internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span if (signature.IsAsync) { // pre-allocate the result handle and Task - var holder = new JSHostImplementation.PromiseHolder(targetContext); + var holder = targetContext.CreatePromiseHolder(); arguments[1].slot.Type = MarshalerType.TaskPreCreated; arguments[1].slot.GCHandle = holder.GCHandle; } - fixed (JSMarshalerArgument* ptr = arguments) +#if FEATURE_WASM_THREADS + // if we are on correct thread already or this is synchronous call, just call it + if (targetContext.IsCurrentThread()) { - Interop.Runtime.InvokeJSImport(signature.ImportHandle, ptr); - ref JSMarshalerArgument exceptionArg = ref arguments[0]; - if (exceptionArg.slot.Type != MarshalerType.None) + InvokeJSImportCurrent(signature, arguments); + +#if DEBUG + if (signature.IsAsync && arguments[1].slot.Type == MarshalerType.None) { - JSHostImplementation.ThrowException(ref exceptionArg); + throw new InvalidOperationException("null Task/Promise return is not supported"); } +#endif + } + else if (!signature.IsAsync) + { + DispatchJSImportSync(signature, targetContext, arguments); + } + else + { + DispatchJSImportAsync(signature, targetContext, arguments); + } +#else + InvokeJSImportCurrent(signature, arguments); + if (signature.IsAsync) { // if js synchronously returned null @@ -252,22 +308,83 @@ internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span holderHandle.Free(); } } +#endif } - internal static unsafe JSFunctionBinding BindJSImportImpl(string functionName, string moduleName, ReadOnlySpan signatures) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe void InvokeJSImportCurrent(JSFunctionBinding signature, Span arguments) { + fixed (JSMarshalerArgument* args = arguments) + { #if FEATURE_WASM_THREADS - JSProxyContext.AssertIsInteropThread(); + Interop.Runtime.InvokeJSImportSync((nint)args, (nint)signature.Header); +#else + Interop.Runtime.InvokeJSImport(signature.ImportHandle, (nint)args); #endif + } + + ref JSMarshalerArgument exceptionArg = ref arguments[0]; + if (exceptionArg.slot.Type != MarshalerType.None) + { + JSHostImplementation.ThrowException(ref exceptionArg); + } + } + +#if FEATURE_WASM_THREADS + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe void DispatchJSImportSync(JSFunctionBinding signature, JSProxyContext targetContext, Span arguments) + { + var args = (nint)Unsafe.AsPointer(ref arguments[0]); + var sig = (nint)signature.Header; + + targetContext.SynchronizationContext.Send(static o => + { + var state = ((nint args, nint sig))o!; + Interop.Runtime.InvokeJSImportSync(state.args, state.sig); + }, (args, sig)); + + ref JSMarshalerArgument exceptionArg = ref arguments[0]; + if (exceptionArg.slot.Type != MarshalerType.None) + { + JSHostImplementation.ThrowException(ref exceptionArg); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe void DispatchJSImportAsync(JSFunctionBinding signature, JSProxyContext targetContext, Span arguments) + { + // this copy is freed in mono_wasm_invoke_import_async + var bytes = sizeof(JSMarshalerArgument) * arguments.Length; + void* cpy = (void*)Marshal.AllocHGlobal(bytes); + void* src = Unsafe.AsPointer(ref arguments[0]); + Unsafe.CopyBlock(cpy, src, (uint)bytes); + var sig = (nint)signature.Header; + + targetContext.SynchronizationContext.Post(static o => + { + var state = ((nint args, nint sig))o!; + Interop.Runtime.InvokeJSImportAsync(state.args, state.sig); + }, ((nint)cpy, sig)); + + } + +#endif + + internal static unsafe JSFunctionBinding BindJSImportImpl(string functionName, string moduleName, ReadOnlySpan signatures) + { var signature = JSHostImplementation.GetMethodSignature(signatures, functionName, moduleName); +#if !FEATURE_WASM_THREADS + Interop.Runtime.BindJSImport(signature.Header, out int isException, out object exceptionMessage); if (isException != 0) throw new JSException((string)exceptionMessage); JSHostImplementation.FreeMethodSignatureBuffer(signature); +#endif + return signature; } @@ -286,12 +403,13 @@ internal static unsafe JSFunctionBinding BindManagedFunctionImpl(string fullyQua return signature; } +#if !FEATURE_WASM_THREADS [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static unsafe void ResolveOrRejectPromise(Span arguments) { fixed (JSMarshalerArgument* ptr = arguments) { - Interop.Runtime.ResolveOrRejectPromise(ptr); + Interop.Runtime.ResolveOrRejectPromise((nint)ptr); ref JSMarshalerArgument exceptionArg = ref arguments[0]; if (exceptionArg.slot.Type != MarshalerType.None) { @@ -299,5 +417,27 @@ internal static unsafe void ResolveOrRejectPromise(Span arg } } } +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe void ResolveOrRejectPromise(JSProxyContext targetContext, Span arguments) + { + // this copy is freed in mono_wasm_invoke_import_async + var bytes = sizeof(JSMarshalerArgument) * arguments.Length; + void* cpy = (void*)Marshal.AllocHGlobal(bytes); + void* src = Unsafe.AsPointer(ref arguments[0]); + Unsafe.CopyBlock(cpy, src, (uint)bytes); + + // TODO: we could optimize away the work item allocation in JSSynchronizationContext if we synchronously dispatch this when we are already in the right thread. + + // async + targetContext.SynchronizationContext.Post(static o => + { + var args = (nint)o!; + Interop.Runtime.ResolveOrRejectPromise(args); + }, (nint)cpy); + + // this never throws directly + } +#endif } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHost.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHost.cs index c1ec22aecdd7b..b5238826e5bb7 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHost.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHost.cs @@ -21,9 +21,6 @@ public static JSObject GlobalThis { get { -#if FEATURE_WASM_THREADS - JSProxyContext.AssertIsInteropThread(); -#endif return JavaScriptImports.GetGlobalThis(); } } @@ -35,9 +32,6 @@ public static JSObject DotnetInstance { get { -#if FEATURE_WASM_THREADS - JSProxyContext.AssertIsInteropThread(); -#endif return JavaScriptImports.GetDotnetInstance(); } } @@ -53,9 +47,6 @@ public static JSObject DotnetInstance [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Task ImportAsync(string moduleName, string moduleUrl, CancellationToken cancellationToken = default) { -#if FEATURE_WASM_THREADS - JSProxyContext.AssertIsInteropThread(); -#endif return JSHostImplementation.ImportAsync(moduleName, moduleUrl, cancellationToken); } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs index 877940b6bdacb..e4d8042c05032 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs @@ -87,13 +87,14 @@ public static void ThrowException(ref JSMarshalerArgument arg) public static async Task ImportAsync(string moduleName, string moduleUrl, CancellationToken cancellationToken) { Task modulePromise = JavaScriptImports.DynamicImport(moduleName, moduleUrl); - var wrappedTask = CancelationHelper(modulePromise, cancellationToken); + var wrappedTask = CancellationHelper(modulePromise, cancellationToken); return await wrappedTask.ConfigureAwait( ConfigureAwaitOptions.ContinueOnCapturedContext | ConfigureAwaitOptions.ForceYielding); // this helps to finish the import before we bind the module in [JSImport] } - public static async Task CancelationHelper(Task jsTask, CancellationToken cancellationToken) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static async Task CancellationHelper(Task jsTask, CancellationToken cancellationToken) { if (jsTask.IsCompletedSuccessfully) { @@ -152,6 +153,9 @@ public static unsafe JSFunctionBinding GetMethodSignature(ReadOnlySpan public override bool Equals([NotNullWhen(true)] object? obj) => obj is JSObject other && JSHandle == other.JSHandle; @@ -105,17 +81,32 @@ internal static void AssertThreadAffinity(object value) /// public override string ToString() => $"(js-obj js '{JSHandle}')"; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AssertNotDisposed() + { + lock (ProxyContext) + { + ObjectDisposedException.ThrowIf(IsDisposed, this); + } + } + internal void DisposeImpl(bool skipJsCleanup = false) { if (!_isDisposed) { #if FEATURE_WASM_THREADS + if (ProxyContext.SynchronizationContext._isDisposed) + { + return; + } + if (ProxyContext.IsCurrentThread()) { JSProxyContext.ReleaseCSOwnedObject(this, skipJsCleanup); return; } + // async ProxyContext.SynchronizationContext.Post(static (object? s) => { var x = ((JSObject self, bool skipJS))s!; @@ -123,8 +114,6 @@ internal void DisposeImpl(bool skipJsCleanup = false) }, (this, skipJsCleanup)); #else JSProxyContext.ReleaseCSOwnedObject(this, skipJsCleanup); - _isDisposed = true; - JSHandle = IntPtr.Zero; #endif } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs index 57dc9abb45a38..d6c7b8ab21083 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs @@ -16,7 +16,13 @@ public partial class JSObject : IDisposable /// /// Returns true if the proxy was already disposed. /// - public bool IsDisposed { get => _isDisposed; } + public bool IsDisposed + { + get + { + return _isDisposed; + } + } /// /// Checks whether the target object or one of its prototypes has a property with the specified name. @@ -26,10 +32,7 @@ public partial class JSObject : IDisposable [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool HasProperty(string propertyName) { - ObjectDisposedException.ThrowIf(IsDisposed, this); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(this); -#endif + AssertNotDisposed(); return JavaScriptImports.HasProperty(this, propertyName); } @@ -41,10 +44,7 @@ public bool HasProperty(string propertyName) [MethodImpl(MethodImplOptions.AggressiveInlining)] public string GetTypeOfProperty(string propertyName) { - ObjectDisposedException.ThrowIf(IsDisposed, this); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(this); -#endif + AssertNotDisposed(); return JavaScriptImports.GetTypeOfProperty(this, propertyName); } @@ -59,10 +59,7 @@ public string GetTypeOfProperty(string propertyName) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool GetPropertyAsBoolean(string propertyName) { - ObjectDisposedException.ThrowIf(IsDisposed, this); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(this); -#endif + AssertNotDisposed(); return JavaScriptImports.GetPropertyAsBoolean(this, propertyName); } @@ -77,10 +74,7 @@ public bool GetPropertyAsBoolean(string propertyName) [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetPropertyAsInt32(string propertyName) { - ObjectDisposedException.ThrowIf(IsDisposed, this); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(this); -#endif + AssertNotDisposed(); return JavaScriptImports.GetPropertyAsInt32(this, propertyName); } @@ -95,10 +89,7 @@ public int GetPropertyAsInt32(string propertyName) [MethodImpl(MethodImplOptions.AggressiveInlining)] public double GetPropertyAsDouble(string propertyName) { - ObjectDisposedException.ThrowIf(IsDisposed, this); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(this); -#endif + AssertNotDisposed(); return JavaScriptImports.GetPropertyAsDouble(this, propertyName); } @@ -113,10 +104,7 @@ public double GetPropertyAsDouble(string propertyName) [MethodImpl(MethodImplOptions.AggressiveInlining)] public string? GetPropertyAsString(string propertyName) { - ObjectDisposedException.ThrowIf(IsDisposed, this); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(this); -#endif + AssertNotDisposed(); return JavaScriptImports.GetPropertyAsString(this, propertyName); } @@ -131,10 +119,7 @@ public double GetPropertyAsDouble(string propertyName) [MethodImpl(MethodImplOptions.AggressiveInlining)] public JSObject? GetPropertyAsJSObject(string propertyName) { - ObjectDisposedException.ThrowIf(IsDisposed, this); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(this); -#endif + AssertNotDisposed(); return JavaScriptImports.GetPropertyAsJSObject(this, propertyName); } @@ -150,10 +135,7 @@ public double GetPropertyAsDouble(string propertyName) [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte[]? GetPropertyAsByteArray(string propertyName) { - ObjectDisposedException.ThrowIf(IsDisposed, this); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(this); -#endif + AssertNotDisposed(); return JavaScriptImports.GetPropertyAsByteArray(this, propertyName); } @@ -165,10 +147,7 @@ public double GetPropertyAsDouble(string propertyName) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetProperty(string propertyName, bool value) { - ObjectDisposedException.ThrowIf(IsDisposed, this); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(this); -#endif + AssertNotDisposed(); JavaScriptImports.SetPropertyBool(this, propertyName, value); } @@ -180,10 +159,7 @@ public void SetProperty(string propertyName, bool value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetProperty(string propertyName, int value) { - ObjectDisposedException.ThrowIf(IsDisposed, this); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(this); -#endif + AssertNotDisposed(); JavaScriptImports.SetPropertyInt(this, propertyName, value); } @@ -195,10 +171,7 @@ public void SetProperty(string propertyName, int value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetProperty(string propertyName, double value) { - ObjectDisposedException.ThrowIf(IsDisposed, this); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(this); -#endif + AssertNotDisposed(); JavaScriptImports.SetPropertyDouble(this, propertyName, value); } @@ -210,7 +183,7 @@ public void SetProperty(string propertyName, double value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetProperty(string propertyName, string? value) { - ObjectDisposedException.ThrowIf(IsDisposed, this); + AssertNotDisposed(); JavaScriptImports.SetPropertyString(this, propertyName, value); } @@ -222,10 +195,7 @@ public void SetProperty(string propertyName, string? value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetProperty(string propertyName, JSObject? value) { - ObjectDisposedException.ThrowIf(IsDisposed, this); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(this); -#endif + AssertNotDisposed(); JavaScriptImports.SetPropertyJSObject(this, propertyName, value); } @@ -238,10 +208,7 @@ public void SetProperty(string propertyName, JSObject? value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetProperty(string propertyName, byte[]? value) { - ObjectDisposedException.ThrowIf(IsDisposed, this); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(this); -#endif + AssertNotDisposed(); JavaScriptImports.SetPropertyBytes(this, propertyName, value); } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs index be59f8cf717c0..73937af88bf3d 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs @@ -38,7 +38,7 @@ private JSProxyContext() [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsCurrentThread() { - return ManagedTID == Thread.CurrentThread.ManagedThreadId; + return ManagedTID == Environment.CurrentManagedThreadId; } [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "thread_id")] @@ -54,7 +54,7 @@ public JSProxyContext(bool isMainThread, JSSynchronizationContext synchronizatio { SynchronizationContext = synchronizationContext; NativeTID = GetNativeThreadId(); - ManagedTID = Thread.CurrentThread.ManagedThreadId; + ManagedTID = Environment.CurrentManagedThreadId; IsMainThread = isMainThread; ContextHandle = (nint)GCHandle.Alloc(this, GCHandleType.Normal); } @@ -225,7 +225,7 @@ public static JSProxyContext AssertIsInteropThread() var ctx = CurrentThreadContext; if (ctx == null) { - throw new InvalidOperationException($"Please use dedicated worker for working with JavaScript interop, ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}. See https://aka.ms/dotnet-JS-interop-threads"); + throw new InvalidOperationException($"Please use dedicated worker for working with JavaScript interop, ManagedThreadId:{Environment.CurrentManagedThreadId}. See https://aka.ms/dotnet-JS-interop-threads"); } if (ctx._isDisposed) { @@ -439,9 +439,10 @@ public static void ReleaseCSOwnedObject(JSObject proxy, bool skipJS) { return; } + var jsHandle = proxy.JSHandle; proxy._isDisposed = true; + proxy.JSHandle = IntPtr.Zero; GC.SuppressFinalize(proxy); - var jsHandle = proxy.JSHandle; if (!ctx.ThreadCsOwnedObjects.Remove(jsHandle)) { Environment.FailFast($"ReleaseCSOwnedObject expected to find registration for JSHandle: {jsHandle}, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}"); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/LegacyHostImplementation.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/LegacyHostImplementation.cs index 8e6242017979f..c0503f449fd41 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/LegacyHostImplementation.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/LegacyHostImplementation.cs @@ -216,7 +216,7 @@ public enum MappedType #if FEATURE_WASM_THREADS public static void ThrowIfLegacyWorkerThread() { - if (Thread.CurrentThread.ManagedThreadId != 1) + if (Environment.CurrentManagedThreadId != 1) { throw new PlatformNotSupportedException("Legacy interop is not supported with WebAssembly threads."); } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/Runtime.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/Runtime.cs index c6e0e362af65e..81535a162d51c 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/Runtime.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/Runtime.cs @@ -38,7 +38,7 @@ public static object Invoke(this JSObject self, string method, params object?[] LegacyHostImplementation.ThrowIfLegacyWorkerThread(); #endif ArgumentNullException.ThrowIfNull(self); - ObjectDisposedException.ThrowIf(self.IsDisposed, self); + self.AssertNotDisposed(); Interop.Runtime.InvokeJSWithArgsRef(self.JSHandle, method, args, out int exception, out object res); if (exception != 0) throw new JSException((string)res); @@ -75,7 +75,7 @@ public static object GetObjectProperty(this JSObject self, string name) LegacyHostImplementation.ThrowIfLegacyWorkerThread(); #endif ArgumentNullException.ThrowIfNull(self); - ObjectDisposedException.ThrowIf(self.IsDisposed, self); + self.AssertNotDisposed(); Interop.Runtime.GetObjectPropertyRef(self.JSHandle, name, out int exception, out object propertyValue); if (exception != 0) @@ -102,18 +102,13 @@ public static void SetObjectProperty(this JSObject self, string name, object? va LegacyHostImplementation.ThrowIfLegacyWorkerThread(); #endif ArgumentNullException.ThrowIfNull(self); - ObjectDisposedException.ThrowIf(self.IsDisposed, self); + self.AssertNotDisposed(); Interop.Runtime.SetObjectPropertyRef(self.JSHandle, name, in value, createIfNotExists, hasOwnProperty, out int exception, out object res); if (exception != 0) throw new JSException(SR.Format(SR.ErrorLegacySettingProperty, name, self.JSHandle, res)); } - public static void AssertNotDisposed(this JSObject self) - { - ObjectDisposedException.ThrowIf(self.IsDisposed, self); - } - public static void AssertInFlight(this JSObject self, int expectedInFlightCount) { if (self.InFlightCounter != expectedInFlightCount) throw new InvalidOperationException(SR.Format(SR.UnsupportedLegacyMarshlerType, self.JSHandle, expectedInFlightCount, self.InFlightCounter)); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Exception.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Exception.cs index f89c4a669818c..a64b045fd55e3 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Exception.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Exception.cs @@ -66,10 +66,13 @@ public unsafe void ToJS(Exception? value) var jse = cpy as JSException; if (jse != null && jse.jsException != null) { - ObjectDisposedException.ThrowIf(jse.jsException.IsDisposed, value); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(value); - var ctx = jse.jsException.ProxyContext; + var jsException = jse.jsException; +#if !FEATURE_WASM_THREADS + jsException.AssertNotDisposed(); +#else + var ctx = jsException.ProxyContext; + jsException.AssertNotDisposed(); + if (JSProxyContext.CapturingState == JSProxyContext.JSImportOperationState.JSImportParams) { JSProxyContext.CaptureContextFromParameter(ctx); @@ -82,7 +85,7 @@ public unsafe void ToJS(Exception? value) #endif // this is JSException roundtrip slot.Type = MarshalerType.JSException; - slot.JSHandle = jse.jsException.JSHandle; + slot.JSHandle = jsException.JSHandle; } else { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Func.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Func.cs index ff3183c78ed39..3f713e1512885 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Func.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Func.cs @@ -19,9 +19,6 @@ public void InvokeJS() // JSObject (held by this lambda) would be collected by GC after the lambda is collected // and would also allow the JS function to be collected -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(JSObject); -#endif Span arguments = stackalloc JSMarshalerArgument[4]; ref JSMarshalerArgument args_exception = ref arguments[0]; @@ -53,9 +50,6 @@ public ActionJS(JSObject holder, ArgumentToJSCallback arg1Marshaler) public void InvokeJS(T arg1) { -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(JSObject); -#endif Span arguments = stackalloc JSMarshalerArgument[4]; ref JSMarshalerArgument args_exception = ref arguments[0]; @@ -93,9 +87,6 @@ public ActionJS(JSObject holder, ArgumentToJSCallback arg1Marshaler, Argumen public void InvokeJS(T1 arg1, T2 arg2) { -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(JSObject); -#endif Span arguments = stackalloc JSMarshalerArgument[4]; ref JSMarshalerArgument args_exception = ref arguments[0]; @@ -138,9 +129,6 @@ public ActionJS(JSObject holder, ArgumentToJSCallback arg1Marshaler, Argumen public void InvokeJS(T1 arg1, T2 arg2, T3 arg3) { -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(JSObject); -#endif Span arguments = stackalloc JSMarshalerArgument[5]; ref JSMarshalerArgument args_exception = ref arguments[0]; @@ -266,9 +254,6 @@ public FuncJS(JSObject holder, ArgumentToManagedCallback resMarshaler) public TResult InvokeJS() { -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(JSObject); -#endif // JSObject (held by this lambda) would be collected by GC after the lambda is collected // and would also allow the JS function to be collected @@ -309,9 +294,6 @@ public FuncJS(JSObject holder, ArgumentToJSCallback arg1Marshaler, ArgumentTo public TResult InvokeJS(T arg1) { -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(JSObject); -#endif Span arguments = stackalloc JSMarshalerArgument[4]; ref JSMarshalerArgument args_exception = ref arguments[0]; @@ -353,9 +335,6 @@ public FuncJS(JSObject holder, ArgumentToJSCallback arg1Marshaler, ArgumentT public TResult InvokeJS(T1 arg1, T2 arg2) { -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(JSObject); -#endif Span arguments = stackalloc JSMarshalerArgument[4]; ref JSMarshalerArgument args_exception = ref arguments[0]; @@ -402,9 +381,6 @@ public FuncJS(JSObject holder, ArgumentToJSCallback arg1Marshaler, ArgumentT public TResult InvokeJS(T1 arg1, T2 arg2, T3 arg3) { -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(JSObject); -#endif Span arguments = stackalloc JSMarshalerArgument[5]; ref JSMarshalerArgument args_exception = ref arguments[0]; diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.JSObject.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.JSObject.cs index 3c41ec2fccc56..bc8fb323775fe 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.JSObject.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.JSObject.cs @@ -40,10 +40,12 @@ public void ToJS(JSObject? value) } else { - ObjectDisposedException.ThrowIf(value.IsDisposed, value); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(value); +#if !FEATURE_WASM_THREADS + value.AssertNotDisposed(); +#else + value.AssertNotDisposed(); var ctx = value.ProxyContext; + if (JSProxyContext.CapturingState == JSProxyContext.JSImportOperationState.JSImportParams) { JSProxyContext.CaptureContextFromParameter(ctx); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Object.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Object.cs index 75fa4d6aa2f0e..3ce627aeff21c 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Object.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Object.cs @@ -370,7 +370,7 @@ public unsafe void ToJS(object?[] value) JSMarshalerArgument* payload = (JSMarshalerArgument*)Marshal.AllocHGlobal(bytes); Unsafe.InitBlock(payload, 0, (uint)bytes); #if !ENABLE_JS_INTEROP_BY_VALUE - Interop.Runtime.RegisterGCRoot((IntPtr)payload, bytes, IntPtr.Zero); + Interop.Runtime.RegisterGCRoot(payload, bytes, IntPtr.Zero); #endif for (int i = 0; i < slot.Length; i++) { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.String.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.String.cs index c101f2048e245..efe764cd837fb 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.String.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.String.cs @@ -112,7 +112,7 @@ public unsafe void ToJS(string?[] value) JSMarshalerArgument* payload = (JSMarshalerArgument*)Marshal.AllocHGlobal(bytes); Unsafe.InitBlock(payload, 0, (uint)bytes); #if !ENABLE_JS_INTEROP_BY_VALUE - Interop.Runtime.RegisterGCRoot((IntPtr)payload, bytes, IntPtr.Zero); + Interop.Runtime.RegisterGCRoot(payload, bytes, IntPtr.Zero); #endif for (int i = 0; i < slot.Length; i++) { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Task.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Task.cs index 51bfa81989133..098dfb18ec267 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Task.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Task.cs @@ -363,10 +363,6 @@ private static void RejectPromise(JSObject holder, Exception ex) { holder.AssertNotDisposed(); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(holder); -#endif - Span args = stackalloc JSMarshalerArgument[4]; ref JSMarshalerArgument exc = ref args[0]; ref JSMarshalerArgument res = ref args[1]; @@ -399,16 +395,13 @@ private static void RejectPromise(JSObject holder, Exception ex) JSFunctionBinding.ResolveOrRejectPromise(args); #else // order of operations with DisposeImpl matters - JSFunctionBinding.ResolveOrRejectPromise(args); + JSFunctionBinding.ResolveOrRejectPromise(holder.ProxyContext, args); #endif } private static void ResolveVoidPromise(JSObject holder) { holder.AssertNotDisposed(); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(holder); -#endif Span args = stackalloc JSMarshalerArgument[4]; ref JSMarshalerArgument exc = ref args[0]; @@ -441,16 +434,13 @@ private static void ResolveVoidPromise(JSObject holder) JSFunctionBinding.ResolveOrRejectPromise(args); #else // order of operations with DisposeImpl matters - JSFunctionBinding.ResolveOrRejectPromise(args); + JSFunctionBinding.ResolveOrRejectPromise(holder.ProxyContext, args); #endif } private static void ResolvePromise(JSObject holder, T value, ArgumentToJSCallback marshaler) { holder.AssertNotDisposed(); -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(holder); -#endif Span args = stackalloc JSMarshalerArgument[4]; ref JSMarshalerArgument exc = ref args[0]; @@ -484,7 +474,7 @@ private static void ResolvePromise(JSObject holder, T value, ArgumentToJSCall JSFunctionBinding.ResolveOrRejectPromise(args); #else // order of operations with DisposeImpl matters - JSFunctionBinding.ResolveOrRejectPromise(args); + JSFunctionBinding.ResolveOrRejectPromise(holder.ProxyContext, args); #endif } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Legacy.UnitTests/System.Runtime.InteropServices.JavaScript.Legacy.Tests.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Legacy.UnitTests/System.Runtime.InteropServices.JavaScript.Legacy.Tests.csproj index 80bdbabd3b1d8..a46f95dde5a92 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Legacy.UnitTests/System.Runtime.InteropServices.JavaScript.Legacy.Tests.csproj +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Legacy.UnitTests/System.Runtime.InteropServices.JavaScript.Legacy.Tests.csproj @@ -8,7 +8,7 @@ true $(DefineConstants);DISABLE_LEGACY_JS_INTEROP WasmTestOnBrowser - + <_XUnitBackgroundExec>false diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Legacy.UnitTests/System/Runtime/InteropServices/JavaScript/DelegateTests.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Legacy.UnitTests/System/Runtime/InteropServices/JavaScript/DelegateTests.cs index 5fa7648974d57..f5a49b0ef4c4b 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Legacy.UnitTests/System/Runtime/InteropServices/JavaScript/DelegateTests.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Legacy.UnitTests/System/Runtime/InteropServices/JavaScript/DelegateTests.cs @@ -240,7 +240,7 @@ public static void EventsAreNotCollected() var temp = new bool[attempts]; Action cb = (JSObject envt) => { - envt.AssertNotDisposed(); + ObjectDisposedException.ThrowIf(envt.IsDisposed, envt); envt.AssertInFlight(0); var data = (int)envt.GetObjectProperty("data"); temp[data] = true; diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj index 8d6cf7aaf2809..3785901bd97f7 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj @@ -1,5 +1,6 @@ + $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) true $(NetCoreAppCurrent)-browser true @@ -7,11 +8,11 @@ true true false + true + $(DefineConstants);FEATURE_WASM_THREADS $(DefineConstants);DISABLE_LEGACY_JS_INTEROP true - - <_XUnitBackgroundExec>false @@ -25,4 +26,11 @@ + + + + + + + diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs index 9fabc04697af8..8fdcfebf52c41 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs @@ -36,6 +36,7 @@ public async Task MultipleImportAsync() instance1.Dispose(); } +#if !FEATURE_WASM_THREADS // because in MT JSHost.ImportAsync is really async, it will finish before the caller could cancel it [Fact] public async Task CancelableImportAsync() { @@ -48,6 +49,7 @@ public async Task CancelableImportAsync() var actualEx = await Assert.ThrowsAsync(async () => await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs", new CancellationToken(true))); Assert.Equal("Error: OperationCanceledException", actualEx.Message); } +#endif [Fact] public unsafe void GlobalThis() @@ -187,7 +189,12 @@ public unsafe void CreateFunctionDoubleThrow() Func doubleThrows = Utils.CreateFunctionDoubleDoubleDouble("a", "b", "throw Error('test '+a+' '+b);"); var ex = Assert.Throws(() => doubleThrows(1, 2)); Assert.Equal("Error: test 1 2", ex.Message); + +#if !FEATURE_WASM_THREADS Assert.Contains("create_function", ex.StackTrace); +#else + Assert.Contains("omitted JavaScript stack trace", ex.StackTrace); +#endif } [Fact] @@ -2002,12 +2009,20 @@ public void JsImportMath() var exThrow0 = Assert.Throws(() => JavaScriptTestHelper.throw0()); Assert.Contains("throw-0-msg", exThrow0.Message); Assert.DoesNotContain(" at ", exThrow0.Message); +#if !FEATURE_WASM_THREADS Assert.Contains("throw0fn", exThrow0.StackTrace); +#else + Assert.Contains("omitted JavaScript stack trace", exThrow0.StackTrace); +#endif var exThrow1 = Assert.Throws(() => throw1(value)); Assert.Contains("throw1-msg", exThrow1.Message); Assert.DoesNotContain(" at ", exThrow1.Message); +#if !FEATURE_WASM_THREADS Assert.Contains("throw1fn", exThrow1.StackTrace); +#else + Assert.Contains("omitted JavaScript stack trace", exThrow0.StackTrace); +#endif // anything is a system.object, sometimes it would be JSObject wrapper if (typeof(T).IsPrimitive) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs index 559b9b4ff88e8..d3e63213078ad 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -1000,14 +1000,17 @@ public static async Task InitializeAsync() if (_module == null) { _module = await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs"); ; - await Setup(); ; + await Setup(); } - var p = echopromise_String("aaa"); - await p; - - // this gives browser chance to serve UI thread event loop before every test - await Task.Yield(); +#if FEATURE_WASM_THREADS + // are we in the UI thread ? + if (Environment.CurrentManagedThreadId == 1) +#endif + { + // this gives browser chance to serve UI thread event loop before every test + await Task.Yield(); + } } public static Task DisposeAsync() diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs new file mode 100644 index 0000000000000..3a65cc9f6ca47 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs @@ -0,0 +1,189 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading.Tasks; +using System.Threading; +using System.Net.Http; +using Xunit; +using System.IO; +using System.Collections.Generic; +using System.Net.WebSockets; +using System.Text; +using System.Linq; + +namespace System.Runtime.InteropServices.JavaScript.Tests +{ + // TODO test: + // JSWebWorker.RunAsync with CancellationToken + // JSExport 2x + // JSExport async + // timer + // GC + finalizer + dispose + // lock + // thread allocation, many threads + // TLS + // ProxyContext flow, child thread, child task + // use JSObject after JSWebWorker finished + // JSWebWorker JS setTimeout till after close + // WS on JSWebWorker + // Yield will hit event loop 3x + // HTTP continue on TP + // WS continue on TP + // event pipe + // FS + + public class WebWorkerTest + { + #region executor threads + + public static IEnumerable GetTargetThreads() + { + return Enum.GetValues().Select(type => new object[] { new Executor(type) }); + } + + public static IEnumerable GetTargetThreads2x2() + { + return Enum.GetValues().SelectMany( + type1 => Enum.GetValues().Select( + type2 => new object[] { new Executor(type1), new Executor(type2) })); + } + + #endregion + + #region Console, Yield, Delay + + [Theory, MemberData(nameof(GetTargetThreads))] + public async Task ManagedConsole(Executor executor) + { + await executor.Execute(() => + { + Console.WriteLine("C# Hello from ManagedThreadId: " + Environment.CurrentManagedThreadId); + return Task.CompletedTask; + }); + } + + [Theory, MemberData(nameof(GetTargetThreads))] + public async Task JSConsole(Executor executor) + { + await executor.Execute(() => + { + WebWorkerTestHelper.Log("JS Hello from ManagedThreadId: " + Environment.CurrentManagedThreadId + " NativeThreadId: " + WebWorkerTestHelper.NativeThreadId); + return Task.CompletedTask; + }); + } + + [Theory, MemberData(nameof(GetTargetThreads))] + public async Task NativeThreadId(Executor executor) + { + await executor.Execute(async () => + { + await executor.StickyAwait(WebWorkerTestHelper.InitializeAsync()); + + var jsTid = WebWorkerTestHelper.GetTid(); + var csTid = WebWorkerTestHelper.NativeThreadId; + if (executor.Type == ExecutorType.Main || executor.Type == ExecutorType.JSWebWorker) + { + Assert.Equal(jsTid, csTid); + } + else + { + Assert.NotEqual(jsTid, csTid); + } + + await WebWorkerTestHelper.DisposeAsync(); + }); + } + + [Theory, MemberData(nameof(GetTargetThreads))] + public async Task ThreadingTimer(Executor executor) + { + await executor.Execute(async () => + { + TaskCompletionSource tcs = new TaskCompletionSource(); + executor.AssertTargetThread(); + + using var timer = new Threading.Timer(_ => + { + Assert.NotEqual(1, Environment.CurrentManagedThreadId); + Assert.True(Thread.CurrentThread.IsThreadPoolThread); + tcs.SetResult(); + }, null, 100, Timeout.Infinite); + + await tcs.Task; + }); + } + + [Theory, MemberData(nameof(GetTargetThreads))] + public async Task JSDelay_ContinueWith(Executor executor) + { + await executor.Execute(async () => + { + await executor.StickyAwait(WebWorkerTestHelper.CreateDelay()); + + await WebWorkerTestHelper.Delay(1).ContinueWith(_ => + { + // continue on the context of the target JS interop + executor.AssertInteropThread(); + }, TaskContinuationOptions.ExecuteSynchronously); + }); + } + + [Theory, MemberData(nameof(GetTargetThreads))] + public async Task JSDelay_ConfigureAwait_True(Executor executor) + { + await executor.Execute(async () => + { + await executor.StickyAwait(WebWorkerTestHelper.CreateDelay()); + + await WebWorkerTestHelper.Delay(1).ConfigureAwait(true); + + executor.AssertAwaitCapturedContext(); + }); + } + + [Theory, MemberData(nameof(GetTargetThreads))] + public async Task ManagedDelay_ContinueWith(Executor executor) + { + await executor.Execute(async () => + { + executor.AssertTargetThread(); + await Task.Delay(1).ContinueWith(_ => + { + // continue on the context of the Timer's thread pool thread + Assert.True(Thread.CurrentThread.IsThreadPoolThread); + }, TaskContinuationOptions.ExecuteSynchronously); + }); + } + + + [Theory, MemberData(nameof(GetTargetThreads))] + public async Task ManagedDelay_ConfigureAwait_True(Executor executor) + { + await executor.Execute(async () => + { + executor.AssertTargetThread(); + + await Task.Delay(1).ConfigureAwait(true); + + executor.AssertAwaitCapturedContext(); + }); + } + + [Theory, MemberData(nameof(GetTargetThreads))] + public async Task ManagedYield(Executor executor) + { + await executor.Execute(async () => + { + executor.AssertTargetThread(); + + await Task.Yield(); + + executor.AssertAwaitCapturedContext(); + }); + } + + #endregion + + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.cs new file mode 100644 index 0000000000000..dfccfd6b609c1 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.cs @@ -0,0 +1,303 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using System.Threading; +using Xunit; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +namespace System.Runtime.InteropServices.JavaScript.Tests +{ + public partial class WebWorkerTestHelper + { + public static readonly string LocalHttpEcho = "http://" + Environment.GetEnvironmentVariable("DOTNET_TEST_HTTPHOST") + "/Echo.ashx"; + public static readonly string LocalWsEcho = "ws://" + Environment.GetEnvironmentVariable("DOTNET_TEST_WEBSOCKETHOST") + "/WebSocket/EchoWebSocket.ashx"; + + [JSImport("globalThis.console.log")] + public static partial void Log(string message); + + [JSImport("delay", "InlineTestHelper")] + public static partial Task Delay(int ms); + + [JSImport("getTid", "WebWorkerTestHelper")] + public static partial int GetTid(); + + public static string GetOriginUrl() + { + using var globalThis = JSHost.GlobalThis; + using var document = globalThis.GetPropertyAsJSObject("document"); + using var location = globalThis.GetPropertyAsJSObject("location"); + return location.GetPropertyAsString("origin"); + } + + public static Task ImportModuleFromString(string jsModule) + { + var es6DataUrl = $"data:text/javascript,{jsModule.Replace('\r', ' ').Replace('\n', ' ')}"; + return JSHost.ImportAsync("InlineTestHelper", es6DataUrl); + } + + #region Execute + + public async static Task RunOnNewThread(Func job) + { + TaskCompletionSource tcs = new TaskCompletionSource(); + var t = new Thread(() => + { + try + { + var task = job(); + task.Wait(); + tcs.SetResult(); + } + catch (Exception ex) + { + tcs.SetException(ex); + } + }); + t.Start(); + await tcs.Task; + t.Join(); + } + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern")] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern")] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:UnrecognizedReflectionPattern")] + public async static Task RunOnMainAsync(Func job) + { + if (MainSynchronizationContext == null) + { + var jsProxyContext = typeof(JSObject).Assembly.GetType("System.Runtime.InteropServices.JavaScript.JSProxyContext"); + var mainThreadContext = jsProxyContext.GetField("_MainThreadContext", BindingFlags.NonPublic | BindingFlags.Static); + var synchronizationContext = jsProxyContext.GetField("SynchronizationContext", BindingFlags.Public | BindingFlags.Instance); + var mainCtx = mainThreadContext.GetValue(null); + MainSynchronizationContext = (SynchronizationContext)synchronizationContext.GetValue(mainCtx); + } + await RunOnTargetAsync(MainSynchronizationContext, job); + } + + public static Task RunOnTargetAsync(SynchronizationContext ctx, Func job) + { + TaskCompletionSource tcs = new TaskCompletionSource(); + ctx.Post(async _ => + { + await InitializeAsync(); + try + { + await job().ConfigureAwait(true); + tcs.SetResult(); + } + catch (Exception ex) + { + tcs.SetException(ex); + } + finally + { + await DisposeAsync(); + } + }, null); + return tcs.Task; + } + + #endregion + + #region Setup + + [ThreadStatic] + public static JSObject WebWorkerTestHelperModule; + [ThreadStatic] + public static JSObject InlineTestHelperModule; + + public static SynchronizationContext MainSynchronizationContext; + + [JSImport("setup", "WebWorkerTestHelper")] + internal static partial Task Setup(); + + [JSImport("INTERNAL.forceDisposeProxies")] + internal static partial void ForceDisposeProxies(bool disposeMethods, bool verbose); + + public static async Task CreateDelay() + { + if (InlineTestHelperModule == null) + { + InlineTestHelperModule = await ImportModuleFromString(@" + export function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)) + } + ").ConfigureAwait(false); + } + else + { + await Delay(1).ConfigureAwait(false); + } + } + + public static async Task InitializeAsync() + { + if (WebWorkerTestHelperModule != null) + { + await DisposeAsync(); + } + + WebWorkerTestHelperModule = await JSHost.ImportAsync("WebWorkerTestHelper", "../WebWorkerTestHelper.mjs?" + Guid.NewGuid()); + await CreateDelay(); + await Setup(); + } + + public static Task DisposeAsync() + { + WebWorkerTestHelperModule?.Dispose(); + WebWorkerTestHelperModule = null; + return Task.CompletedTask; + } + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "thread_id")] + private static extern ref long GetThreadNativeThreadId(Thread @this); + + public static IntPtr NativeThreadId => (int)GetThreadNativeThreadId(Thread.CurrentThread); + + #endregion + } + + #region + + public enum ExecutorType + { + Main, + ThreadPool, + NewThread, + JSWebWorker, + } + public class Executor + { + public int ExecutorTID; + + public ExecutorType Type; + + public Executor(ExecutorType type) + { + Type = type; + } + + public Task Execute(Func job) + { + Task wrapExecute() + { + ExecutorTID = Environment.CurrentManagedThreadId; + return job(); + } + + switch (Type) + { + case ExecutorType.Main: + return WebWorkerTestHelper.RunOnMainAsync(wrapExecute); + case ExecutorType.ThreadPool: + return Task.Run(wrapExecute); + case ExecutorType.NewThread: + return WebWorkerTestHelper.RunOnNewThread(wrapExecute); + case ExecutorType.JSWebWorker: + return JSWebWorker.RunAsync(wrapExecute); + default: + throw new InvalidOperationException(); + } + } + + public void AssertTargetThread() + { + if (Type == ExecutorType.Main) + { + Assert.Equal(1, Environment.CurrentManagedThreadId); + } + else + { + Assert.NotEqual(1, Environment.CurrentManagedThreadId); + } + if (Type == ExecutorType.ThreadPool) + { + Assert.True(Thread.CurrentThread.IsThreadPoolThread); + } + else + { + Assert.False(Thread.CurrentThread.IsThreadPoolThread); + } + } + + public void AssertAwaitCapturedContext() + { + switch (Type) + { + case ExecutorType.Main: + Assert.Equal(1, Environment.CurrentManagedThreadId); + Assert.Equal(ExecutorTID, Environment.CurrentManagedThreadId); + Assert.False(Thread.CurrentThread.IsThreadPoolThread); + break; + case ExecutorType.JSWebWorker: + Assert.NotEqual(1, Environment.CurrentManagedThreadId); + Assert.Equal(ExecutorTID, Environment.CurrentManagedThreadId); + Assert.False(Thread.CurrentThread.IsThreadPoolThread); + break; + case ExecutorType.NewThread: + // the actual new thread is now blocked in .Wait() and so this is running on TP + Assert.NotEqual(1, Environment.CurrentManagedThreadId); + Assert.NotEqual(ExecutorTID, Environment.CurrentManagedThreadId); + Assert.True(Thread.CurrentThread.IsThreadPoolThread); + break; + case ExecutorType.ThreadPool: + // it could migrate to any TP thread + Assert.NotEqual(1, Environment.CurrentManagedThreadId); + Assert.True(Thread.CurrentThread.IsThreadPoolThread); + break; + } + } + + public void AssertInteropThread() + { + switch (Type) + { + case ExecutorType.Main: + Assert.Equal(1, Environment.CurrentManagedThreadId); + Assert.Equal(ExecutorTID, Environment.CurrentManagedThreadId); + Assert.False(Thread.CurrentThread.IsThreadPoolThread); + break; + case ExecutorType.JSWebWorker: + Assert.NotEqual(1, Environment.CurrentManagedThreadId); + Assert.Equal(ExecutorTID, Environment.CurrentManagedThreadId); + Assert.False(Thread.CurrentThread.IsThreadPoolThread); + break; + case ExecutorType.NewThread: + // it will synchronously continue on the UI thread + Assert.Equal(1, Environment.CurrentManagedThreadId); + Assert.NotEqual(ExecutorTID, Environment.CurrentManagedThreadId); + Assert.False(Thread.CurrentThread.IsThreadPoolThread); + break; + case ExecutorType.ThreadPool: + // it will synchronously continue on the UI thread + Assert.Equal(1, Environment.CurrentManagedThreadId); + Assert.NotEqual(ExecutorTID, Environment.CurrentManagedThreadId); + Assert.False(Thread.CurrentThread.IsThreadPoolThread); + break; + } + } + + public override string ToString() => Type.ToString(); + + // make sure we stay on the executor + public async Task StickyAwait(Task task) + { + if (Type == ExecutorType.NewThread) + { + task.Wait(); + } + else + { + await task.ConfigureAwait(true); + } + AssertTargetThread(); + } + } + + #endregion +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.mjs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.mjs new file mode 100644 index 0000000000000..d7912c0480be5 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.mjs @@ -0,0 +1,11 @@ +let dllExports; + +const runtime = getDotnetRuntime(0); + +export async function setup() { + dllExports = await runtime.getAssemblyExports("System.Runtime.InteropServices.JavaScript.Tests.dll"); +} + +export function getTid() { + return runtime.Module["_pthread_self"](); +} diff --git a/src/mono/browser/runtime/corebindings.c b/src/mono/browser/runtime/corebindings.c index 8657cd261459f..4b52eb4badf22 100644 --- a/src/mono/browser/runtime/corebindings.c +++ b/src/mono/browser/runtime/corebindings.c @@ -18,10 +18,6 @@ //JS funcs extern void mono_wasm_release_cs_owned_object (int js_handle); -extern void mono_wasm_bind_js_import(void *signature, int *is_exception, MonoObject **result); -extern void mono_wasm_invoke_js_function(int function_js_handle, void *args); -extern void mono_wasm_invoke_js_import(int function_handle, void *args); -extern void mono_wasm_bind_cs_function(MonoString **fully_qualified_name, int signature_hash, void* signatures, int *is_exception, MonoObject **result); extern void mono_wasm_resolve_or_reject_promise(void *data); typedef void (*background_job_cb)(void); @@ -44,6 +40,15 @@ extern void* mono_wasm_invoke_js_blazor (MonoString **exceptionMessage, void *ca #ifndef DISABLE_THREADS extern void mono_wasm_install_js_worker_interop (int context_gc_handle); extern void mono_wasm_uninstall_js_worker_interop (); +extern void mono_wasm_bind_cs_function(MonoString **fully_qualified_name, int signature_hash, void* signatures, int *is_exception, MonoObject **result); +extern void mono_wasm_invoke_import_async(void* args, void* signature); +extern void mono_wasm_invoke_import_sync(void* args, void* signature); +extern void mono_wasm_invoke_js_function(int function_js_handle, void *args); +#else +extern void mono_wasm_bind_cs_function(MonoString **fully_qualified_name, int signature_hash, void* signatures, int *is_exception, MonoObject **result); +extern void mono_wasm_bind_js_import(void *signature, int *is_exception, MonoObject **result); +extern void mono_wasm_invoke_js_import(int function_handle, void *args); +extern void mono_wasm_invoke_js_function(int function_js_handle, void *args); #endif /* DISABLE_THREADS */ // HybridGlobalization @@ -61,10 +66,6 @@ extern int mono_wasm_get_first_week_of_year(MonoString **culture, int *is_except void bindings_initialize_internals (void) { mono_add_internal_call ("Interop/Runtime::ReleaseCSOwnedObject", mono_wasm_release_cs_owned_object); - mono_add_internal_call ("Interop/Runtime::BindJSImport", mono_wasm_bind_js_import); - mono_add_internal_call ("Interop/Runtime::InvokeJSFunction", mono_wasm_invoke_js_function); - mono_add_internal_call ("Interop/Runtime::InvokeJSImport", mono_wasm_invoke_js_import); - mono_add_internal_call ("Interop/Runtime::BindCSFunction", mono_wasm_bind_cs_function); mono_add_internal_call ("Interop/Runtime::ResolveOrRejectPromise", mono_wasm_resolve_or_reject_promise); #ifndef ENABLE_JS_INTEROP_BY_VALUE @@ -75,6 +76,15 @@ void bindings_initialize_internals (void) #ifndef DISABLE_THREADS mono_add_internal_call ("Interop/Runtime::InstallWebWorkerInterop", mono_wasm_install_js_worker_interop); mono_add_internal_call ("Interop/Runtime::UninstallWebWorkerInterop", mono_wasm_uninstall_js_worker_interop); + mono_add_internal_call ("Interop/Runtime::BindCSFunction", mono_wasm_bind_cs_function); + mono_add_internal_call ("Interop/Runtime::InvokeJSImportSync", mono_wasm_invoke_import_sync); + mono_add_internal_call ("Interop/Runtime::InvokeJSImportAsync", mono_wasm_invoke_import_async); + mono_add_internal_call ("Interop/Runtime::InvokeJSFunction", mono_wasm_invoke_js_function); +#else + mono_add_internal_call ("Interop/Runtime::BindCSFunction", mono_wasm_bind_cs_function); + mono_add_internal_call ("Interop/Runtime::BindJSImport", mono_wasm_bind_js_import); + mono_add_internal_call ("Interop/Runtime::InvokeJSImport", mono_wasm_invoke_js_import); + mono_add_internal_call ("Interop/Runtime::InvokeJSFunction", mono_wasm_invoke_js_function); #endif /* DISABLE_THREADS */ #ifndef DISABLE_LEGACY_JS_INTEROP diff --git a/src/mono/browser/runtime/exports-binding.ts b/src/mono/browser/runtime/exports-binding.ts index 50f3c14c0a313..34d23af190d22 100644 --- a/src/mono/browser/runtime/exports-binding.ts +++ b/src/mono/browser/runtime/exports-binding.ts @@ -7,7 +7,7 @@ import WasmEnableLegacyJsInterop from "consts:wasmEnableLegacyJsInterop"; 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_cs_function } from "./invoke-cs"; -import { mono_wasm_bind_js_import, mono_wasm_invoke_js_function, mono_wasm_invoke_js_import } from "./invoke-js"; +import { mono_wasm_bind_js_import, mono_wasm_invoke_js_function, mono_wasm_invoke_import_async, mono_wasm_invoke_import_sync, mono_wasm_invoke_js_import } 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"; @@ -53,6 +53,8 @@ export const mono_wasm_threads_imports = !MonoWasmThreads ? [] : [ // corebindings.c mono_wasm_install_js_worker_interop, mono_wasm_uninstall_js_worker_interop, + mono_wasm_invoke_import_async, + mono_wasm_invoke_import_sync, ]; export const mono_wasm_legacy_interop_imports = !WasmEnableLegacyJsInterop ? [] : [ diff --git a/src/mono/browser/runtime/gc-handles.ts b/src/mono/browser/runtime/gc-handles.ts index 6d95160ada062..d0c8c6094cba1 100644 --- a/src/mono/browser/runtime/gc-handles.ts +++ b/src/mono/browser/runtime/gc-handles.ts @@ -5,7 +5,7 @@ import MonoWasmThreads from "consts:monoWasmThreads"; import BuildConfiguration from "consts:configuration"; import { loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; -import { js_import_wrapper_by_fn_handle } from "./invoke-js"; +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"; @@ -70,6 +70,7 @@ export function mono_wasm_get_jsobj_from_js_handle(js_handle: JSHandle): any { } export function mono_wasm_get_js_handle(js_obj: any): JSHandle { + assert_js_interop(); if (js_obj[cs_owned_js_handle_symbol]) { return js_obj[cs_owned_js_handle_symbol]; } @@ -90,6 +91,7 @@ export function mono_wasm_get_js_handle(js_obj: any): JSHandle { } export function register_with_jsv_handle(js_obj: any, jsv_handle: JSHandle) { + assert_js_interop(); // note _cs_owned_objects_by_js_handle is list, not Map. That's why we maintain _js_handle_free_list. _cs_owned_objects_by_jsv_handle[0 - jsv_handle] = js_obj; @@ -118,6 +120,7 @@ export function mono_wasm_release_cs_owned_object(js_handle: JSHandle): void { } export function setup_managed_proxy(owner: any, gc_handle: GCHandle): void { + assert_js_interop(); // keep the gc_handle so that we could easily convert it back to original C# object for roundtrip owner[js_owned_gc_handle_symbol] = gc_handle; @@ -134,6 +137,7 @@ export function setup_managed_proxy(owner: any, gc_handle: GCHandle): void { } export function teardown_managed_proxy(owner: any, gc_handle: GCHandle, skipManaged?: boolean): void { + assert_js_interop(); // The JS object associated with this gc_handle has been collected by the JS GC. // As such, it's not possible for this gc_handle to be invoked by JS anymore, so // we can release the tracking weakref (it's null now, by definition), diff --git a/src/mono/browser/runtime/http.ts b/src/mono/browser/runtime/http.ts index 23f8b20e3e4be..64026166f1a59 100644 --- a/src/mono/browser/runtime/http.ts +++ b/src/mono/browser/runtime/http.ts @@ -3,8 +3,8 @@ import { wrap_as_cancelable_promise } from "./cancelable-promise"; import { ENVIRONMENT_IS_NODE, Module, loaderHelpers, mono_assert } from "./globals"; +import { assert_js_interop } from "./invoke-js"; import { MemoryViewType, Span } from "./marshal"; -import { assert_synchronization_context } from "./pthreads/shared"; import type { VoidPtr } from "./types/emscripten"; import { ControllablePromise } from "./types/internal"; @@ -113,7 +113,7 @@ export function http_wasm_fetch_bytes(url: string, header_names: string[], heade export function http_wasm_fetch(url: string, header_names: string[], header_values: string[], option_names: string[], option_values: any[], abort_controller: AbortController, body: Uint8Array | ReadableStream | null): ControllablePromise { verifyEnvironment(); - assert_synchronization_context(); + assert_js_interop(); mono_assert(url && typeof url === "string", "expected url string"); mono_assert(header_names && header_values && Array.isArray(header_names) && Array.isArray(header_values) && header_names.length === header_values.length, "expected headerNames and headerValues arrays"); mono_assert(option_names && option_values && Array.isArray(option_names) && Array.isArray(option_values) && option_names.length === option_values.length, "expected headerNames and headerValues arrays"); diff --git a/src/mono/browser/runtime/invoke-cs.ts b/src/mono/browser/runtime/invoke-cs.ts index 46324a6617559..03caffeecea74 100644 --- a/src/mono/browser/runtime/invoke-cs.ts +++ b/src/mono/browser/runtime/invoke-cs.ts @@ -17,13 +17,12 @@ import { MonoObjectRef, MonoStringRef, MonoString, MonoObject, MonoMethod, JSMar import { Int32Ptr } from "./types/emscripten"; import cwraps from "./cwraps"; import { assembly_load } from "./class-loader"; -import { assert_bindings, wrap_error_root, wrap_no_error_root } from "./invoke-js"; +import { assert_c_interop, assert_js_interop, 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_bindings(); + assert_js_interop(); const fqn_root = mono_wasm_new_external_root(fully_qualified_name), resultRoot = mono_wasm_new_external_root(result_address); const mark = startMeasure(); try { @@ -55,9 +54,6 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, for (let index = 0; index < args_count; index++) { const sig = get_sig(signature, index + 2); const marshaler_type = get_signature_type(sig); - if (marshaler_type == MarshalerType.Task) { - assert_synchronization_context(); - } const arg_marshaler = bind_arg_marshal_to_cs(sig, marshaler_type, index + 2); mono_assert(arg_marshaler, "ERR43: argument marshaler must be resolved"); arg_marshalers[index] = arg_marshaler; @@ -67,7 +63,6 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, let res_marshaler_type = get_signature_type(res_sig); const is_async = res_marshaler_type == MarshalerType.Task; if (is_async) { - assert_synchronization_context(); res_marshaler_type = MarshalerType.TaskPreCreated; } const res_converter = bind_arg_marshal_to_js(res_sig, res_marshaler_type, 1); @@ -353,7 +348,7 @@ type BindingClosure = { } export function invoke_method_and_handle_exception(method: MonoMethod, args: JSMarshalerArguments): void { - assert_bindings(); + assert_js_interop(); const fail_root = mono_wasm_new_root(); try { set_args_context(args); @@ -370,7 +365,7 @@ export function invoke_method_and_handle_exception(method: MonoMethod, args: JSM } export function invoke_method_raw(method: MonoMethod): void { - assert_bindings(); + assert_c_interop(); const fail_root = mono_wasm_new_root(); try { const fail = cwraps.mono_wasm_invoke_method_raw(method, fail_root.address); @@ -412,7 +407,7 @@ function _walk_exports_to_set_function(assembly: string, namespace: string, clas } export async function mono_wasm_get_assembly_exports(assembly: string): Promise { - assert_bindings(); + assert_js_interop(); const result = exportsByAssembly.get(assembly); if (!result) { const mark = startMeasure(); diff --git a/src/mono/browser/runtime/invoke-js.ts b/src/mono/browser/runtime/invoke-js.ts index d87e1b3d2f5cd..9aa56b1e745a5 100644 --- a/src/mono/browser/runtime/invoke-js.ts +++ b/src/mono/browser/runtime/invoke-js.ts @@ -17,12 +17,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"; export const js_import_wrapper_by_fn_handle: Function[] = [null];// 0th slot is dummy, main thread we free them on shutdown. On web worker thread we free them when worker is detached. export function mono_wasm_bind_js_import(signature: JSFunctionSignature, is_exception: Int32Ptr, result_address: MonoObjectRef): void { - assert_bindings(); + if (MonoWasmThreads) return; + assert_js_interop(); const resultRoot = mono_wasm_new_external_root(result_address); try { bind_js_import(signature); @@ -35,7 +35,43 @@ export function mono_wasm_bind_js_import(signature: JSFunctionSignature, is_exce } } +export function mono_wasm_invoke_import_async(args: JSMarshalerArguments, signature: JSFunctionSignature) { + if (!MonoWasmThreads) return; + assert_js_interop(); + + const function_handle = get_signature_handle(signature); + + let bound_fn = js_import_wrapper_by_fn_handle[function_handle]; + if (bound_fn == undefined) { + // it was not bound yet, let's do it now + bound_fn = bind_js_import(signature); + } + mono_assert(bound_fn, () => `Imported function handle expected ${function_handle}`); + + bound_fn(args); + + // this works together with AllocHGlobal in JSFunctionBinding.DispatchJSImportAsync + Module._free(args as any); +} + +export function mono_wasm_invoke_import_sync(args: JSMarshalerArguments, signature: JSFunctionSignature) { + if (!MonoWasmThreads) return; + assert_js_interop(); + + const function_handle = get_signature_handle(signature); + + let bound_fn = js_import_wrapper_by_fn_handle[function_handle]; + if (bound_fn == undefined) { + // it was not bound yet, let's do it now + bound_fn = bind_js_import(signature); + } + mono_assert(bound_fn, () => `Imported function handle expected ${function_handle}`); + + bound_fn(args); +} + function bind_js_import(signature: JSFunctionSignature): Function { + assert_js_interop(); const mark = startMeasure(); const version = get_signature_version(signature); @@ -67,15 +103,9 @@ function bind_js_import(signature: JSFunctionSignature): Function { }; has_cleanup = true; } - else if (marshaler_type == MarshalerType.Task) { - assert_synchronization_context(); - } } const res_sig = get_sig(signature, 1); const res_marshaler_type = get_signature_type(res_sig); - if (res_marshaler_type == MarshalerType.Task) { - assert_synchronization_context(); - } const res_converter = bind_arg_marshal_to_cs(res_sig, res_marshaler_type, 1); const closure: BindingClosure = { @@ -348,9 +378,9 @@ export const importedModulesPromises: Map> = new Map(); export const importedModules: Map> = new Map(); export function dynamic_import(module_name: string, module_url: string): Promise { + assert_js_interop(); mono_assert(module_name && typeof module_name === "string", "module_name must be string"); mono_assert(module_url && typeof module_url === "string", "module_url must be string"); - assert_synchronization_context(); let promise = importedModulesPromises.get(module_name); const newPromise = !promise; if (newPromise) { @@ -408,7 +438,16 @@ export function wrap_no_error_root(is_exception: Int32Ptr | null, result?: WasmR } } -export function assert_bindings(): void { +export function assert_js_interop(): void { + loaderHelpers.assert_runtime_running(); + if (MonoWasmThreads) { + mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready && runtimeHelpers.proxy_context_gc_handle, "Please use dedicated worker for working with JavaScript interop. See https://github.com/dotnet/runtime/blob/main/src/mono/wasm/threads.md#JS-interop-on-dedicated-threads"); + } else { + mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "The runtime must be initialized."); + } +} + +export function assert_c_interop(): void { loaderHelpers.assert_runtime_running(); if (MonoWasmThreads) { mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "Please use dedicated worker for working with JavaScript interop. See https://github.com/dotnet/runtime/blob/main/src/mono/wasm/threads.md#JS-interop-on-dedicated-threads"); diff --git a/src/mono/browser/runtime/loader/globals.ts b/src/mono/browser/runtime/loader/globals.ts index 2b912b6e1b530..fa07faabf0b6d 100644 --- a/src/mono/browser/runtime/loader/globals.ts +++ b/src/mono/browser/runtime/loader/globals.ts @@ -13,7 +13,7 @@ import type { MonoConfig, RuntimeAPI } from "../types"; import { assert_runtime_running, is_exited, is_runtime_running, mono_exit } from "./exit"; import { assertIsControllablePromise, createPromiseController, getPromiseController } from "./promise-controller"; import { mono_download_assets, resolve_single_asset_path, retrieve_asset_download } from "./assets"; -import { setup_proxy_console } from "./logging"; +import { mono_set_thread_name, setup_proxy_console } from "./logging"; import { invokeLibraryInitializers } from "./libraryInitializers"; import { deep_merge_config, hasDebuggingEnabled } from "./config"; import { logDownloadStatsToConsole, purgeUnusedCacheEntriesAsync } from "./assetsCache"; @@ -114,6 +114,7 @@ export function setLoaderGlobals( mono_download_assets, resolve_single_asset_path, setup_proxy_console, + mono_set_thread_name, logDownloadStatsToConsole, purgeUnusedCacheEntriesAsync, diff --git a/src/mono/browser/runtime/loader/logging.ts b/src/mono/browser/runtime/loader/logging.ts index ed0a76feaf715..df6723397f87d 100644 --- a/src/mono/browser/runtime/loader/logging.ts +++ b/src/mono/browser/runtime/loader/logging.ts @@ -2,14 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. /* eslint-disable no-console */ -import { loaderHelpers } from "./globals"; +import { ENVIRONMENT_IS_WORKER, loaderHelpers } from "./globals"; const methods = ["debug", "log", "trace", "warn", "info", "error"]; const prefix = "MONO_WASM: "; let consoleWebSocket: WebSocket; let theConsoleApi: any; let originalConsoleMethods: any; -let threadId: string; +let threadNamePrefix: string; + +export function mono_set_thread_name(threadName: string) { + threadNamePrefix = threadName; +} export function mono_log_debug(msg: string, ...data: any) { if (loaderHelpers.diagnosticTracing) { @@ -55,13 +59,13 @@ function proxyConsoleMethod(prefix: string, func: any, asJson: boolean) { if (typeof payload === "string") { if (payload[0] == "[") { const now = new Date().toISOString(); - if (threadId !== "main") { - payload = `[${threadId}][${now}] ${payload}`; + if (ENVIRONMENT_IS_WORKER) { + payload = `[${threadNamePrefix}][${now}] ${payload}`; } else { payload = `[${now}] ${payload}`; } - } else if (threadId !== "main") { - payload = `[${threadId}] ${payload}`; + } else if (ENVIRONMENT_IS_WORKER) { + payload = `[${threadNamePrefix}] ${payload}`; } } @@ -82,7 +86,7 @@ function proxyConsoleMethod(prefix: string, func: any, asJson: boolean) { export function setup_proxy_console(id: string, console: Console, origin: string): void { theConsoleApi = console as any; - threadId = id; + threadNamePrefix = id; originalConsoleMethods = { ...console }; @@ -94,11 +98,6 @@ export function setup_proxy_console(id: string, console: Console, origin: string consoleWebSocket = new WebSocket(consoleUrl); consoleWebSocket.addEventListener("error", logWSError); consoleWebSocket.addEventListener("close", logWSClose); - consoleWebSocket.addEventListener("open", () => { - originalConsoleMethods.log(`browser: [${threadId}] Console websocket connected.`); - }, { - once: true - }); } export function teardown_proxy_console(message?: string) { @@ -138,11 +137,11 @@ function send(msg: string) { } function logWSError(event: Event) { - originalConsoleMethods.error(`[${threadId}] websocket error: ${event}`, event); + originalConsoleMethods.error(`[${threadNamePrefix}] websocket error: ${event}`, event); } function logWSClose(event: Event) { - originalConsoleMethods.error(`[${threadId}] websocket closed: ${event}`, event); + originalConsoleMethods.error(`[${threadNamePrefix}] websocket closed: ${event}`, event); } function setupWS() { diff --git a/src/mono/browser/runtime/loader/run.ts b/src/mono/browser/runtime/loader/run.ts index 9d26827a83f4a..7f3804917a26c 100644 --- a/src/mono/browser/runtime/loader/run.ts +++ b/src/mono/browser/runtime/loader/run.ts @@ -6,7 +6,7 @@ import BuildConfiguration from "consts:configuration"; import type { MonoConfig, DotnetHostBuilder, DotnetModuleConfig, RuntimeAPI, LoadBootResourceCallback } from "../types"; import type { MonoConfigInternal, EmscriptenModuleInternal, RuntimeModuleExportsInternal, NativeModuleExportsInternal, } from "../types/internal"; -import { ENVIRONMENT_IS_WEB, emscriptenModule, exportedRuntimeAPI, globalObjectsRoot, monoConfig, mono_assert } from "./globals"; +import { ENVIRONMENT_IS_WEB, ENVIRONMENT_IS_WORKER, emscriptenModule, exportedRuntimeAPI, globalObjectsRoot, monoConfig, mono_assert } from "./globals"; import { deep_merge_config, deep_merge_module, mono_wasm_load_config } from "./config"; import { mono_exit, register_exit_handlers } from "./exit"; import { setup_proxy_console, mono_log_info, mono_log_debug } from "./logging"; @@ -419,7 +419,7 @@ export async function createEmscripten(moduleFactory: DotnetModuleConfig | ((api } await detect_features_and_polyfill(emscriptenModule); - if (BuildConfiguration === "Debug") { + if (BuildConfiguration === "Debug" && !ENVIRONMENT_IS_WORKER) { mono_log_info(`starting script ${loaderHelpers.scriptUrl}`); mono_log_info(`starting in ${loaderHelpers.scriptDirectory}`); } diff --git a/src/mono/browser/runtime/loader/worker.ts b/src/mono/browser/runtime/loader/worker.ts index 01b5d23c92264..3bb042aed5e4f 100644 --- a/src/mono/browser/runtime/loader/worker.ts +++ b/src/mono/browser/runtime/loader/worker.ts @@ -39,7 +39,7 @@ function onMonoConfigReceived(config: MonoConfigInternal): void { loaderHelpers.afterConfigLoaded.promise_control.resolve(loaderHelpers.config); if (ENVIRONMENT_IS_WEB && config.forwardConsoleLogsToWS && typeof globalThis.WebSocket != "undefined") { - loaderHelpers.setup_proxy_console("pthread-worker", console, globalThis.location.origin); + loaderHelpers.setup_proxy_console("worker-idle", console, globalThis.location.origin); } } diff --git a/src/mono/browser/runtime/logging.ts b/src/mono/browser/runtime/logging.ts index 869a08e3d685a..83f61569f03ec 100644 --- a/src/mono/browser/runtime/logging.ts +++ b/src/mono/browser/runtime/logging.ts @@ -8,8 +8,8 @@ import { CharPtr, VoidPtr } from "./types/emscripten"; let prefix = "MONO_WASM: "; -export function mono_set_thread_id(tid: string) { - prefix = `MONO_WASM [${tid}]: `; +export function mono_set_thread_name(threadName: string) { + prefix = `MONO_WASM [${threadName}]: `; } export function mono_log_debug(msg: string, ...data: any) { diff --git a/src/mono/browser/runtime/managed-exports.ts b/src/mono/browser/runtime/managed-exports.ts index fff5deb4bb909..6d2e54a551f6e 100644 --- a/src/mono/browser/runtime/managed-exports.ts +++ b/src/mono/browser/runtime/managed-exports.ts @@ -59,6 +59,7 @@ export function init_managed_exports(): void { // because this is async, we could pre-allocate the promise let promise = begin_marshal_task_to_js(res, MarshalerType.TaskPreCreated, marshal_int32_to_js); + // NOTE: at the moment this is synchronous call on the same thread and therefore we could marshal (null) result synchronously invoke_method_and_handle_exception(call_entry_point, args); // in case the C# side returned synchronously diff --git a/src/mono/browser/runtime/marshal-to-cs.ts b/src/mono/browser/runtime/marshal-to-cs.ts index c0dc159eaa8fa..defc56cb0417a 100644 --- a/src/mono/browser/runtime/marshal-to-cs.ts +++ b/src/mono/browser/runtime/marshal-to-cs.ts @@ -309,13 +309,22 @@ export class PromiseHolder extends ManagedObject { } function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: MarshalerType, res_converter?: MarshalerToCs) { + const handleIsPreallocated = get_arg_type(arg) == MarshalerType.TaskPreCreated; if (value === null || value === undefined) { - set_arg_type(arg, MarshalerType.None); - return; + if (MonoWasmThreads && handleIsPreallocated) { + // This is multi-threading return from JSImport with Task result and we can't return synchronously, + // because C# caller could be on different thread and sent us an async message. + // It already returned pending Task to it's own caller. + const err = new Error("InvalidOperationException: Task return with null value is not supported in multi-threading scenario."); + // Alternatively we can return promise and resolve it with null/default value. + value = Promise.reject(err); + } else { + set_arg_type(arg, MarshalerType.None); + return; + } } mono_check(isThenable(value), "Value is not a Promise"); - const handleIsPreallocated = get_arg_type(arg) == MarshalerType.TaskPreCreated; const gc_handle = handleIsPreallocated ? get_arg_gc_handle(arg) : alloc_gcv_handle(); if (!handleIsPreallocated) { set_gc_handle(arg, gc_handle); @@ -330,7 +339,7 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: if (MonoWasmThreads) addUnsettledPromise(); - value.then(data => { + function resolve(data: any) { if (!loaderHelpers.is_runtime_running()) { mono_log_debug("This promise can't be propagated to managed code, mono runtime already exited."); return; @@ -349,7 +358,9 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: catch (ex) { runtimeHelpers.abort(ex); } - }).catch(reason => { + } + + function reject(reason: any) { if (!loaderHelpers.is_runtime_running()) { mono_log_debug("This promise can't be propagated to managed code, mono runtime already exited.", reason); return; @@ -367,7 +378,9 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: catch (ex) { runtimeHelpers.abort(ex); } - }); + } + + value.then(resolve).catch(reject); } export function marshal_exception_to_cs(arg: JSMarshalerArgument, value: any): void { diff --git a/src/mono/browser/runtime/marshal-to-js.ts b/src/mono/browser/runtime/marshal-to-js.ts index 0aa489b3b83c3..4eb46d211e105 100644 --- a/src/mono/browser/runtime/marshal-to-js.ts +++ b/src/mono/browser/runtime/marshal-to-js.ts @@ -185,6 +185,7 @@ function _marshal_datetime_to_js(arg: JSMarshalerArgument): Date | null { return get_arg_date(arg); } +// NOTE: at the moment, this can't dispatch async calls (with Task/Promise return type). Therefore we don't have to worry about pre-created Task. function _marshal_delegate_to_js(arg: JSMarshalerArgument, _?: MarshalerType, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs): Function | null { const type = get_arg_type(arg); if (type === MarshalerType.None) { @@ -348,11 +349,18 @@ export function mono_wasm_resolve_or_reject_promise(args: JSMarshalerArguments): mono_assert(holder, () => `Cannot find Promise for JSHandle ${js_handle}`); holder.resolve_or_reject(type, js_handle, arg_value); - + if (MonoWasmThreads) { + // this works together with AllocHGlobal in JSFunctionBinding.ResolveOrRejectPromise + Module._free(args as any); + return; + } set_arg_type(res, MarshalerType.Void); set_arg_type(exc, MarshalerType.None); } catch (ex: any) { + if (MonoWasmThreads) { + mono_assert(false, () => `Failed to resolve or reject promise ${ex}`); + } marshal_exception_to_cs(exc, ex); } } diff --git a/src/mono/browser/runtime/net6-legacy/method-binding.ts b/src/mono/browser/runtime/net6-legacy/method-binding.ts index 2d38c950674a9..aa710700924e8 100644 --- a/src/mono/browser/runtime/net6-legacy/method-binding.ts +++ b/src/mono/browser/runtime/net6-legacy/method-binding.ts @@ -17,7 +17,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_bindings } from "../invoke-js"; +import { assert_js_interop } from "../invoke-js"; const escapeRE = /[^A-Za-z0-9_$]/g; @@ -678,5 +678,5 @@ export function assert_legacy_interop(): void { if (MonoWasmThreads) { mono_assert(!ENVIRONMENT_IS_PTHREAD, "Legacy interop is not supported with WebAssembly threads."); } - assert_bindings(); + assert_js_interop(); } \ No newline at end of file diff --git a/src/mono/browser/runtime/pthreads/browser/index.ts b/src/mono/browser/runtime/pthreads/browser/index.ts index 4b0f3153db44a..2ff00afe4eda9 100644 --- a/src/mono/browser/runtime/pthreads/browser/index.ts +++ b/src/mono/browser/runtime/pthreads/browser/index.ts @@ -101,7 +101,6 @@ function monoWorkerMessageHandler(worker: Worker, ev: MessageEvent void, } -interface EmscriptenPThreadInfo { - threadInfoStruct: pthreadPtr; -} /// N.B. emscripten deletes the `pthread` property from the worker when it is not actively running a pthread export interface PThreadWorker extends Worker { - pthread: EmscriptenPThreadInfo; + pthread_ptr: pthreadPtr; loaded: boolean; interopInstalled: boolean; } @@ -60,8 +57,7 @@ export const Internals = !MonoWasmThreads ? null as any : { /// They hang a "pthread" object from the worker if the worker is running a thread, and remove it when the thread stops by doing `pthread_exit` or when it's joined using `pthread_join`. if (!isRunningPThreadWorker(worker)) return undefined; - const emscriptenThreadInfo = worker.pthread; - return emscriptenThreadInfo.threadInfoStruct; + return worker.pthread_ptr; }, allocateUnusedWorker: (): void => { /// See library_pthread.js in Emscripten. diff --git a/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts b/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts index 8fec3b10faf8a..dcbb767ebe2c4 100644 --- a/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts +++ b/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts @@ -40,7 +40,7 @@ export function replaceEmscriptenPThreadLibrary(modulePThread: PThreadLibrary): worker.onmessage!(new MessageEvent("message", { data: { "cmd": "killThread", - thread: worker.pthread + thread: worker.pthread_ptr } })); } else { diff --git a/src/mono/browser/runtime/pthreads/shared/index.ts b/src/mono/browser/runtime/pthreads/shared/index.ts index 499ff3f129c0d..0024e09f1e4c8 100644 --- a/src/mono/browser/runtime/pthreads/shared/index.ts +++ b/src/mono/browser/runtime/pthreads/shared/index.ts @@ -20,19 +20,11 @@ export interface PThreadInfo { export const MainThread: PThreadInfo = { get pthreadId(): pthreadPtr { - return getBrowserThreadID(); + return mono_wasm_main_thread_ptr(); }, isBrowserThread: true }; -let browserThreadIdLazy: pthreadPtr | undefined; -export function getBrowserThreadID(): pthreadPtr { - if (browserThreadIdLazy === undefined) { - browserThreadIdLazy = (Module)["_emscripten_main_runtime_thread_id"]() as pthreadPtr; - } - return browserThreadIdLazy; -} - const enum WorkerMonoCommandType { enabledInterop = "notify_enabled_interop", channelCreated = "channel_created", @@ -195,12 +187,6 @@ export function mono_wasm_uninstall_js_worker_interop(): void { set_thread_info(pthread_self ? pthread_self.pthreadId : 0, true, false, false); } -export function assert_synchronization_context(): void { - if (MonoWasmThreads) { - mono_assert(runtimeHelpers.proxy_context_gc_handle, "Please use dedicated worker for working with JavaScript interop. See https://github.com/dotnet/runtime/blob/main/src/mono/wasm/threads.md#JS-interop-on-dedicated-threads"); - } -} - // this is just for Debug build of the runtime, making it easier to debug worker threads export function set_thread_info(pthread_ptr: number, isAttached: boolean, hasInterop: boolean, hasSynchronization: boolean): void { if (MonoWasmThreads && BuildConfiguration === "Debug" && !runtimeHelpers.cspPolicy) { @@ -212,3 +198,13 @@ export function set_thread_info(pthread_ptr: number, isAttached: boolean, hasInt } } } + +export function mono_wasm_pthread_ptr(): number { + if (!MonoWasmThreads) return 0; + return (Module)["_pthread_self"](); +} + +export function mono_wasm_main_thread_ptr(): number { + if (!MonoWasmThreads) return 0; + return (Module)["_emscripten_main_runtime_thread_id"](); +} diff --git a/src/mono/browser/runtime/pthreads/worker/index.ts b/src/mono/browser/runtime/pthreads/worker/index.ts index b92b5bb8c3bbb..468c703bf65e9 100644 --- a/src/mono/browser/runtime/pthreads/worker/index.ts +++ b/src/mono/browser/runtime/pthreads/worker/index.ts @@ -5,8 +5,8 @@ import MonoWasmThreads from "consts:monoWasmThreads"; -import { Module, ENVIRONMENT_IS_PTHREAD, mono_assert } from "../../globals"; -import { makeChannelCreatedMonoMessage, set_thread_info } from "../shared"; +import { ENVIRONMENT_IS_PTHREAD, mono_assert, loaderHelpers } from "../../globals"; +import { makeChannelCreatedMonoMessage, mono_wasm_pthread_ptr, set_thread_info } from "../shared"; import type { pthreadPtr } from "../shared/types"; import { is_nullish } from "../../types/internal"; import type { MonoThreadMessage } from "../shared"; @@ -18,7 +18,7 @@ import { WorkerThreadEventTarget } from "./events"; import { postRunWorker, preRunWorker } from "../../startup"; -import { mono_log_debug, mono_set_thread_id } from "../../logging"; +import { mono_log_debug, mono_set_thread_name } from "../../logging"; import { jiterpreter_allocate_tables } from "../../jiterpreter-support"; // re-export some of the events types @@ -70,6 +70,7 @@ function monoDedicatedChannelMessageFromMainToWorker(event: MessageEvent function setupChannelToMainThread(pthread_ptr: pthreadPtr): PThreadSelf { + if (!MonoWasmThreads) return null as any; const channel = new MessageChannel(); const workerPort = channel.port1; const mainPort = channel.port2; @@ -85,13 +86,14 @@ function setupChannelToMainThread(pthread_ptr: pthreadPtr): PThreadSelf { /// Called in the worker thread (not main thread) from mono when a pthread becomes attached to the mono runtime. export function mono_wasm_pthread_on_pthread_attached(pthread_id: number): void { if (!MonoWasmThreads) return; - const self = pthread_self; - mono_assert(self !== null && self.pthreadId == pthread_id, "expected pthread_self to be set already when attaching"); - mono_set_thread_id("0x" + pthread_id.toString(16)); + mono_assert(pthread_self !== null && pthread_self.pthreadId == pthread_id, "expected pthread_self to be set already when attaching"); + const threadName = `0x${pthread_id.toString(16)}-worker`; + mono_set_thread_name(threadName); + loaderHelpers.mono_set_thread_name(threadName); preRunWorker(); set_thread_info(pthread_id, true, false, false); jiterpreter_allocate_tables(); - currentWorkerThreadEvents.dispatchEvent(makeWorkerThreadEvent(dotnetPthreadAttached, self)); + currentWorkerThreadEvents.dispatchEvent(makeWorkerThreadEvent(dotnetPthreadAttached, pthread_self)); } /// Called in the worker thread (not main thread) from mono when a pthread becomes detached from the mono runtime. @@ -99,7 +101,9 @@ export function mono_wasm_pthread_on_pthread_detached(pthread_id: number): void if (!MonoWasmThreads) return; postRunWorker(); set_thread_info(pthread_id, false, false, false); - mono_set_thread_id(""); + const threadName = `0x${pthread_id.toString(16)}-worker-detached`; + mono_set_thread_name(threadName); + loaderHelpers.mono_set_thread_name(threadName); } /// This is an implementation detail function. @@ -109,9 +113,9 @@ export function afterThreadInitTLS(): void { if (!MonoWasmThreads) return; // don't do this callback for the main thread if (ENVIRONMENT_IS_PTHREAD) { - const pthread_ptr = (Module)["_pthread_self"](); + const pthread_ptr = mono_wasm_pthread_ptr(); mono_assert(!is_nullish(pthread_ptr), "pthread_self() returned null"); - const self = setupChannelToMainThread(pthread_ptr); - currentWorkerThreadEvents.dispatchEvent(makeWorkerThreadEvent(dotnetPthreadCreated, self)); + const pthread_self = setupChannelToMainThread(pthread_ptr); + currentWorkerThreadEvents.dispatchEvent(makeWorkerThreadEvent(dotnetPthreadCreated, pthread_self)); } } diff --git a/src/mono/browser/runtime/run.ts b/src/mono/browser/runtime/run.ts index 41444a5251f8c..c41cfed8906cf 100644 --- a/src/mono/browser/runtime/run.ts +++ b/src/mono/browser/runtime/run.ts @@ -7,7 +7,7 @@ import { mono_wasm_set_main_args } from "./startup"; import cwraps from "./cwraps"; import { assembly_load } from "./class-loader"; import { mono_log_info } from "./logging"; -import { assert_bindings } from "./invoke-js"; +import { assert_js_interop } from "./invoke-js"; /** * Possible signatures are described here https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/main-command-line @@ -66,7 +66,7 @@ export async function mono_run_main(main_assembly_name: string, args?: string[]) export function find_entry_point(assembly: string) { loaderHelpers.assert_runtime_running(); - assert_bindings(); + assert_js_interop(); const asm = assembly_load(assembly); if (!asm) throw new Error("Could not find assembly: " + assembly); diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index 6ec998ccaabeb..8ba863664e9d9 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -23,12 +23,12 @@ import { replace_linker_placeholders } from "./exports-binding"; import { endMeasure, MeasuredBlock, startMeasure } from "./profiler"; import { checkMemorySnapshotSize, getMemorySnapshot, storeMemorySnapshot } from "./snapshot"; import { interp_pgo_load_data, interp_pgo_save_data } from "./interp-pgo"; -import { mono_log_debug, mono_log_error, mono_log_warn, mono_set_thread_id } from "./logging"; +import { mono_log_debug, mono_log_error, mono_log_warn, mono_set_thread_name } from "./logging"; // threads import { preAllocatePThreadWorkerPool, instantiateWasmPThreadWorkerPool } from "./pthreads/browser"; import { currentWorkerThreadEvents, dotnetPthreadCreated, initWorkerThreadEvents } from "./pthreads/worker"; -import { getBrowserThreadID } from "./pthreads/shared"; +import { mono_wasm_main_thread_ptr } from "./pthreads/shared"; import { jiterpreter_allocate_tables } from "./jiterpreter-support"; // legacy @@ -360,8 +360,9 @@ async function mono_wasm_init_threads() { if (!MonoWasmThreads) { return; } - const tid = getBrowserThreadID(); - mono_set_thread_id(`0x${tid.toString(16)}-main`); + const threadName = `0x${mono_wasm_main_thread_ptr().toString(16)}-main`; + mono_set_thread_name(threadName); + loaderHelpers.mono_set_thread_name(threadName); await instantiateWasmPThreadWorkerPool(); await mono_wasm_init_diagnostics(); } @@ -664,8 +665,8 @@ export function mono_wasm_set_main_args(name: string, allRuntimeArguments: strin /// 3. At the point when this executes there is no pthread assigned to the worker yet. export async function configureWorkerStartup(module: DotnetModuleInternal): Promise { initWorkerThreadEvents(); - currentWorkerThreadEvents.addEventListener(dotnetPthreadCreated, (ev) => { - mono_log_debug("pthread created 0x" + ev.pthread_self.pthreadId.toString(16)); + currentWorkerThreadEvents.addEventListener(dotnetPthreadCreated, () => { + // mono_log_debug("pthread created 0x" + ev.pthread_self.pthreadId.toString(16)); }); // these are the only events which are called on worker diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index 6e7b649a56c6b..45fcafbc080f9 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -142,6 +142,7 @@ export type LoaderHelpers = { mono_download_assets: () => Promise, resolve_single_asset_path: (behavior: AssetBehaviors) => AssetEntryInternal, setup_proxy_console: (id: string, console: Console, origin: string) => void + mono_set_thread_name: (tid: string) => void fetch_like: (url: string, init?: RequestInit) => Promise; locateFile: (path: string, prefix?: string) => string, out(message: string): void; diff --git a/src/mono/browser/runtime/web-socket.ts b/src/mono/browser/runtime/web-socket.ts index 41b3ca2299428..ec88b12d9ff14 100644 --- a/src/mono/browser/runtime/web-socket.ts +++ b/src/mono/browser/runtime/web-socket.ts @@ -13,7 +13,7 @@ import { mono_log_warn } from "./logging"; import { viewOrCopy, utf8ToStringRelaxed, stringToUTF8 } from "./strings"; import { IDisposable } from "./marshal"; import { wrap_as_cancelable } from "./cancelable-promise"; -import { assert_synchronization_context } from "./pthreads/shared"; +import { assert_js_interop } from "./invoke-js"; const wasm_ws_pending_send_buffer = Symbol.for("wasm ws_pending_send_buffer"); const wasm_ws_pending_send_buffer_offset = Symbol.for("wasm ws_pending_send_buffer_offset"); @@ -45,7 +45,7 @@ function verifyEnvironment() { export function ws_wasm_create(uri: string, sub_protocols: string[] | null, receive_status_ptr: VoidPtr, onClosed: (code: number, reason: string) => void): WebSocketExtension { verifyEnvironment(); - assert_synchronization_context(); + assert_js_interop(); mono_assert(uri && typeof uri === "string", () => `ERR12: Invalid uri ${typeof uri}`); mono_assert(typeof onClosed === "function", () => `ERR12: Invalid onClosed ${typeof onClosed}`); diff --git a/src/mono/sample/wasm/browser-threads-minimal/JSWebWorker.cs b/src/mono/sample/wasm/browser-threads-minimal/JSWebWorker.cs deleted file mode 100644 index c197cc266ebae..0000000000000 --- a/src/mono/sample/wasm/browser-threads-minimal/JSWebWorker.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices.JavaScript; -using System.Threading; -using System.Reflection; -using System.Threading.Tasks; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace Sample -{ - // this is just temporary thin wrapper to expose future public API - public partial class JSWebWorker - { - private static MethodInfo runAsyncMethod; - private static MethodInfo runAsyncVoidMethod; - - public static Task RunAsync(Func body) - { - return RunAsync(body, CancellationToken.None); - } - - public static Task RunAsync(Func> body) - { - return RunAsync(body, CancellationToken.None); - } - - - [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker", "System.Runtime.InteropServices.JavaScript")] - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", Justification = "work in progress")] - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern", Justification = "work in progress")] - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:UnrecognizedReflectionPattern", Justification = "work in progress")] - public static Task RunAsync(Func> body, CancellationToken cancellationToken) - { - if(runAsyncMethod == null) - { - var webWorker = typeof(JSObject).Assembly.GetType("System.Runtime.InteropServices.JavaScript.JSWebWorker"); - runAsyncMethod = webWorker.GetMethod("RunAsyncGeneric", BindingFlags.NonPublic|BindingFlags.Static); - } - - var genericRunAsyncMethod = runAsyncMethod.MakeGenericMethod(typeof(T)); - return (Task)genericRunAsyncMethod.Invoke(null, new object[] { body, cancellationToken }); - } - - [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker", "System.Runtime.InteropServices.JavaScript")] - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", Justification = "work in progress")] - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern", Justification = "work in progress")] - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:UnrecognizedReflectionPattern", Justification = "work in progress")] - public static Task RunAsync(Func body, CancellationToken cancellationToken) - { - if(runAsyncVoidMethod == null) - { - var webWorker = typeof(JSObject).Assembly.GetType("System.Runtime.InteropServices.JavaScript.JSWebWorker"); - runAsyncVoidMethod = webWorker.GetMethod("RunAsyncVoid", BindingFlags.NonPublic|BindingFlags.Static); - } - return (Task)runAsyncVoidMethod.Invoke(null, new object[] { body, cancellationToken }); - } - } -} \ No newline at end of file diff --git a/src/mono/sample/wasm/browser-threads-minimal/Makefile b/src/mono/sample/wasm/browser-threads-minimal/Makefile deleted file mode 100644 index 6026b8176cd0e..0000000000000 --- a/src/mono/sample/wasm/browser-threads-minimal/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -TOP=../../../../.. - -include ../wasm.mk - -override MSBUILD_ARGS+=/p:WasmEnableThreads=true - -ifneq ($(AOT),) -override MSBUILD_ARGS+=/p:RunAOTCompilation=true -endif - -PROJECT_NAME=Wasm.Browser.Threads.Minimal.Sample.csproj - -run: run-browser diff --git a/src/mono/sample/wasm/browser-threads-minimal/Program.cs b/src/mono/sample/wasm/browser-threads-minimal/Program.cs deleted file mode 100644 index 11e4d01ff6141..0000000000000 --- a/src/mono/sample/wasm/browser-threads-minimal/Program.cs +++ /dev/null @@ -1,445 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices.JavaScript; -using System.Threading; -using System.Reflection; -using System.Threading.Tasks; -using System.Collections.Generic; -using System.Net.Http; -using System.Net.WebSockets; - -namespace Sample -{ - public partial class Test - { - public static int Main(string[] args) - { - Console.WriteLine("Hello, World!"); - return 0; - } - - [JSExport] - public static async Task LockTest() - { - var lck=new Object(); - Console.WriteLine("LockTest A ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - Monitor.Enter(lck); - Console.WriteLine("LockTest B ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - Monitor.Exit(lck); - Console.WriteLine("LockTest C ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - - await Task.Run(() => - { - Console.WriteLine("LockTest D ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - Monitor.Enter(lck); - Console.WriteLine("LockTest E ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - Monitor.Exit(lck); - Console.WriteLine("LockTest F ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - }); - - /* deadlock - Monitor.Enter(lck); - await Task.Run(() => - { - Console.WriteLine("LockTest G ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - Monitor.Enter(lck); - Console.WriteLine("LockTest H ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - Monitor.Exit(lck); - Console.WriteLine("LockTest I ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - }); - Monitor.Exit(lck); - Console.WriteLine("LockTest J ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - */ - - /* deadlock - await Task.Run(() => - { - Console.WriteLine("LockTest K ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - Monitor.Enter(lck); - }); - Console.WriteLine("LockTest L ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - Monitor.Enter(lck); - Console.WriteLine("LockTest M ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - */ - } - - - [JSExport] - public static async Task DisposeTest() - { - Console.WriteLine("DisposeTest A ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - var test1 = JSHost.GlobalThis.GetPropertyAsJSObject("test1"); - var test2 = JSHost.GlobalThis.GetPropertyAsJSObject("test2"); - Console.WriteLine("DisposeTest 0 ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - await Task.Delay(10).ConfigureAwait(false); - Console.WriteLine("DisposeTest 1 ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - test1.Dispose(); - - Console.WriteLine("DisposeTest 2 ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - Console.WriteLine("DisposeTest 3 ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - - await Task.Run(() => - { - Console.WriteLine("DisposeTest 4 ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - test2.Dispose(); - Console.WriteLine("DisposeTest 5 ManagedThreadId: "+Thread.CurrentThread.ManagedThreadId); - }); - } - - - [JSImport("globalThis.setTimeout")] - static partial void GlobalThisSetTimeout([JSMarshalAs] Action cb, int timeoutMs); - - [JSImport("globalThis.fetch")] - private static partial Task GlobalThisFetch(string url); - - [JSImport("globalThis.console.log")] - private static partial void GlobalThisConsoleLog(string text); - - const string fetchhelper = "../fetchhelper.js"; - - [JSImport("responseText", fetchhelper)] - private static partial Task FetchHelperResponseText(JSObject response, int delayMs); - - [JSImport("delay", fetchhelper)] - private static partial Task Delay(int timeoutMs); - - [JSExport] - internal static Task TestHelloWebWorker() - { - Console.WriteLine($"smoke: TestHelloWebWorker 1 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - Task t = JSWebWorker.RunAsync(() => - { - Console.WriteLine($"smoke: TestHelloWebWorker 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - GlobalThisConsoleLog($"smoke: TestHelloWebWorker 3 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - Console.WriteLine($"smoke: TestHelloWebWorker 4 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - return Task.CompletedTask; - }); - Console.WriteLine($"smoke: TestHelloWebWorker 5 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - return t.ContinueWith(Gogo); - } - - private static void Gogo(Task t) - { - Console.WriteLine($"smoke: TestHelloWebWorker 6 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - } - - [JSExport] - public static async Task TestCanStartThread() - { - 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"); - } - - static bool _timerDone = false; - - [JSExport] - internal static void StartTimerFromWorker() - { - Console.WriteLine("smoke: StartTimerFromWorker 1 utc {0}", DateTime.UtcNow.ToUniversalTime()); - JSWebWorker.RunAsync(async () => - { - while (!_timerDone) - { - await Task.Delay(1 * 1000); - Console.WriteLine("smoke: StartTimerFromWorker 2 utc {0}", DateTime.UtcNow.ToUniversalTime()); - } - Console.WriteLine("smoke: StartTimerFromWorker done utc {0}", DateTime.UtcNow.ToUniversalTime()); - }); - } - - [JSExport] - internal static void StartAllocatorFromWorker() - { - Console.WriteLine("smoke: StartAllocatorFromWorker 1 utc {0}", DateTime.UtcNow.ToUniversalTime()); - JSWebWorker.RunAsync(async () => - { - while (!_timerDone) - { - await Task.Delay(1 * 100); - var x = new List(); - for (int i = 0; i < 1000; i++) - { - var v = new int[1000]; - v[i] = i; - x.Add(v); - } - Console.WriteLine("smoke: StartAllocatorFromWorker 2 utc {0} {1} {2}", DateTime.UtcNow.ToUniversalTime(), x[1][1], GC.GetTotalAllocatedBytes()); - } - Console.WriteLine("smoke: StartAllocatorFromWorker done utc {0}", DateTime.UtcNow.ToUniversalTime()); - }); - } - - [JSExport] - internal static void StopTimerFromWorker() - { - _timerDone = true; - } - - [JSExport] - public static async Task TestCallSetTimeoutOnWorker() - { - await JSWebWorker.RunAsync(() => TimeOutThenComplete()); - Console.WriteLine($"XYZ: Main Thread caught task tid:{Thread.CurrentThread.ManagedThreadId}"); - } - - private static async Task HttpClientGet(string name, string url) - { - Console.WriteLine($"smoke: {name} 1 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - using var client = new HttpClient(); - using var response = await client.GetAsync(url); - response.EnsureSuccessStatusCode(); - var text = await response.Content.ReadAsStringAsync(); - Console.WriteLine($"smoke: {name} 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - return text; - } - - [JSExport] - public static Task HttpClientMain(string url) - { - return HttpClientGet("HttpClientMain", url); - } - - [JSExport] - public static Task HttpClientWorker(string url) - { - return JSWebWorker.RunAsync(() => - { - return HttpClientGet("HttpClientWorker", url); - }); - } - - [JSExport] - public static Task HttpClientPool(string url) - { - return Task.Run(() => - { - return HttpClientGet("HttpClientPool", url); - }); - } - - [JSExport] - public static Task HttpClientThread(string url) - { - var tcs = new TaskCompletionSource(); - var t = new Thread(() => { - var t = HttpClientGet("HttpClientThread", url); - // this is blocking! - tcs.SetResult(t.Result); - }); - t.Start(); - return tcs.Task; - } - - private static async Task WsClientHello(string name, string url) - { - Console.WriteLine($"smoke: {name} 1 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - using var client = new ClientWebSocket (); - await client.ConnectAsync(new Uri(url), CancellationToken.None); - var message=new byte[]{0x68,0x65,0x6C,0x6C,0x6F};// hello - var body = new ReadOnlyMemory(message); - await client.SendAsync(body, WebSocketMessageType.Text, true, CancellationToken.None); - Console.WriteLine($"smoke: {name} 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - return "ok"; - } - - [JSExport] - public static Task WsClientMain(string url) - { - return WsClientHello("WsClientHello", url); - } - - [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 = JSWebWorker.RunAsync(async () => - { - var ctx = SynchronizationContext.Current; - - 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($"smoke: FetchBackground 5 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - if (ok) - { -#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; - } - Console.WriteLine($"smoke: FetchBackground 7 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - return "not-ok"; - }); - var r = await t; - Console.WriteLine($"smoke: FetchBackground thread:{Thread.CurrentThread.ManagedThreadId} background thread returned"); - return r; - } - - [ThreadStatic] - public static int meaning = 42; - - [JSExport] - public static async Task TestTLS() - { - Console.WriteLine($"smoke {meaning}: TestTLS 1 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - meaning = 40; - await JSWebWorker.RunAsync(async () => - { - Console.WriteLine($"smoke {meaning}: TestTLS 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - meaning = 41; - await JSHost.ImportAsync(fetchhelper, "../fetchhelper.js"); - Console.WriteLine($"smoke {meaning}: TestTLS 3 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - meaning = 43; - Console.WriteLine($"smoke {meaning}: TestTLS 4 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - await Delay(100); - meaning = 44; - Console.WriteLine($"smoke {meaning}: TestTLS 5 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - }); - Console.WriteLine($"smoke {meaning}: TestTLS 9 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - } - - private static async Task TimeOutThenComplete() - { - var tcs = new TaskCompletionSource(); - Console.WriteLine($"smoke: Task running tid:{Thread.CurrentThread.ManagedThreadId}"); - GlobalThisSetTimeout(() => - { - tcs.SetResult(); - Console.WriteLine($"smoke: Timeout fired tid:{Thread.CurrentThread.ManagedThreadId}"); - }, 250); - Console.WriteLine($"smoke: Task sleeping tid:{Thread.CurrentThread.ManagedThreadId}"); - await tcs.Task; - Console.WriteLine($"smoke: Task resumed tid:{Thread.CurrentThread.ManagedThreadId}"); - } - - [JSExport] - public static async Task RunBackgroundThreadCompute() - { - var tcs = new TaskCompletionSource(); - var t = new Thread(() => - { - var n = CountingCollatzTest(); - tcs.SetResult(n); - }); - t.Start(); - return await tcs.Task; - } - - [JSExport] - public static async Task RunBackgroundLongRunningTaskCompute() - { - var factory = new TaskFactory(); - var t = factory.StartNew(() => - { - var n = CountingCollatzTest(); - return n; - }, TaskCreationOptions.LongRunning); - return await t; - } - - [JSExport] - public static async Task RunBackgroundTaskRunCompute() - { - var t1 = Task.Run(() => - { - var n = CountingCollatzTest(); - return n; - }); - var t2 = Task.Run(() => - { - var n = CountingCollatzTest(); - return n; - }); - var rs = await Task.WhenAll(new[] { t1, t2 }); - if (rs[0] != rs[1]) - throw new Exception($"Results from two tasks {rs[0]}, {rs[1]}, differ"); - return rs[0]; - } - - [JSExport] - internal static void GCCollect() - { - GC.Collect(); - GC.WaitForPendingFinalizers(); - } - - - public static int CountingCollatzTest() - { - const int limit = 5000; - const int maxInput = 200_000; - int bigly = 0; - int hugely = 0; - int maxSteps = 0; - for (int n = 1; n < maxInput; n++) - { - int steps = CountingCollatz((long)n, limit); - if (steps > maxSteps) - maxSteps = steps; - if (steps > 120) - bigly++; - if (steps >= limit) - hugely++; - } - - Console.WriteLine($"Bigly: {bigly}, Hugely: {hugely}, maxSteps: {maxSteps}"); - - if (bigly == 86187 && hugely == 0 && maxSteps == 382) - return 524; - else - return 0; - } - - - private static int CountingCollatz(long n, int limit) - { - int steps = 0; - while (n > 1) - { - n = Collatz1(n); - steps++; - if (steps >= limit) - break; - } - return steps; - } - - private static long Collatz1(long n) - { - if (n <= 0) - throw new Exception("Unexpected non-positive input"); - if (n % 2 == 0) - return n / 2; - else - return 3 * n + 1; - } - } -} diff --git a/src/mono/sample/wasm/browser-threads-minimal/Wasm.Browser.Threads.Minimal.Sample.csproj b/src/mono/sample/wasm/browser-threads-minimal/Wasm.Browser.Threads.Minimal.Sample.csproj deleted file mode 100644 index bd8644d2fefd3..0000000000000 --- a/src/mono/sample/wasm/browser-threads-minimal/Wasm.Browser.Threads.Minimal.Sample.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - true - - - - - - - - - - - - - - - - - - - diff --git a/src/mono/sample/wasm/browser-threads-minimal/blurst.txt b/src/mono/sample/wasm/browser-threads-minimal/blurst.txt deleted file mode 100644 index 6679d914da1c7..0000000000000 --- a/src/mono/sample/wasm/browser-threads-minimal/blurst.txt +++ /dev/null @@ -1 +0,0 @@ -It was the best of times, it was the blurst of times. diff --git a/src/mono/sample/wasm/browser-threads-minimal/fetchhelper.js b/src/mono/sample/wasm/browser-threads-minimal/fetchhelper.js deleted file mode 100644 index 74b168a8a8bd7..0000000000000 --- a/src/mono/sample/wasm/browser-threads-minimal/fetchhelper.js +++ /dev/null @@ -1,11 +0,0 @@ - -export function delay(timeoutMs) { - return new Promise(resolve => setTimeout(resolve, timeoutMs)); -} - -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(); -} diff --git a/src/mono/sample/wasm/browser-threads-minimal/index.html b/src/mono/sample/wasm/browser-threads-minimal/index.html deleted file mode 100644 index 7a8c2e0d8c3d9..0000000000000 --- a/src/mono/sample/wasm/browser-threads-minimal/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - Wasm Browser Smoke Test Threading Sample - - - - - - - - This calculation will take a long time: - - - diff --git a/src/mono/sample/wasm/browser-threads-minimal/main.js b/src/mono/sample/wasm/browser-threads-minimal/main.js deleted file mode 100644 index 329a45aa99b42..0000000000000 --- a/src/mono/sample/wasm/browser-threads-minimal/main.js +++ /dev/null @@ -1,134 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import { dotnet, exit } from './_framework/dotnet.js' - -const assemblyName = "Wasm.Browser.Threads.Minimal.Sample.dll"; - - -try { - const resolveUrl = (relativeUrl) => (new URL(relativeUrl, window.location.href)).toString() - - const { getAssemblyExports, runMain } = await dotnet - //.withEnvironmentVariable("MONO_LOG_LEVEL", "debug") - //.withDiagnosticTracing(true) - .withElementOnExit() - .withExitCodeLogging() - .create(); - - globalThis.test1 = { a: 1 }; - globalThis.test2 = { a: 2 }; - - const exports = await getAssemblyExports(assemblyName); - - console.log("smoke: running LockTest"); - await exports.Sample.Test.LockTest(); - console.log("smoke: LockTest done "); - - - console.log("smoke: running DisposeTest"); - await exports.Sample.Test.DisposeTest(); - console.log("smoke: DisposeTest done "); - - console.log("smoke: running TestHelloWebWorker"); - await exports.Sample.Test.TestHelloWebWorker(); - await exports.Sample.Test.TestHelloWebWorker(); - await exports.Sample.Test.TestHelloWebWorker(); - await exports.Sample.Test.TestHelloWebWorker(); - await exports.Sample.Test.TestHelloWebWorker(); - await exports.Sample.Test.TestHelloWebWorker(); - await exports.Sample.Test.TestHelloWebWorker(); - await exports.Sample.Test.TestHelloWebWorker(); - console.log("smoke: TestHelloWebWorker done"); - - console.log("smoke: running TestCanStartThread"); - await exports.Sample.Test.TestCanStartThread(); - console.log("smoke: TestCanStartThread done"); - - console.log("smoke: running TestTLS"); - await exports.Sample.Test.TestTLS(); - console.log("smoke: TestTLS done"); - - console.log("smoke: running StartTimerFromWorker"); - exports.Sample.Test.StartTimerFromWorker(); - - console.log("smoke: running TestCallSetTimeoutOnWorker"); - await exports.Sample.Test.TestCallSetTimeoutOnWorker(); - console.log("smoke: TestCallSetTimeoutOnWorker done"); - - console.log("smoke: running HttpClientMain(blurst.txt)"); - let t = await exports.Sample.Test.HttpClientMain(globalThis.document.baseURI + "blurst.txt"); - console.log("smoke: HttpClientMain(blurst.txt) done " + t); - - console.log("smoke: running HttpClientWorker(blurst.txt)"); - let t2 = await exports.Sample.Test.HttpClientWorker(globalThis.document.baseURI + "blurst.txt"); - console.log("smoke: HttpClientWorker(blurst.txt) done " + t2); - - console.log("smoke: running HttpClientPool(blurst.txt)"); - let t3 = await exports.Sample.Test.HttpClientPool(globalThis.document.baseURI + "blurst.txt"); - console.log("smoke: HttpClientPool(blurst.txt) done " + t3); - - console.log("smoke: running HttpClientThread(blurst.txt)"); - let t4 = await exports.Sample.Test.HttpClientThread(globalThis.document.baseURI + "blurst.txt"); - console.log("smoke: HttpClientThread(blurst.txt) done " + t4); - - console.log("smoke: running WsClientMain"); - let w0 = await exports.Sample.Test.WsClientMain("wss://corefx-net-http11.azurewebsites.net/WebSocket/EchoWebSocket.ashx"); - console.log("smoke: WsClientMain done " + w0); - - console.log("smoke: running FetchBackground(blurst.txt)"); - let s = await exports.Sample.Test.FetchBackground(resolveUrl("./blurst.txt")); - 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); - } - - console.log("smoke: running FetchBackground(missing)"); - s = await exports.Sample.Test.FetchBackground(resolveUrl("./missing.txt")); - 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); - } - - 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: running StartAllocatorFromWorker"); - exports.Sample.Test.StartAllocatorFromWorker(); - - /* ActiveIssue https://github.com/dotnet/runtime/issues/88663 - await delay(5000); - - console.log("smoke: running GCCollect"); - exports.Sample.Test.GCCollect(); - - await delay(5000); - - console.log("smoke: running GCCollect"); - exports.Sample.Test.GCCollect(); - console.log("smoke: running GCCollect done"); - */ - - console.log("smoke: running StopTimerFromWorker"); - exports.Sample.Test.StopTimerFromWorker(); - - let exit_code = await runMain(assemblyName, []); - exit(exit_code); -} catch (err) { - exit(2, err); -} - -function delay(timeoutMs) { - return new Promise(resolve => setTimeout(resolve, timeoutMs)); -} -