diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs index 03c19b3d03e0e..edf075ab2867d 100644 --- a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs +++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs @@ -22,7 +22,7 @@ internal static unsafe partial class Runtime [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 MarshalPromise(void* data); [MethodImpl(MethodImplOptions.InternalCall)] public static extern IntPtr RegisterGCRoot(IntPtr start, int bytesSize, IntPtr name); [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/System.Runtime.InteropServices.JavaScript.sln b/src/libraries/System.Runtime.InteropServices.JavaScript/System.Runtime.InteropServices.JavaScript.sln index b0c228723bac4..0f5bce54f6882 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/System.Runtime.InteropServices.JavaScript.sln +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/System.Runtime.InteropServices.JavaScript.sln @@ -43,11 +43,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{F2C2C78A-CED EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7973EAA3-43B6-4D78-B24C-38BA6BC0D1E3}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{569E6837-0771-4C08-BB09-460281030538}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "tools\gen", "{569E6837-0771-4C08-BB09-460281030538}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{67F3A00A-AE6C-434C-927D-E5D38DE2DA2C}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "tools\src", "{67F3A00A-AE6C-434C-927D-E5D38DE2DA2C}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "tools\ref", "{1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{F92020A9-28BE-4398-86FF-5CFE44C94882}" EndProject @@ -135,32 +135,28 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {ED86AB26-1CFB-457D-BF87-B7A0D8FAF272} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} - {8B1D80E9-AE0D-4E3C-9F91-E6862B49AEF3} = {C22C479B-769A-4859-B974-E9B9D65918DE} - {CE5E53C1-F9B5-41EE-8D00-837913EC57D1} = {F2C2C78A-CEDD-4DE0-9C3A-0195F00E0B4E} - {28278E01-BF5C-4AB6-AA7A-8DD4E6C04DB1} = {C22C479B-769A-4859-B974-E9B9D65918DE} - {71A845ED-4344-41FC-8FCA-3AC9B6BA6C45} = {7973EAA3-43B6-4D78-B24C-38BA6BC0D1E3} {BFED925C-18F2-4C98-833E-66F205234598} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} {ABA5A92B-CAD8-47E8-A7CE-D28A67FB69C0} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} {765B4AA5-723A-44FF-BC4E-EB0F03103F6D} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} + {EC3ADEFA-1FF3-482C-8CCB-FE4C77292532} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} + {8B1D80E9-AE0D-4E3C-9F91-E6862B49AEF3} = {C22C479B-769A-4859-B974-E9B9D65918DE} + {28278E01-BF5C-4AB6-AA7A-8DD4E6C04DB1} = {C22C479B-769A-4859-B974-E9B9D65918DE} + {44BAE6F1-94C2-415B-9A16-3B8EC429B09B} = {C22C479B-769A-4859-B974-E9B9D65918DE} + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1} = {F2C2C78A-CEDD-4DE0-9C3A-0195F00E0B4E} {FB12C247-AFEF-4772-BB0C-983969B6CF32} = {F2C2C78A-CEDD-4DE0-9C3A-0195F00E0B4E} {09AA6758-0BD3-4312-9C07-AE9F1D50A3AD} = {F2C2C78A-CEDD-4DE0-9C3A-0195F00E0B4E} {B4E3E774-2C16-4CBF-87EF-88C547529B94} = {F2C2C78A-CEDD-4DE0-9C3A-0195F00E0B4E} - {EC3ADEFA-1FF3-482C-8CCB-FE4C77292532} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} - {44BAE6F1-94C2-415B-9A16-3B8EC429B09B} = {C22C479B-769A-4859-B974-E9B9D65918DE} + {71A845ED-4344-41FC-8FCA-3AC9B6BA6C45} = {7973EAA3-43B6-4D78-B24C-38BA6BC0D1E3} {4D8B7538-D933-4F3A-818D-4E19ABA7E182} = {569E6837-0771-4C08-BB09-460281030538} {6C60944F-4FE1-450F-884B-D523EDFCFAB3} = {569E6837-0771-4C08-BB09-460281030538} + {569E6837-0771-4C08-BB09-460281030538} = {F92020A9-28BE-4398-86FF-5CFE44C94882} {B8F2E56D-6571-466D-9EF2-9FCAD3FC6E5B} = {67F3A00A-AE6C-434C-927D-E5D38DE2DA2C} {42F9A600-BEC3-4F87-97EE-38E0DCAABC5A} = {67F3A00A-AE6C-434C-927D-E5D38DE2DA2C} - {008873D5-9028-4FF3-8354-71F713748625} = {1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF} - {569E6837-0771-4C08-BB09-460281030538} = {F92020A9-28BE-4398-86FF-5CFE44C94882} {67F3A00A-AE6C-434C-927D-E5D38DE2DA2C} = {F92020A9-28BE-4398-86FF-5CFE44C94882} + {008873D5-9028-4FF3-8354-71F713748625} = {1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF} {1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF} = {F92020A9-28BE-4398-86FF-5CFE44C94882} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3FE64246-4AFA-424A-AE5D-7007E20451B5} EndGlobalSection - GlobalSection(SharedMSBuildProjectFiles) = preSolution - ..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems*{42f9a600-bec3-4f87-97ee-38e0dcaabc5a}*SharedItemsImports = 5 - ..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems*{6c60944f-4fe1-450f-884b-d523edfcfab3}*SharedItemsImports = 5 - EndGlobalSection EndGlobal diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/Resources/Strings.resx b/src/libraries/System.Runtime.InteropServices.JavaScript/src/Resources/Strings.resx index 9cb71cc66223a..dc490f9911b2e 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/Resources/Strings.resx +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/Resources/Strings.resx @@ -147,8 +147,8 @@ ToManagedCallback is null. - - PromiseHolder is null. + + TaskCallback is null. Empty profile data. @@ -162,8 +162,8 @@ Failed to marshal exception. - - Failed to marshal Promise callback. + + Failed to marshal Task callback. Invalid InFlightCounter for JSObject {0}, expected: {1}, actual: {2}. 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 3f3573f5f26c1..7a8a0aa1f2d9f 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 @@ -9,7 +9,7 @@ namespace System.Runtime.InteropServices.JavaScript public static partial class CancelablePromise { [JSImport("INTERNAL.mono_wasm_cancel_promise")] - private static partial void _CancelPromise(IntPtr gcvHandle); + private static partial void _CancelPromise(IntPtr promiseGCHandle); public static void CancelPromise(Task promise) { @@ -18,15 +18,15 @@ public static void CancelPromise(Task promise) { return; } - JSHostImplementation.PromiseHolder? holder = promise.AsyncState as JSHostImplementation.PromiseHolder; + JSHostImplementation.TaskCallback? holder = promise.AsyncState as JSHostImplementation.TaskCallback; if (holder == null) throw new InvalidOperationException("Expected Task converted from JS Promise"); #if FEATURE_WASM_THREADS - holder.SynchronizationContext!.Send(static (JSHostImplementation.PromiseHolder holder) => + holder.SynchronizationContext!.Send(static (JSHostImplementation.TaskCallback holder) => { #endif - _CancelPromise(holder.GCVHandle); + _CancelPromise(holder.GCHandle); #if FEATURE_WASM_THREADS }, holder); #endif @@ -39,15 +39,15 @@ public static void CancelPromise(Task promise, Action callback, T state) { return; } - JSHostImplementation.PromiseHolder? holder = promise.AsyncState as JSHostImplementation.PromiseHolder; + JSHostImplementation.TaskCallback? holder = promise.AsyncState as JSHostImplementation.TaskCallback; if (holder == null) throw new InvalidOperationException("Expected Task converted from JS Promise"); #if FEATURE_WASM_THREADS - holder.SynchronizationContext!.Send((JSHostImplementation.PromiseHolder holder) => + holder.SynchronizationContext!.Send((JSHostImplementation.TaskCallback holder) => { #endif - _CancelPromise(holder.GCVHandle); + _CancelPromise(holder.GCHandle); callback.Invoke(state); #if FEATURE_WASM_THREADS }, holder); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs index 7c0a18dba1648..20c0279d9ff1e 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using System.Diagnostics.CodeAnalysis; using System.Threading; -using static System.Runtime.InteropServices.JavaScript.JSHostImplementation; namespace System.Runtime.InteropServices.JavaScript { @@ -32,7 +31,7 @@ public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer) throw new MissingMethodException(SR.MissingManagedEntrypointHandle); } - RuntimeMethodHandle methodHandle = GetMethodHandleFromIntPtr(entrypointPtr); + RuntimeMethodHandle methodHandle = JSHostImplementation.GetMethodHandleFromIntPtr(entrypointPtr); // this would not work for generic types. But Main() could not be generic, so we are fine. MethodInfo? method = MethodBase.GetMethodFromHandle(methodHandle) as MethodInfo; if (method == null) @@ -142,18 +141,32 @@ public static void ReleaseJSOwnedObjectByGCHandle(JSMarshalerArgument* arguments ref JSMarshalerArgument arg_1 = ref arguments_buffer[2]; // initialized and set by caller try { - var gcHandle = arg_1.slot.GCHandle; - if (IsGCVHandle(gcHandle) && ThreadJsOwnedHolders.Remove(gcHandle, out PromiseHolder? holder)) - { - holder.GCVHandle = IntPtr.Zero; - holder.Callback!(null); - } - else - { - GCHandle handle = (GCHandle)gcHandle; - ThreadJsOwnedObjects.Remove(handle.Target!); - handle.Free(); - } + GCHandle handle = (GCHandle)arg_1.slot.GCHandle; + + JSHostImplementation.ThreadJsOwnedObjects.Remove(handle.Target!); + handle.Free(); + } + catch (Exception ex) + { + arg_exc.ToJS(ex); + } + } + + // the marshaled signature is: + // GCHandle CreateTaskCallback() + public static void CreateTaskCallback(JSMarshalerArgument* arguments_buffer) + { + ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame() + ref JSMarshalerArgument arg_return = ref arguments_buffer[1]; // used as return value + try + { + JSHostImplementation.TaskCallback holder = new JSHostImplementation.TaskCallback(); +#if FEATURE_WASM_THREADS + holder.OwnerThreadId = Thread.CurrentThread.ManagedThreadId; + holder.SynchronizationContext = SynchronizationContext.Current ?? new SynchronizationContext(); +#endif + arg_return.slot.Type = MarshalerType.Object; + arg_return.slot.GCHandle = holder.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(holder); } catch (Exception ex) { @@ -174,7 +187,7 @@ public static void CallDelegate(JSMarshalerArgument* arguments_buffer) try { GCHandle callback_gc_handle = (GCHandle)arg_1.slot.GCHandle; - if (callback_gc_handle.Target is ToManagedCallback callback) + if (callback_gc_handle.Target is JSHostImplementation.ToManagedCallback callback) { // arg_2, arg_3, arg_4, arg_res are processed by the callback callback(arguments_buffer); @@ -191,7 +204,7 @@ public static void CallDelegate(JSMarshalerArgument* arguments_buffer) } // the marshaled signature is: - // void CompleteTask(GCVHandle holder, Exception? exceptionResult, T? result) + // void CompleteTask(GCHandle holder, Exception? exceptionResult, T? result) public static void CompleteTask(JSMarshalerArgument* arguments_buffer) { ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame() @@ -200,17 +213,15 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer) // arg_3 set by caller when this is SetResult call try { - var callback_gcv_handle = arg_1.slot.GCHandle; - if (ThreadJsOwnedHolders.Remove(callback_gcv_handle, out PromiseHolder? promiseHolder) && promiseHolder.Callback != null) + GCHandle callback_gc_handle = (GCHandle)arg_1.slot.GCHandle; + if (callback_gc_handle.Target is JSHostImplementation.TaskCallback holder && holder.Callback is not null) { - promiseHolder.GCVHandle = IntPtr.Zero; - // arg_2, arg_3 are processed by the callback - promiseHolder.Callback(arguments_buffer); + holder.Callback(arguments_buffer); } else { - throw new InvalidOperationException(SR.NullPromiseHolder); + throw new InvalidOperationException(SR.NullTaskCallback); } } catch (Exception ex) @@ -253,7 +264,7 @@ public static void InstallSynchronizationContext (JSMarshalerArgument* arguments ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame() try { - InstallWebWorkerInterop(true, true); + JSHostImplementation.InstallWebWorkerInterop(true, true); } catch (Exception ex) { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.cs index 4ebb8a772e236..79805b1b2df02 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.cs @@ -7,11 +7,11 @@ namespace System.Runtime.InteropServices.JavaScript { internal static unsafe partial class JavaScriptImports { - public static void ResolveOrRejectPromise(Span arguments) + public static void MarshalPromise(Span arguments) { fixed (JSMarshalerArgument* ptr = arguments) { - Interop.Runtime.ResolveOrRejectPromise(ptr); + Interop.Runtime.MarshalPromise(ptr); ref JSMarshalerArgument exceptionArg = ref arguments[0]; if (exceptionArg.slot.Type != MarshalerType.None) { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/LegacyExports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/LegacyExports.cs index bb4b017ef743f..c15b81826b0fb 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/LegacyExports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/LegacyExports.cs @@ -32,7 +32,7 @@ internal static void PreventTrimming() public static void GetCSOwnedObjectByJSHandleRef(nint jsHandle, int shouldAddInflight, out JSObject? result) { - if (JSHostImplementation.ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference? reference)) + if (JSHostImplementation.ThreadCsOwnedObjects.TryGetValue((int)jsHandle, out WeakReference? reference)) { reference.TryGetTarget(out JSObject? jsObject); if (shouldAddInflight != 0) @@ -74,7 +74,7 @@ public static void CreateCSOwnedProxyRef(nint jsHandle, LegacyHostImplementation JSObject? res = null; - if (!JSHostImplementation.ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference? reference) || + if (!JSHostImplementation.ThreadCsOwnedObjects.TryGetValue((int)jsHandle, out WeakReference? reference) || !reference.TryGetTarget(out res) || res.IsDisposed) { @@ -90,7 +90,7 @@ public static void CreateCSOwnedProxyRef(nint jsHandle, LegacyHostImplementation _ => throw new ArgumentOutOfRangeException(nameof(mappedType)) }; #pragma warning restore CS0612 // Type or member is obsolete - JSHostImplementation.ThreadCsOwnedObjects[jsHandle] = new WeakReference(res, trackResurrection: true); + JSHostImplementation.ThreadCsOwnedObjects[(int)jsHandle] = new WeakReference(res, trackResurrection: true); } if (shouldAddInflight != 0) { 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 3e4fe41acc053..d5b0036369775 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 @@ -42,28 +42,13 @@ internal struct JSBindingHeader internal struct JSBindingType { internal MarshalerType Type; - internal MarshalerType __ReservedB1; - internal MarshalerType __ReservedB2; - internal MarshalerType __ReservedB3; internal IntPtr __Reserved; internal IntPtr JSCustomMarshallerCode; internal int JSCustomMarshallerCodeLength; internal MarshalerType ResultMarshalerType; - internal MarshalerType __ReservedB4; - internal MarshalerType __ReservedB5; - internal MarshalerType __ReservedB6; internal MarshalerType Arg1MarshalerType; - internal MarshalerType __ReservedB7; - internal MarshalerType __ReservedB8; - internal MarshalerType __ReservedB9; internal MarshalerType Arg2MarshalerType; - internal MarshalerType __ReservedB10; - internal MarshalerType __ReservedB11; - internal MarshalerType __ReservedB12; internal MarshalerType Arg3MarshalerType; - internal MarshalerType __ReservedB13; - internal MarshalerType __ReservedB14; - internal MarshalerType __ReservedB15; } internal unsafe int ArgumentCount diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Types.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Types.cs index 1cf4646569ed3..0158d24499dba 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Types.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Types.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Threading; -using System.Threading.Tasks; namespace System.Runtime.InteropServices.JavaScript { @@ -10,24 +9,15 @@ internal static partial class JSHostImplementation { internal unsafe delegate void ToManagedCallback(JSMarshalerArgument* arguments_buffer); - public sealed class PromiseHolder + public sealed class TaskCallback { - public nint GCVHandle; + public nint GCHandle; public ToManagedCallback? Callback; #if FEATURE_WASM_THREADS // the JavaScript object could only exist on the single web worker and can't migrate to other workers internal int OwnerThreadId; internal SynchronizationContext? SynchronizationContext; #endif - - public PromiseHolder(nint gcvHandle) - { - this.GCVHandle = gcvHandle; -#if FEATURE_WASM_THREADS - this.OwnerThreadId = Thread.CurrentThread.ManagedThreadId; - this.SynchronizationContext = SynchronizationContext.Current ?? new SynchronizationContext(); -#endif - } } [StructLayout(LayoutKind.Explicit)] 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 cfc4f7930d580..eaaf04b31d28e 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 @@ -20,9 +20,9 @@ internal static partial class JSHostImplementation #if FEATURE_WASM_THREADS [ThreadStatic] #endif - private static Dictionary>? s_csOwnedObjects; + private static Dictionary>? s_csOwnedObjects; - public static Dictionary> ThreadCsOwnedObjects + public static Dictionary> ThreadCsOwnedObjects { get { @@ -35,78 +35,17 @@ public static Dictionary> ThreadCsOwnedObjects #if FEATURE_WASM_THREADS [ThreadStatic] #endif - private static Dictionary? s_jsOwnedObjects; + private static Dictionary? s_jsOwnedObjects; - public static Dictionary ThreadJsOwnedObjects + public static Dictionary ThreadJsOwnedObjects { get { - s_jsOwnedObjects ??= new Dictionary(ReferenceEqualityComparer.Instance); + s_jsOwnedObjects ??= new Dictionary(ReferenceEqualityComparer.Instance); return s_jsOwnedObjects; } } - // this is similar to GCHandle, but the GCVHandle is allocated on JS side and this keeps the C# proxy alive -#if FEATURE_WASM_THREADS - [ThreadStatic] -#endif - private static Dictionary? s_jsOwnedHolders; - - public static Dictionary ThreadJsOwnedHolders - { - get - { - s_jsOwnedHolders ??= new Dictionary(); - return s_jsOwnedHolders; - } - } - - // JSVHandle is like JSHandle, but it's not tracked and allocated by the JS side - // It's used when we need to create JSHandle-like identity ahead of time, before calling JS. - // they have negative values, so that they don't collide with JSHandles. -#if FEATURE_WASM_THREADS - [ThreadStatic] -#endif - public static nint NextJSVHandle; - -#if FEATURE_WASM_THREADS - [ThreadStatic] -#endif - private static List? s_JSVHandleFreeList; - public static List JSVHandleFreeList - { - get - { - s_JSVHandleFreeList ??= new(); - return s_JSVHandleFreeList; - } - } - - public static nint AllocJSVHandle() - { - if (JSVHandleFreeList.Count > 0) - { - var jsvHandle = JSVHandleFreeList[JSVHandleFreeList.Count]; - JSVHandleFreeList.RemoveAt(JSVHandleFreeList.Count - 1); - return jsvHandle; - } - if (NextJSVHandle == IntPtr.Zero) - { - NextJSVHandle = -2; - } - return NextJSVHandle--; - } - - public static void FreeJSVHandle(nint jsvHandle) - { - JSVHandleFreeList.Add(jsvHandle); - } - - public static bool IsGCVHandle(nint gcHandle) - { - return gcHandle < -1; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReleaseCSOwnedObject(nint jsHandle) { @@ -115,24 +54,17 @@ public static void ReleaseCSOwnedObject(nint jsHandle) #if FEATURE_WASM_THREADS JSSynchronizationContext.AssertWebWorkerContext(); #endif - ThreadCsOwnedObjects.Remove(jsHandle); + ThreadCsOwnedObjects.Remove((int)jsHandle); Interop.Runtime.ReleaseCSOwnedObject(jsHandle); } } - public static bool GetTaskResultDynamic(Task task, out object? value) + public static object? GetTaskResultDynamic(Task task) { - var type = task.GetType(); - if (type == typeof(Task)) - { - value = null; - return false; - } - MethodInfo method = GetTaskResultMethodInfo(type); + MethodInfo method = GetTaskResultMethodInfo(task.GetType()); if (method != null) { - value = method.Invoke(task, null); - return true; + return method.Invoke(task, null); } throw new InvalidOperationException(); } @@ -250,7 +182,7 @@ public static unsafe JSFunctionBinding GetMethodSignature(ReadOnlySpan? reference) || + if (!ThreadCsOwnedObjects.TryGetValue((int)jsHandle, out WeakReference? reference) || !reference.TryGetTarget(out res) || res.IsDisposed) { res = new JSObject(jsHandle); - ThreadCsOwnedObjects[jsHandle] = new WeakReference(res, trackResurrection: true); + ThreadCsOwnedObjects[(int)jsHandle] = new WeakReference(res, trackResurrection: true); } return res; } @@ -347,7 +279,7 @@ public static void UninstallWebWorkerInterop() JSSynchronizationContext.CurrentJSSynchronizationContext = null; ctx.isDisposed = true; } - catch (Exception ex) + catch(Exception ex) { Environment.FailFast($"Unexpected error in UninstallWebWorkerInterop, ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}. " + ex); } @@ -373,17 +305,19 @@ public static void UninstallWebWorkerInterop() foreach (var gch in ThreadJsOwnedObjects.Values) { GCHandle gcHandle = (GCHandle)gch; - gcHandle.Free(); - } - foreach (var holder in ThreadJsOwnedHolders.Values) - { - unsafe + + // if this is pending promise we reject it + if (gcHandle.Target is TaskCallback holder) { - holder.Callback!.Invoke(null); + unsafe + { + holder.Callback!.Invoke(null); + } } + gcHandle.Free(); } } - catch (Exception ex) + catch(Exception ex) { Environment.FailFast($"Unexpected error in UninstallWebWorkerInterop, ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}. " + ex); } @@ -391,8 +325,6 @@ public static void UninstallWebWorkerInterop() ThreadCsOwnedObjects.Clear(); ThreadJsOwnedObjects.Clear(); - JSVHandleFreeList.Clear(); - NextJSVHandle = IntPtr.Zero; } [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "external_eventloop")] diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSMarshalerArgument.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSMarshalerArgument.cs index 38bc3e23ffc10..19fb096aebae3 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSMarshalerArgument.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSMarshalerArgument.cs @@ -44,17 +44,17 @@ internal struct JSMarshalerArgumentImpl internal IntPtr JSHandle; [FieldOffset(4)] internal IntPtr GCHandle; + [FieldOffset(4)] + internal MarshalerType ElementType; [FieldOffset(8)] internal int Length; /// - /// Discriminators + /// Discriminator /// [FieldOffset(12)] internal MarshalerType Type; - [FieldOffset(13)] - internal MarshalerType ElementType; } /// diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs index 97b6fcae46e42..69ec3d7701ba9 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs @@ -93,20 +93,27 @@ internal static void AssertThreadAffinity(object value) { return; } - else if (value is JSObject jsObject) + if (value is JSObject jsObject) { if (jsObject.OwnerThreadId != Thread.CurrentThread.ManagedThreadId) { throw new InvalidOperationException("The JavaScript object can be used only on the thread where it was created."); } } - else if (value is JSException jsException) + if (value is JSException jsException) { if (jsException.jsException != null && jsException.jsException.OwnerThreadId != Thread.CurrentThread.ManagedThreadId) { throw new InvalidOperationException("The JavaScript object can be used only on the thread where it was created."); } } + if (value is JSHostImplementation.TaskCallback holder) + { + if (holder.OwnerThreadId != Thread.CurrentThread.ManagedThreadId) + { + throw new InvalidOperationException("The JavaScript promise can be used only on the thread where it was created."); + } + } } #endif @@ -119,13 +126,6 @@ internal static void AssertThreadAffinity(object value) /// public override string ToString() => $"(js-obj js '{JSHandle}')"; - internal void DisposeLocal() - { - JSHostImplementation.ThreadCsOwnedObjects.Remove(JSHandle); - _isDisposed = true; - JSHandle = IntPtr.Zero; - } - private void DisposeThis() { if (!_isDisposed) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/MarshalerType.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/MarshalerType.cs index 7a2359b74073a..4e245866c0e64 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/MarshalerType.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/MarshalerType.cs @@ -5,7 +5,7 @@ namespace System.Runtime.InteropServices.JavaScript { // maps to names of System.Runtime.InteropServices.JavaScript.JSMarshalerType // please sync with src\mono\wasm\runtime\marshal.ts - internal enum MarshalerType : byte + internal enum MarshalerType : int { None = 0, Void = 1, @@ -38,8 +38,6 @@ internal enum MarshalerType : byte #if !JSIMPORTGENERATOR // only on runtime JSException, - TaskResolved, - TaskRejected, #endif } } 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 fc40f481a098f..cb32c2da0450d 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 @@ -91,7 +91,7 @@ public unsafe void ToManaged(out object? value) throw new NotSupportedException(SR.Format(SR.ToManagedNotImplemented, slot.ElementType+ "[]")); } } - else if (slot.Type == MarshalerType.Task || slot.Type == MarshalerType.TaskResolved || slot.Type == MarshalerType.TaskRejected) + else if (slot.Type == MarshalerType.Task) { ToManaged(out Task? val, static (ref JSMarshalerArgument arg, out object? value) => { @@ -277,6 +277,7 @@ public void ToJS(object? value) { byte[] val = (byte[])value; ToJS(val); + slot.ElementType = MarshalerType.Byte; } else if (typeof(int[]) == type) { 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 78f40a84a1019..e5fa56a11df87 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 @@ -4,7 +4,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; -using static System.Runtime.InteropServices.JavaScript.JSHostImplementation; namespace System.Runtime.InteropServices.JavaScript { @@ -37,15 +36,18 @@ public partial struct JSMarshalerArgument /// The value to be marshaled. public unsafe void ToManaged(out Task? value) { - // there is no nice way in JS how to check that JS promise is already resolved, to send MarshalerType.TaskRejected, MarshalerType.TaskResolved if (slot.Type == MarshalerType.None) { value = null; return; } - PromiseHolder holder = CreateJSOwnedHolder(slot.GCHandle); + + GCHandle gcHandle = (GCHandle)slot.GCHandle; + JSHostImplementation.TaskCallback? holder = (JSHostImplementation.TaskCallback?)gcHandle.Target; + if (holder == null) throw new InvalidOperationException(SR.FailedToMarshalTaskCallback); + TaskCompletionSource tcs = new TaskCompletionSource(holder); - ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) => + JSHostImplementation.ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) => { if (arguments_buffer == null) { @@ -78,15 +80,18 @@ public unsafe void ToManaged(out Task? value) /// Type of marshaled result of the . public unsafe void ToManaged(out Task? value, ArgumentToManagedCallback marshaler) { - // there is no nice way in JS how to check that JS promise is already resolved, to send MarshalerType.TaskRejected, MarshalerType.TaskResolved if (slot.Type == MarshalerType.None) { value = null; return; } - PromiseHolder holder = CreateJSOwnedHolder(slot.GCHandle); + + GCHandle gcHandle = (GCHandle)slot.GCHandle; + JSHostImplementation.TaskCallback? holder = (JSHostImplementation.TaskCallback?)gcHandle.Target; + if (holder == null) throw new InvalidOperationException(SR.FailedToMarshalTaskCallback); + TaskCompletionSource tcs = new TaskCompletionSource(holder); - ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) => + JSHostImplementation.ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) => { if (arguments_buffer == null) { @@ -113,18 +118,6 @@ public unsafe void ToManaged(out Task? value, ArgumentToManagedCallback value = tcs.Task; } - // TODO unregister and collect pending PromiseHolder also when no C# is awaiting ? - private static PromiseHolder CreateJSOwnedHolder(nint gcvHandle) - { -#if FEATURE_WASM_THREADS - JSSynchronizationContext.AssertWebWorkerContext(); -#endif - var holder = new PromiseHolder(gcvHandle); - ThreadJsOwnedHolders.Add(gcvHandle, holder); - return holder; - } - - internal void ToJSDynamic(Task? value) { Task? task = value; @@ -134,37 +127,28 @@ internal void ToJSDynamic(Task? value) slot.Type = MarshalerType.None; return; } + slot.Type = MarshalerType.Task; if (task.IsCompleted) { if (task.Exception != null) { Exception ex = task.Exception; - ToJS(ex); - slot.ElementType = slot.Type; - slot.Type = MarshalerType.TaskRejected; + slot.JSHandle = CreateFailedPromise(ex); return; } else { - if (GetTaskResultDynamic(task, out object? result)) - { - ToJS(result); - slot.ElementType = slot.Type; - } - else - { - slot.ElementType = MarshalerType.Void; - } - slot.Type = MarshalerType.TaskResolved; + object? result = JSHostImplementation.GetTaskResultDynamic(task); + slot.JSHandle = CreateResolvedPromise(result, MarshalResult); return; } } - slot.Type = MarshalerType.Task; - slot.JSHandle = AllocJSVHandle(); - var taskHolder = new JSObject(slot.JSHandle); + IntPtr jsHandle = CreatePendingPromise(); + slot.JSHandle = jsHandle; + JSObject promise = JSHostImplementation.CreateCSOwnedProxy(jsHandle); #if FEATURE_WASM_THREADS task.ContinueWith(_ => Complete(), TaskScheduler.FromCurrentSynchronizationContext()); @@ -174,21 +158,36 @@ internal void ToJSDynamic(Task? value) void Complete() { - if (task.Exception != null) - { - RejectPromise(taskHolder, task.Exception); - } - else +#if FEATURE_WASM_THREADS + JSObject.AssertThreadAffinity(promise); +#endif + + // When this task was never resolved/rejected + // promise (held by this lambda) would be collected by GC after the Task is collected + // and would also allow the JS promise to be collected + + try { - if (GetTaskResultDynamic(task, out object? result)) + if (task.Exception != null) { - ResolvePromise(taskHolder, result, MarshalResult); + FailPromise(promise, task.Exception); } else { - ResolveVoidPromise(taskHolder); + object? result = JSHostImplementation.GetTaskResultDynamic(task); + + ResolvePromise(promise, result, MarshalResult); } } + catch (Exception ex) + { + throw new InvalidOperationException(ex.Message, ex); + } + finally + { + // this should never happen after the task was GC'd + promise.Dispose(); + } } static void MarshalResult(ref JSMarshalerArgument arg, object? taskResult) @@ -211,28 +210,26 @@ public void ToJS(Task? value) slot.Type = MarshalerType.None; return; } + slot.Type = MarshalerType.Task; if (task.IsCompleted) { if (task.Exception != null) { Exception ex = task.Exception; - ToJS(ex); - slot.ElementType = slot.Type; - slot.Type = MarshalerType.TaskRejected; + slot.JSHandle = CreateFailedPromise(ex); return; } else { - slot.ElementType = slot.Type; - slot.Type = MarshalerType.TaskResolved; + slot.JSHandle = IntPtr.Zero; return; } } - slot.Type = MarshalerType.Task; - slot.JSHandle = AllocJSVHandle(); - var taskHolder = new JSObject(slot.JSHandle); + IntPtr jsHandle = CreatePendingPromise(); + slot.JSHandle = jsHandle; + JSObject promise = JSHostImplementation.CreateCSOwnedProxy(jsHandle); #if FEATURE_WASM_THREADS task.ContinueWith(_ => Complete(), TaskScheduler.FromCurrentSynchronizationContext()); @@ -242,13 +239,33 @@ public void ToJS(Task? value) void Complete() { - if (task.Exception != null) +#if FEATURE_WASM_THREADS + JSObject.AssertThreadAffinity(promise); +#endif + + // When this task was never resolved/rejected + // promise (held by this lambda) would be collected by GC after the Task is collected + // and would also allow the JS promise to be collected + + try + { + if (task.Exception != null) + { + FailPromise(promise, task.Exception); + } + else + { + ResolveVoidPromise(promise); + } + } + catch (Exception ex) { - RejectPromise(taskHolder, task.Exception); + throw new InvalidOperationException(ex.Message, ex); } - else + finally { - ResolveVoidPromise(taskHolder); + // this should never happen after the task was GC'd + promise.Dispose(); } } } @@ -269,29 +286,28 @@ public void ToJS(Task? value, ArgumentToJSCallback marshaler) slot.Type = MarshalerType.None; return; } + slot.Type = MarshalerType.Task; if (task.IsCompleted) { if (task.Exception != null) { Exception ex = task.Exception; - ToJS(ex); - slot.ElementType = slot.Type; - slot.Type = MarshalerType.TaskRejected; + slot.JSHandle = CreateFailedPromise(ex); return; } else { T result = task.Result; - ToJS(result); - slot.ElementType = slot.Type; - slot.Type = MarshalerType.TaskResolved; + slot.JSHandle = CreateResolvedPromise(result, marshaler); return; } } - slot.Type = MarshalerType.Task; - slot.JSHandle = AllocJSVHandle(); - var taskHolder = new JSObject(slot.JSHandle); + + + IntPtr jsHandle = CreatePendingPromise(); + slot.JSHandle = jsHandle; + JSObject promise = JSHostImplementation.CreateCSOwnedProxy(jsHandle); #if FEATURE_WASM_THREADS task.ContinueWith(_ => Complete(), TaskScheduler.FromCurrentSynchronizationContext()); @@ -301,21 +317,80 @@ public void ToJS(Task? value, ArgumentToJSCallback marshaler) void Complete() { - if (task.Exception != null) +#if FEATURE_WASM_THREADS + JSObject.AssertThreadAffinity(promise); +#endif + // When this task was never resolved/rejected + // promise (held by this lambda) would be collected by GC after the Task is collected + // and would also allow the JS promise to be collected + + try { - RejectPromise(taskHolder, task.Exception); + if (task.Exception != null) + { + FailPromise(promise, task.Exception); + } + else + { + T result = task.Result; + ResolvePromise(promise, result, marshaler); + } } - else + catch (Exception ex) { - T result = task.Result; - ResolvePromise(taskHolder, result, marshaler); + throw new InvalidOperationException(ex.Message, ex); + } + finally + { + // this should never happen after the task was GC'd + promise.Dispose(); } } } - private static void RejectPromise(JSObject holder, Exception ex) + private static IntPtr CreatePendingPromise() { - holder.AssertNotDisposed(); + Span args = stackalloc JSMarshalerArgument[4]; + ref JSMarshalerArgument exc = ref args[0]; + ref JSMarshalerArgument res = ref args[1]; + ref JSMarshalerArgument arg_handle = ref args[2]; + ref JSMarshalerArgument arg_value = ref args[3]; + + exc.Initialize(); + res.Initialize(); + arg_value.Initialize(); + + // should create new promise + arg_handle.slot.Type = MarshalerType.Task; + arg_handle.slot.JSHandle = IntPtr.Zero; + arg_value.slot.Type = MarshalerType.Task; + + JavaScriptImports.MarshalPromise(args); + return res.slot.JSHandle; + } + + private static IntPtr CreateFailedPromise(Exception ex) + { + Span args = stackalloc JSMarshalerArgument[4]; + ref JSMarshalerArgument exc = ref args[0]; + ref JSMarshalerArgument res = ref args[1]; + ref JSMarshalerArgument arg_handle = ref args[2]; + ref JSMarshalerArgument arg_value = ref args[3]; + res.Initialize(); + arg_value.Initialize(); + + // should create new promise + arg_handle.slot.Type = MarshalerType.Task; + arg_handle.slot.JSHandle = IntPtr.Zero; + // should fail it with exception + exc.ToJS(ex); + JavaScriptImports.MarshalPromise(args); + return res.slot.JSHandle; + } + + private static void FailPromise(JSObject promise, Exception ex) + { + ObjectDisposedException.ThrowIf(promise.IsDisposed, promise); Span args = stackalloc JSMarshalerArgument[4]; ref JSMarshalerArgument exc = ref args[0]; @@ -325,22 +400,43 @@ private static void RejectPromise(JSObject holder, Exception ex) exc.Initialize(); res.Initialize(); + arg_value.Initialize(); // should update existing promise - arg_handle.slot.Type = MarshalerType.TaskRejected; - arg_handle.slot.JSHandle = holder.JSHandle; + arg_handle.slot.Type = MarshalerType.None; + arg_handle.slot.JSHandle = promise.JSHandle; // should fail it with exception - arg_value.ToJS(ex); + exc.ToJS(ex); - JavaScriptImports.ResolveOrRejectPromise(args); + JavaScriptImports.MarshalPromise(args); + } - holder.DisposeLocal(); + private static IntPtr CreateResolvedPromise(T value, ArgumentToJSCallback marshaler) + { + Span args = stackalloc JSMarshalerArgument[4]; + ref JSMarshalerArgument exc = ref args[0]; + ref JSMarshalerArgument res = ref args[1]; + ref JSMarshalerArgument arg_handle = ref args[2]; + ref JSMarshalerArgument arg_value = ref args[3]; + + exc.Initialize(); + res.Initialize(); + + // should create new promise + arg_handle.slot.Type = MarshalerType.Task; + arg_handle.slot.JSHandle = IntPtr.Zero; + + // and resolve it with value + marshaler(ref arg_value, value); + + JavaScriptImports.MarshalPromise(args); + return res.slot.JSHandle; } - private static void ResolveVoidPromise(JSObject holder) + private static void ResolveVoidPromise(JSObject promise) { - holder.AssertNotDisposed(); + ObjectDisposedException.ThrowIf(promise.IsDisposed, promise); Span args = stackalloc JSMarshalerArgument[4]; ref JSMarshalerArgument exc = ref args[0]; @@ -352,19 +448,17 @@ private static void ResolveVoidPromise(JSObject holder) res.Initialize(); // should update existing promise - arg_handle.slot.Type = MarshalerType.TaskResolved; - arg_handle.slot.JSHandle = holder.JSHandle; - - arg_value.slot.Type = MarshalerType.Void; + arg_handle.slot.Type = MarshalerType.None; + arg_handle.slot.JSHandle = promise.JSHandle; - JavaScriptImports.ResolveOrRejectPromise(args); + arg_value.slot.Type = MarshalerType.None; - holder.DisposeLocal(); + JavaScriptImports.MarshalPromise(args); } - private static void ResolvePromise(JSObject holder, T value, ArgumentToJSCallback marshaler) + private static void ResolvePromise(JSObject promise, T value, ArgumentToJSCallback marshaler) { - holder.AssertNotDisposed(); + ObjectDisposedException.ThrowIf(promise.IsDisposed, promise); Span args = stackalloc JSMarshalerArgument[4]; ref JSMarshalerArgument exc = ref args[0]; @@ -376,15 +470,13 @@ private static void ResolvePromise(JSObject holder, T value, ArgumentToJSCall res.Initialize(); // should update existing promise - arg_handle.slot.Type = MarshalerType.TaskResolved; - arg_handle.slot.JSHandle = holder.JSHandle; + arg_handle.slot.Type = MarshalerType.None; + arg_handle.slot.JSHandle = promise.JSHandle; // and resolve it with value marshaler(ref arg_value, value); - JavaScriptImports.ResolveOrRejectPromise(args); - - holder.DisposeLocal(); + JavaScriptImports.MarshalPromise(args); } } } 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 18b53e61d920c..f61ab281e5cb1 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 @@ -1010,6 +1010,8 @@ public static Task DisposeAsync() { _module.Dispose(); _module = null; + // you can set verbose: true to see which proxies are left to the GC to collect + ForceDisposeProxies(false, verbose: false); return Task.CompletedTask; } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.js b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.js index a4c018258f9b2..7302b1c061bc3 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.js +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.js @@ -2,12 +2,6 @@ export async function runSecondRuntimeAndTestStaticState() { const { dotnet: dotnet2 } = await import('./_framework/dotnet.js?instance=2'); const runtime2 = await dotnet2 .withStartupMemoryCache(false) - .withConfig({ - forwardConsoleLogsToWS: false, - diagnosticTracing: false, - appendElementOnExit: false, - logExitCode: false, - }) .create(); const increment1 = await getIncrementStateFunction(App.runtime); diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs index a2df9feee1eea..31dcb65582869 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs @@ -24,7 +24,6 @@ public SatelliteLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFi } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/93387")] public async Task LoadSatelliteAssembly() { CopyTestAsset("WasmBasicTestApp", "SatelliteLoadingTests"); diff --git a/src/mono/wasm/runtime/cancelable-promise.ts b/src/mono/wasm/runtime/cancelable-promise.ts index 3ae287bf99e4d..2fbb648c249ff 100644 --- a/src/mono/wasm/runtime/cancelable-promise.ts +++ b/src/mono/wasm/runtime/cancelable-promise.ts @@ -3,7 +3,7 @@ import { _lookup_js_owned_object } from "./gc-handles"; import { createPromiseController, loaderHelpers, mono_assert } from "./globals"; -import { PromiseHolder } from "./marshal-to-cs"; +import { TaskCallbackHolder } from "./marshal-to-cs"; import { ControllablePromise, GCHandle } from "./types/internal"; export const _are_promises_supported = ((typeof Promise === "object") || (typeof Promise === "function")) && (typeof Promise.resolve === "function"); @@ -22,11 +22,12 @@ export function wrap_as_cancelable_promise(fn: () => Promise): Controllabl return promise; } -export function mono_wasm_cancel_promise(task_holder_gcv_handle: GCHandle): void { - const holder = _lookup_js_owned_object(task_holder_gcv_handle) as PromiseHolder; - mono_assert(!!holder, () => `Expected Promise for GCVHandle ${task_holder_gcv_handle}`); +export function mono_wasm_cancel_promise(task_holder_gc_handle: GCHandle): void { + const holder = _lookup_js_owned_object(task_holder_gc_handle) as TaskCallbackHolder; + if (!holder) return; // probably already GC collected const promise = holder.promise; + mono_assert(!!promise, () => `Expected Promise for GCHandle ${task_holder_gc_handle}`); loaderHelpers.assertIsControllablePromise(promise); const promise_control = loaderHelpers.getPromiseController(promise); promise_control.reject(new Error("OperationCanceledException")); diff --git a/src/mono/wasm/runtime/corebindings.c b/src/mono/wasm/runtime/corebindings.c index dd15493724e93..e12a0adc379e9 100644 --- a/src/mono/wasm/runtime/corebindings.c +++ b/src/mono/wasm/runtime/corebindings.c @@ -22,7 +22,7 @@ extern void mono_wasm_bind_js_function(MonoString **function_name, MonoString ** extern void mono_wasm_invoke_bound_function(int function_js_handle, void *data); extern void mono_wasm_invoke_import(int fn_handle, void *data); 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); +extern void mono_wasm_marshal_promise(void *data); typedef void (*background_job_cb)(void); void mono_main_thread_schedule_background_job (background_job_cb cb); @@ -67,7 +67,7 @@ void bindings_initialize_internals (void) mono_add_internal_call ("Interop/Runtime::InvokeJSFunction", mono_wasm_invoke_bound_function); mono_add_internal_call ("Interop/Runtime::InvokeImport", mono_wasm_invoke_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); + mono_add_internal_call ("Interop/Runtime::MarshalPromise", mono_wasm_marshal_promise); mono_add_internal_call ("Interop/Runtime::RegisterGCRoot", mono_wasm_register_root); mono_add_internal_call ("Interop/Runtime::DeregisterGCRoot", mono_wasm_deregister_root); diff --git a/src/mono/wasm/runtime/exports-binding.ts b/src/mono/wasm/runtime/exports-binding.ts index 5fbfc421a9a57..d7ef9ad945d14 100644 --- a/src/mono/wasm/runtime/exports-binding.ts +++ b/src/mono/wasm/runtime/exports-binding.ts @@ -11,7 +11,7 @@ import { mono_wasm_bind_js_function, mono_wasm_invoke_bound_function, mono_wasm_ 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"; -import { mono_wasm_resolve_or_reject_promise } from "./marshal-to-js"; +import { mono_wasm_marshal_promise } from "./marshal-to-js"; import { mono_wasm_eventloop_has_unsettled_interop_promises } from "./pthreads/shared/eventloop"; import { mono_wasm_pthread_on_pthread_attached, mono_wasm_pthread_on_pthread_detached } from "./pthreads/worker"; import { mono_wasm_schedule_timer, schedule_background_exec } from "./scheduling"; @@ -108,7 +108,7 @@ export const mono_wasm_imports = [ mono_wasm_invoke_bound_function, mono_wasm_invoke_import, mono_wasm_bind_cs_function, - mono_wasm_resolve_or_reject_promise, + mono_wasm_marshal_promise, mono_wasm_change_case_invariant, mono_wasm_change_case, mono_wasm_compare_string, diff --git a/src/mono/wasm/runtime/gc-handles.ts b/src/mono/wasm/runtime/gc-handles.ts index b1d152dc57bdc..857b73782992b 100644 --- a/src/mono/wasm/runtime/gc-handles.ts +++ b/src/mono/wasm/runtime/gc-handles.ts @@ -8,7 +8,7 @@ import { loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { fn_wrapper_by_fn_handle } from "./invoke-js"; import { mono_log_info, mono_log_warn } from "./logging"; import { bound_cs_function_symbol, imported_js_function_symbol, proxy_debug_symbol } from "./marshal"; -import { GCHandle, GCHandleNull, JSHandle, WeakRefInternal } from "./types/internal"; +import { GCHandle, GCHandleNull, JSHandle, JSHandleDisposed, JSHandleNull } from "./types/internal"; import { _use_weak_ref, create_weak_ref } from "./weak-ref"; import { exportsByAssembly } from "./invoke-cs"; @@ -18,38 +18,10 @@ let _js_owned_object_registry: FinalizationRegistry; // this is array, not map. We maintain list of gaps in _js_handle_free_list so that it could be as compact as possible // 0th element is always null, because JSHandle == 0 is invalid handle. const _cs_owned_objects_by_js_handle: any[] = [null]; -const _cs_owned_objects_by_jsv_handle: any[] = [null]; const _js_handle_free_list: JSHandle[] = []; let _next_js_handle = 1; -export const _js_owned_object_table = new Map>(); - -const _gcv_handle_free_list: GCHandle[] = []; -let _next_gcv_handle = -2; - -// GCVHandle is like GCHandle, but it's not tracked and allocated by the mono GC, but just by JS. -// It's used when we need to create GCHandle-like identity ahead of time, before calling Mono. -// they have negative values, so that they don't collide with GCHandles. -export function alloc_gcv_handle(): GCHandle { - const gcv_handle = _gcv_handle_free_list.length ? _gcv_handle_free_list.pop() : _next_gcv_handle--; - return gcv_handle as any; -} - -export function free_gcv_handle(gcv_handle: GCHandle): void { - _gcv_handle_free_list.push(gcv_handle); -} - -export function is_jsv_handle(js_handle: JSHandle): boolean { - return (js_handle as any) < -1; -} - -export function is_js_handle(js_handle: JSHandle): boolean { - return (js_handle as any) > 0; -} - -export function is_gcv_handle(gc_handle: GCHandle): boolean { - return (gc_handle as any) < -1; -} +export const _js_owned_object_table = new Map(); // NOTE: FinalizationRegistry and WeakRef are missing on Safari below 14.1 if (_use_finalization_registry) { @@ -62,10 +34,14 @@ export const do_not_force_dispose = Symbol.for("wasm do_not_force_dispose"); export function mono_wasm_get_jsobj_from_js_handle(js_handle: JSHandle): any { - if (is_js_handle(js_handle)) + if (js_handle !== JSHandleNull && js_handle !== JSHandleDisposed) return _cs_owned_objects_by_js_handle[js_handle]; - if (is_jsv_handle(js_handle)) - return _cs_owned_objects_by_jsv_handle[0 - js_handle]; + return null; +} + +export function get_js_obj(js_handle: JSHandle): any { + if (js_handle !== JSHandleNull && js_handle !== JSHandleDisposed) + return mono_wasm_get_jsobj_from_js_handle(js_handle); return null; } @@ -74,9 +50,8 @@ export function mono_wasm_get_js_handle(js_obj: any): JSHandle { return js_obj[cs_owned_js_handle_symbol]; } const js_handle = _js_handle_free_list.length ? _js_handle_free_list.pop() : _next_js_handle++; - // note _cs_owned_objects_by_js_handle is list, not Map. That's why we maintain _js_handle_free_list. - _cs_owned_objects_by_js_handle[js_handle] = js_obj; + _cs_owned_objects_by_js_handle[js_handle!] = js_obj; if (Object.isExtensible(js_obj)) { js_obj[cs_owned_js_handle_symbol] = js_handle; @@ -89,67 +64,50 @@ export function mono_wasm_get_js_handle(js_obj: any): JSHandle { return js_handle as JSHandle; } -export function register_with_jsv_handle(js_obj: any, jsv_handle: JSHandle) { - // 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; - - if (Object.isExtensible(js_obj)) { - js_obj[cs_owned_js_handle_symbol] = jsv_handle; - } -} - export function mono_wasm_release_cs_owned_object(js_handle: JSHandle): void { - let obj: any; - if (is_js_handle(js_handle)) { - obj = _cs_owned_objects_by_js_handle[js_handle]; + const obj = _cs_owned_objects_by_js_handle[js_handle]; + if (typeof obj !== "undefined" && obj !== null) { + if (typeof obj[cs_owned_js_handle_symbol] !== "undefined") { + obj[cs_owned_js_handle_symbol] = undefined; + } + _cs_owned_objects_by_js_handle[js_handle] = undefined; _js_handle_free_list.push(js_handle); } - if (is_jsv_handle(js_handle)) { - obj = _cs_owned_objects_by_jsv_handle[0 - js_handle]; - _cs_owned_objects_by_jsv_handle[0 - js_handle] = undefined; - } - mono_assert(obj !== undefined && obj !== null, "ObjectDisposedException"); - if (typeof obj[cs_owned_js_handle_symbol] !== "undefined") { - obj[cs_owned_js_handle_symbol] = undefined; - } } -export function setup_managed_proxy(owner: any, gc_handle: GCHandle): void { +export function setup_managed_proxy(result: any, gc_handle: GCHandle): void { // 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; + result[js_owned_gc_handle_symbol] = gc_handle; // NOTE: this would be leaking C# objects when the browser doesn't support FinalizationRegistry/WeakRef if (_use_finalization_registry) { // register for GC of the C# object after the JS side is done with the object - _js_owned_object_registry.register(owner, gc_handle, owner); + _js_owned_object_registry.register(result, gc_handle, result); } // register for instance reuse // NOTE: this would be leaking C# objects when the browser doesn't support FinalizationRegistry/WeakRef - const wr = create_weak_ref(owner); + const wr = create_weak_ref(result); _js_owned_object_table.set(gc_handle, wr); } -export function teardown_managed_proxy(owner: any, gc_handle: GCHandle, skipManaged?: boolean): void { +export function teardown_managed_proxy(result: any, gc_handle: GCHandle): void { // 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), // and tell the C# side to stop holding a reference to the managed object. // "The FinalizationRegistry callback is called potentially multiple times" - if (owner) { - gc_handle = owner[js_owned_gc_handle_symbol]; - owner[js_owned_gc_handle_symbol] = GCHandleNull; + if (result) { + gc_handle = result[js_owned_gc_handle_symbol]; + result[js_owned_gc_handle_symbol] = GCHandleNull; if (_use_finalization_registry) { - _js_owned_object_registry.unregister(owner); + _js_owned_object_registry.unregister(result); } } - if (gc_handle !== GCHandleNull && _js_owned_object_table.delete(gc_handle) && !skipManaged) { + if (gc_handle !== GCHandleNull && _js_owned_object_table.delete(gc_handle)) { runtimeHelpers.javaScriptExports.release_js_owned_object_by_gc_handle(gc_handle); } - if (is_gcv_handle(gc_handle)) { - free_gcv_handle(gc_handle); - } } export function assert_not_disposed(result: any): GCHandle { @@ -171,9 +129,9 @@ export function _lookup_js_owned_object(gc_handle: GCHandle): any { return null; const wr = _js_owned_object_table.get(gc_handle); if (wr) { - // this could be null even before _js_owned_object_finalized was called - // TODO: are there race condition consequences ? return wr.deref(); + // TODO: could this be null before _js_owned_object_finalized was called ? + // TODO: are there race condition consequences ? } return null; } @@ -182,13 +140,12 @@ export function assertNoProxies(): void { if (!MonoWasmThreads) return; mono_assert(_js_owned_object_table.size === 0, "There should be no proxies on this thread."); mono_assert(_cs_owned_objects_by_js_handle.length === 1, "There should be no proxies on this thread."); - mono_assert(_cs_owned_objects_by_jsv_handle.length === 1, "There should be no proxies on this thread."); mono_assert(exportsByAssembly.size === 0, "There should be no exports on this thread."); mono_assert(fn_wrapper_by_fn_handle.length === 1, "There should be no imports on this thread."); } -// when we arrive here from UninstallWebWorkerInterop, the C# will unregister the handles too. -// when called from elsewhere, C# side could be unbalanced!! +// when we arrive here, the C# side is already done with the object. +// We don't have to call back to release them. export function forceDisposeProxies(disposeMethods: boolean, verbose: boolean): void { let keepSomeCsAlive = false; let keepSomeJsAlive = false; @@ -201,7 +158,7 @@ export function forceDisposeProxies(disposeMethods: boolean, verbose: boolean): const gc_handles = [..._js_owned_object_table.keys()]; for (const gc_handle of gc_handles) { const wr = _js_owned_object_table.get(gc_handle); - const obj = wr && wr.deref(); + const obj = wr.deref(); if (_use_finalization_registry && obj) { _js_owned_object_registry.unregister(obj); } @@ -227,7 +184,7 @@ export function forceDisposeProxies(disposeMethods: boolean, verbose: boolean): if (obj[js_owned_gc_handle_symbol] === gc_handle) { obj[js_owned_gc_handle_symbol] = GCHandleNull; } - if (!_use_weak_ref && wr) wr.dispose!(); + if (!_use_weak_ref && wr) wr.dispose(); doneGCHandles++; } else { keepSomeCsAlive = true; @@ -240,11 +197,13 @@ export function forceDisposeProxies(disposeMethods: boolean, verbose: boolean): _js_owned_object_registry = new globalThis.FinalizationRegistry(_js_owned_object_finalized); } } - const free_js_handle = (js_handle: number, list: any[]): void => { - const obj = list[js_handle]; + + // dispose all proxies to JS objects + for (let js_handle = 0; js_handle < _cs_owned_objects_by_js_handle.length; js_handle++) { + const obj = _cs_owned_objects_by_js_handle[js_handle]; const keepAlive = obj && typeof obj[do_not_force_dispose] === "boolean" && obj[do_not_force_dispose]; if (!keepAlive) { - list[js_handle] = undefined; + _cs_owned_objects_by_js_handle[js_handle] = undefined; } if (obj) { if (verbose) { @@ -271,22 +230,12 @@ export function forceDisposeProxies(disposeMethods: boolean, verbose: boolean): keepSomeJsAlive = true; } } - }; - // dispose all proxies to JS objects - for (let js_handle = 0; js_handle < _cs_owned_objects_by_js_handle.length; js_handle++) { - free_js_handle(js_handle, _cs_owned_objects_by_js_handle); - } - for (let jsv_handle = 0; jsv_handle < _cs_owned_objects_by_jsv_handle.length; jsv_handle++) { - free_js_handle(jsv_handle, _cs_owned_objects_by_jsv_handle); } if (!keepSomeJsAlive) { _cs_owned_objects_by_js_handle.length = 1; - _cs_owned_objects_by_jsv_handle.length = 1; _next_js_handle = 1; _js_handle_free_list.length = 0; } - _gcv_handle_free_list.length = 0; - _next_gcv_handle = -2; if (disposeMethods) { // dispose all [JSImport] diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index 65a0f160d7e3c..b38393a6fe6f4 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -28,7 +28,7 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, const mark = startMeasure(); try { const version = get_signature_version(signature); - mono_assert(version === 2, () => `Signature version ${version} mismatch.`); + mono_assert(version === 1, () => `Signature version ${version} mismatch.`); const args_count = get_signature_argument_count(signature); const js_fqn = monoStringToString(fqn_root)!; diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index 844c0f4827a47..a17ef89be3f06 100644 --- a/src/mono/wasm/runtime/invoke-js.ts +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -28,7 +28,7 @@ export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_ resultRoot = mono_wasm_new_external_root(result_address); try { const version = get_signature_version(signature); - mono_assert(version === 2, () => `Signature version ${version} mismatch.`); + mono_assert(version === 1, () => `Signature version ${version} mismatch.`); const js_function_name = monoStringToString(function_name_root)!; const mark = startMeasure(); diff --git a/src/mono/wasm/runtime/loader/exit.ts b/src/mono/wasm/runtime/loader/exit.ts index 76160405494ec..4c7099e4279e3 100644 --- a/src/mono/wasm/runtime/loader/exit.ts +++ b/src/mono/wasm/runtime/loader/exit.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WEB, INTERNAL, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; -import { mono_log_debug, mono_log_error, mono_log_info_no_prefix, mono_log_warn, teardown_proxy_console } from "./logging"; +import { mono_log_debug, consoleWebSocket, mono_log_error, mono_log_info_no_prefix, mono_log_warn } from "./logging"; export function is_exited() { return loaderHelpers.exitCode !== undefined; @@ -14,7 +14,7 @@ export function is_runtime_running() { export function assert_runtime_running() { mono_assert(runtimeHelpers.runtimeReady, "mono runtime didn't start yet"); - mono_assert(!loaderHelpers.assertAfterExit || !is_exited(), () => `mono runtime already exited with ${loaderHelpers.exitCode} ${loaderHelpers.exitReason}`); + mono_assert(!loaderHelpers.assertAfterExit || !is_exited(), () => `mono runtime already exited with ${loaderHelpers.exitCode}`); } // this will also call mono_wasm_exit if available, which will call exitJS -> _proc_exit -> terminateAllThreads @@ -62,7 +62,6 @@ export function mono_exit(exit_code: number, reason?: any): void { } loaderHelpers.exitCode = exit_code; - loaderHelpers.exitReason = reason.message; } if (loaderHelpers.config && loaderHelpers.config.asyncFlushOnExit && exit_code === 0) { @@ -119,7 +118,6 @@ async function flush_node_streams() { } function abort_promises(reason: any) { - loaderHelpers.exitReason = reason; loaderHelpers.allDownloadsQueued.promise_control.reject(reason); loaderHelpers.afterConfigLoaded.promise_control.reject(reason); loaderHelpers.wasmCompilePromise.promise_control.reject(reason); @@ -170,8 +168,20 @@ function logOnExit(exit_code: number, reason: any) { } } if (loaderHelpers.config && loaderHelpers.config.logExitCode) { - if (loaderHelpers.config.forwardConsoleLogsToWS) { - teardown_proxy_console("WASM EXIT " + exit_code); + if (consoleWebSocket) { + const stop_when_ws_buffer_empty = () => { + if (consoleWebSocket.bufferedAmount == 0) { + // tell xharness WasmTestMessagesProcessor we are done. + // note this sends last few bytes into the same WS + mono_log_info_no_prefix("WASM EXIT " + exit_code); + consoleWebSocket.onclose = null; + consoleWebSocket.close(1000, "exit_code:" + exit_code + ": " + reason); + } + else { + globalThis.setTimeout(stop_when_ws_buffer_empty, 100); + } + }; + stop_when_ws_buffer_empty(); } else { mono_log_info_no_prefix("WASM EXIT " + exit_code); } diff --git a/src/mono/wasm/runtime/loader/logging.ts b/src/mono/wasm/runtime/loader/logging.ts index 57274bb04a02f..938938f229d34 100644 --- a/src/mono/wasm/runtime/loader/logging.ts +++ b/src/mono/wasm/runtime/loader/logging.ts @@ -4,12 +4,7 @@ /* eslint-disable no-console */ import { 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; export function mono_log_debug(msg: string, ...data: any) { if (loaderHelpers.diagnosticTracing) { @@ -36,117 +31,88 @@ export function mono_log_error(msg: string, ...data: any) { } console.error(prefix + msg, ...data); } +export let consoleWebSocket: WebSocket; -function proxyConsoleMethod(prefix: string, func: any, asJson: boolean) { - return function (...args: any[]) { - try { - let payload = args[0]; - if (payload === undefined) payload = "undefined"; - else if (payload === null) payload = "null"; - else if (typeof payload === "function") payload = payload.toString(); - else if (typeof payload !== "string") { - try { - payload = JSON.stringify(payload); - } catch (e) { - payload = payload.toString(); +export function setup_proxy_console(id: string, console: Console, origin: string): void { + // this need to be copy, in order to keep reference to original methods + const originalConsole = { + log: console.log, + error: console.error + }; + const anyConsole = console as any; + + function proxyConsoleMethod(prefix: string, func: any, asJson: boolean) { + return function (...args: any[]) { + try { + let payload = args[0]; + if (payload === undefined) payload = "undefined"; + else if (payload === null) payload = "null"; + else if (typeof payload === "function") payload = payload.toString(); + else if (typeof payload !== "string") { + try { + payload = JSON.stringify(payload); + } catch (e) { + payload = payload.toString(); + } } - } - if (typeof payload === "string") { - if (payload[0] == "[") { - const now = new Date().toISOString(); - if (threadId !== "main") { - payload = `[${threadId}][${now}] ${payload}`; - } else { - payload = `[${now}] ${payload}`; + if (typeof payload === "string") { + if (payload[0] == "[") { + const now = new Date().toISOString(); + if (id !== "main") { + payload = `[${id}][${now}] ${payload}`; + } else { + payload = `[${now}] ${payload}`; + } + } else if (id !== "main") { + payload = `[${id}] ${payload}`; } - } else if (threadId !== "main") { - payload = `[${threadId}] ${payload}`; } - } - if (asJson) { - func(JSON.stringify({ - method: prefix, - payload: payload, - arguments: args.slice(1) - })); - } else { - func([prefix + payload, ...args.slice(1)]); + if (asJson) { + func(JSON.stringify({ + method: prefix, + payload: payload, + arguments: args.slice(1) + })); + } else { + func([prefix + payload, ...args.slice(1)]); + } + } catch (err) { + originalConsole.error(`proxyConsole failed: ${err}`); } - } catch (err) { - originalConsoleMethods.error(`proxyConsole failed: ${err}`); - } - }; -} - -export function setup_proxy_console(id: string, console: Console, origin: string): void { - theConsoleApi = console as any; - threadId = id; - originalConsoleMethods = { - ...console - }; + }; + } - setupOriginal(); + const methods = ["debug", "trace", "warn", "info", "error"]; + for (const m of methods) { + if (typeof (anyConsole[m]) !== "function") { + anyConsole[m] = proxyConsoleMethod(`console.${m}: `, console.log, false); + } + } const consoleUrl = `${origin}/console`.replace("https://", "wss://").replace("http://", "ws://"); consoleWebSocket = new WebSocket(consoleUrl); - consoleWebSocket.addEventListener("error", logWSError); - consoleWebSocket.addEventListener("close", logWSClose); consoleWebSocket.addEventListener("open", () => { - originalConsoleMethods.log(`browser: [${threadId}] Console websocket connected.`); - setupWS(); - }, { - once: true + originalConsole.log(`browser: [${id}] Console websocket connected.`); + }); + consoleWebSocket.addEventListener("error", (event) => { + originalConsole.error(`[${id}] websocket error: ${event}`, event); + }); + consoleWebSocket.addEventListener("close", (event) => { + originalConsole.error(`[${id}] websocket closed: ${event}`, event); }); -} -export function teardown_proxy_console(message: string) { - originalConsoleMethods.log(message); - const stop_when_ws_buffer_empty = () => { - if (consoleWebSocket.bufferedAmount == 0) { - // tell xharness WasmTestMessagesProcessor we are done. - // note this sends last few bytes into the same WS - mono_log_info_no_prefix(message); - setupOriginal(); - - consoleWebSocket.removeEventListener("error", logWSError); - consoleWebSocket.removeEventListener("close", logWSClose); - consoleWebSocket.close(1000, message); + const send = (msg: string) => { + if (consoleWebSocket.readyState === WebSocket.OPEN) { + consoleWebSocket.send(msg); } else { - globalThis.setTimeout(stop_when_ws_buffer_empty, 100); + originalConsole.log(msg); } }; - stop_when_ws_buffer_empty(); -} - -function send(msg: string) { - if (consoleWebSocket.readyState === WebSocket.OPEN) { - consoleWebSocket.send(msg); - } - else { - originalConsoleMethods.log(msg); - } -} - -function logWSError(event: Event) { - originalConsoleMethods.error(`[${threadId}] websocket error: ${event}`, event); -} -function logWSClose(event: Event) { - originalConsoleMethods.error(`[${threadId}] websocket closed: ${event}`, event); -} - -function setupWS() { - for (const m of methods) { - theConsoleApi[m] = proxyConsoleMethod(`console.${m}`, send, true); - } -} - -function setupOriginal() { - for (const m of methods) { - theConsoleApi[m] = proxyConsoleMethod(`console.${m}`, originalConsoleMethods.log, false); - } + for (const m of ["log", ...methods]) + anyConsole[m] = proxyConsoleMethod(`console.${m}`, send, true); } diff --git a/src/mono/wasm/runtime/loader/promise-controller.ts b/src/mono/wasm/runtime/loader/promise-controller.ts index b27c8216f4fc3..2a0f0a7a4144f 100644 --- a/src/mono/wasm/runtime/loader/promise-controller.ts +++ b/src/mono/wasm/runtime/loader/promise-controller.ts @@ -51,5 +51,5 @@ export function isControllablePromise(promise: Promise): promise is Contro } export function assertIsControllablePromise(promise: Promise): asserts promise is ControllablePromise { - mono_assert(promise && isControllablePromise(promise), "Promise is not controllable"); + mono_assert(isControllablePromise(promise), "Promise is not controllable"); } diff --git a/src/mono/wasm/runtime/managed-exports.ts b/src/mono/wasm/runtime/managed-exports.ts index fee9ed61fecca..49b16f7ae3477 100644 --- a/src/mono/wasm/runtime/managed-exports.ts +++ b/src/mono/wasm/runtime/managed-exports.ts @@ -6,7 +6,7 @@ import MonoWasmThreads from "consts:monoWasmThreads"; import { GCHandle, MarshalerToCs, MarshalerToJs, MarshalerType, MonoMethod } from "./types/internal"; import cwraps from "./cwraps"; import { runtimeHelpers, Module, loaderHelpers, mono_assert } from "./globals"; -import { alloc_stack_frame, get_arg, set_arg_type, set_gc_handle } from "./marshal"; +import { alloc_stack_frame, get_arg, get_arg_gc_handle, set_arg_type, set_gc_handle } from "./marshal"; import { invoke_method_and_handle_exception } from "./invoke-cs"; import { marshal_array_to_cs, marshal_array_to_cs_impl, marshal_exception_to_cs, marshal_intptr_to_cs } from "./marshal-to-cs"; import { marshal_int32_to_js, marshal_string_to_js, marshal_task_to_js } from "./marshal-to-js"; @@ -30,6 +30,8 @@ export function init_managed_exports(): void { mono_assert(call_entry_point, "Can't find CallEntrypoint method"); const release_js_owned_object_by_gc_handle_method = get_method("ReleaseJSOwnedObjectByGCHandle"); mono_assert(release_js_owned_object_by_gc_handle_method, "Can't find ReleaseJSOwnedObjectByGCHandle method"); + const create_task_callback_method = get_method("CreateTaskCallback"); + mono_assert(create_task_callback_method, "Can't find CreateTaskCallback method"); const complete_task_method = get_method("CompleteTask"); mono_assert(complete_task_method, "Can't find CompleteTask method"); const call_delegate_method = get_method("CallDelegate"); @@ -108,14 +110,26 @@ export function init_managed_exports(): void { Module.stackRestore(sp); } }; - runtimeHelpers.javaScriptExports.complete_task = (holder_gcv_handle: GCHandle, error?: any, data?: any, res_converter?: MarshalerToCs) => { + runtimeHelpers.javaScriptExports.create_task_callback = () => { + const sp = Module.stackSave(); + loaderHelpers.assert_runtime_running(); + try { + const args = alloc_stack_frame(2); + invoke_method_and_handle_exception(create_task_callback_method, args); + const res = get_arg(args, 1); + return get_arg_gc_handle(res); + } finally { + Module.stackRestore(sp); + } + }; + runtimeHelpers.javaScriptExports.complete_task = (holder_gc_handle: GCHandle, error?: any, data?: any, res_converter?: MarshalerToCs) => { loaderHelpers.assert_runtime_running(); const sp = Module.stackSave(); try { const args = alloc_stack_frame(5); const arg1 = get_arg(args, 2); set_arg_type(arg1, MarshalerType.Object); - set_gc_handle(arg1, holder_gcv_handle); + set_gc_handle(arg1, holder_gc_handle); const arg2 = get_arg(args, 3); if (error) { marshal_exception_to_cs(arg2, error); diff --git a/src/mono/wasm/runtime/marshal-to-cs.ts b/src/mono/wasm/runtime/marshal-to-cs.ts index 493ff67c8f02b..14d6c7e3dd747 100644 --- a/src/mono/wasm/runtime/marshal-to-cs.ts +++ b/src/mono/wasm/runtime/marshal-to-cs.ts @@ -6,22 +6,23 @@ import BuildConfiguration from "consts:configuration"; import { isThenable } from "./cancelable-promise"; import cwraps from "./cwraps"; -import { alloc_gcv_handle, assert_not_disposed, cs_owned_js_handle_symbol, js_owned_gc_handle_symbol, mono_wasm_get_js_handle, setup_managed_proxy, teardown_managed_proxy } from "./gc-handles"; +import { assert_not_disposed, cs_owned_js_handle_symbol, js_owned_gc_handle_symbol, mono_wasm_get_js_handle, setup_managed_proxy, teardown_managed_proxy } from "./gc-handles"; import { Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { ManagedError, set_gc_handle, set_js_handle, set_arg_type, set_arg_i32, set_arg_f64, set_arg_i52, set_arg_f32, set_arg_i16, set_arg_u8, set_arg_b8, set_arg_date, set_arg_length, get_arg, get_signature_arg1_type, get_signature_arg2_type, js_to_cs_marshalers, get_signature_res_type, bound_js_function_symbol, set_arg_u16, array_element_size, - get_string_root, Span, ArraySegment, MemoryViewType, get_signature_arg3_type, set_arg_i64_big, set_arg_intptr, + get_string_root, Span, ArraySegment, MemoryViewType, get_signature_arg3_type, set_arg_i64_big, set_arg_intptr, IDisposable, set_arg_element_type, ManagedObject, JavaScriptMarshalerArgSize, proxy_debug_symbol } from "./marshal"; import { get_marshaler_to_js_by_type } from "./marshal-to-js"; import { _zero_region, localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory"; import { stringToMonoStringRoot } from "./strings"; -import { JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToCs, MarshalerType } from "./types/internal"; +import { GCHandle, GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToCs, MarshalerType } from "./types/internal"; import { TypedArray } from "./types/emscripten"; import { addUnsettledPromise, settleUnsettledPromise } from "./pthreads/shared/eventloop"; +import { mono_log_warn } from "./logging"; export const jsinteropDoc = "For more information see https://aka.ms/dotnet-wasm-jsinterop"; @@ -48,8 +49,6 @@ export function initialize_marshalers_to_cs(): void { js_to_cs_marshalers.set(MarshalerType.JSObject, marshal_js_object_to_cs); js_to_cs_marshalers.set(MarshalerType.Object, _marshal_cs_object_to_cs); js_to_cs_marshalers.set(MarshalerType.Task, _marshal_task_to_cs); - js_to_cs_marshalers.set(MarshalerType.TaskResolved, _marshal_task_to_cs); - js_to_cs_marshalers.set(MarshalerType.TaskRejected, _marshal_task_to_cs); js_to_cs_marshalers.set(MarshalerType.Action, _marshal_function_to_cs); js_to_cs_marshalers.set(MarshalerType.Function, _marshal_function_to_cs); js_to_cs_marshalers.set(MarshalerType.None, _marshal_null_to_cs);// also void @@ -292,9 +291,19 @@ function _marshal_function_to_cs(arg: JSMarshalerArgument, value: Function, _?: set_arg_type(arg, MarshalerType.Function);//TODO or action ? } -export class PromiseHolder extends ManagedObject { - public constructor(public promise: Promise) { - super(); +export class TaskCallbackHolder implements IDisposable { + public promise: Promise; + + public constructor(promise: Promise) { + this.promise = promise; + } + + dispose(): void { + teardown_managed_proxy(this, GCHandleNull); + } + + get isDisposed(): boolean { + return (this)[js_owned_gc_handle_symbol] === GCHandleNull; } } @@ -305,13 +314,13 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: } mono_check(isThenable(value), "Value is not a Promise"); - const gc_handle = alloc_gcv_handle(); + const gc_handle: GCHandle = runtimeHelpers.javaScriptExports.create_task_callback(); set_gc_handle(arg, gc_handle); set_arg_type(arg, MarshalerType.Task); - const holder = new PromiseHolder(value); + const holder = new TaskCallbackHolder(value); setup_managed_proxy(holder, gc_handle); if (BuildConfiguration === "Debug") { - (holder as any)[proxy_debug_symbol] = `PromiseHolder with GCVHandle ${gc_handle}`; + (holder as any)[proxy_debug_symbol] = `C# Task with GCHandle ${gc_handle}`; } if (MonoWasmThreads) @@ -324,10 +333,10 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: if (MonoWasmThreads) settleUnsettledPromise(); runtimeHelpers.javaScriptExports.complete_task(gc_handle, null, data, res_converter || _marshal_cs_object_to_cs); - teardown_managed_proxy(holder, gc_handle, true); // this holds holder alive for finalizer, until the promise is freed, (holding promise instead would not work) + teardown_managed_proxy(holder, gc_handle); // this holds holder alive for finalizer, until the promise is freed, (holding promise instead would not work) } catch (ex) { - runtimeHelpers.abort(ex); + mono_log_warn("Exception marshalling result of JS promise to CS: ", ex); } }).catch(reason => { try { @@ -336,10 +345,12 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: if (MonoWasmThreads) settleUnsettledPromise(); runtimeHelpers.javaScriptExports.complete_task(gc_handle, reason, null, undefined); - teardown_managed_proxy(holder, gc_handle, true); // this holds holder alive for finalizer, until the promise is freed + teardown_managed_proxy(holder, gc_handle); // this holds holder alive for finalizer, until the promise is freed } catch (ex) { - runtimeHelpers.abort(ex); + if (!loaderHelpers.is_exited()) { + mono_log_warn("Exception marshalling error of JS promise to CS: ", ex); + } } }); } diff --git a/src/mono/wasm/runtime/marshal-to-js.ts b/src/mono/wasm/runtime/marshal-to-js.ts index 21af5460110ee..1ad71dbc4ae78 100644 --- a/src/mono/wasm/runtime/marshal-to-js.ts +++ b/src/mono/wasm/runtime/marshal-to-js.ts @@ -5,20 +5,20 @@ import MonoWasmThreads from "consts:monoWasmThreads"; import BuildConfiguration from "consts:configuration"; import cwraps from "./cwraps"; -import { _lookup_js_owned_object, mono_wasm_get_jsobj_from_js_handle, mono_wasm_release_cs_owned_object, register_with_jsv_handle, setup_managed_proxy, teardown_managed_proxy } from "./gc-handles"; -import { Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; +import { _lookup_js_owned_object, mono_wasm_get_jsobj_from_js_handle, mono_wasm_get_js_handle, setup_managed_proxy, teardown_managed_proxy } from "./gc-handles"; +import { Module, createPromiseController, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { ManagedObject, ManagedError, get_arg_gc_handle, get_arg_js_handle, get_arg_type, get_arg_i32, get_arg_f64, get_arg_i52, get_arg_i16, get_arg_u8, get_arg_f32, - get_arg_b8, get_arg_date, get_arg_length, get_arg, set_arg_type, + get_arg_b8, get_arg_date, get_arg_length, set_js_handle, get_arg, set_arg_type, get_signature_arg2_type, get_signature_arg1_type, cs_to_js_marshalers, get_signature_res_type, get_arg_u16, array_element_size, get_string_root, ArraySegment, Span, MemoryViewType, get_signature_arg3_type, get_arg_i64_big, get_arg_intptr, get_arg_element_type, JavaScriptMarshalerArgSize, proxy_debug_symbol } from "./marshal"; import { monoStringToString } from "./strings"; -import { GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToJs, MarshalerType } from "./types/internal"; +import { JSHandleNull, GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToJs, MarshalerType } from "./types/internal"; import { TypedArray } from "./types/emscripten"; -import { get_marshaler_to_cs_by_type, jsinteropDoc, marshal_exception_to_cs } from "./marshal-to-cs"; +import { get_marshaler_to_cs_by_type, jsinteropDoc } from "./marshal-to-cs"; import { localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory"; export function initialize_marshalers_to_js(): void { @@ -44,8 +44,6 @@ export function initialize_marshalers_to_js(): void { cs_to_js_marshalers.set(MarshalerType.DateTime, _marshal_datetime_to_js); cs_to_js_marshalers.set(MarshalerType.DateTimeOffset, _marshal_datetime_to_js); cs_to_js_marshalers.set(MarshalerType.Task, marshal_task_to_js); - cs_to_js_marshalers.set(MarshalerType.TaskRejected, marshal_task_to_js); - cs_to_js_marshalers.set(MarshalerType.TaskResolved, marshal_task_to_js); cs_to_js_marshalers.set(MarshalerType.Action, _marshal_delegate_to_js); cs_to_js_marshalers.set(MarshalerType.Function, _marshal_delegate_to_js); cs_to_js_marshalers.set(MarshalerType.None, _marshal_null_to_js); @@ -214,95 +212,107 @@ function _marshal_delegate_to_js(arg: JSMarshalerArgument, _?: MarshalerType, re return result; } -export class TaskHolder { - constructor(public resolve_or_reject: (type: MarshalerType, argInner: JSMarshalerArgument) => void) { - } -} - export function marshal_task_to_js(arg: JSMarshalerArgument, _?: MarshalerType, res_converter?: MarshalerToJs): Promise | null { const type = get_arg_type(arg); if (type === MarshalerType.None) { return null; } - if (type === MarshalerType.TaskRejected) { - return Promise.reject(marshal_exception_to_js(arg)); - } - if (type === MarshalerType.TaskResolved) { - const element_type = get_arg_element_type(arg); - if (element_type === MarshalerType.Void) { - return Promise.resolve(); - } - // this will change the type to the actual type of the result - set_arg_type(arg, element_type); + + if (type !== MarshalerType.Task) { + if (!res_converter) { // when we arrived here from _marshal_cs_object_to_js - res_converter = cs_to_js_marshalers.get(element_type); + res_converter = cs_to_js_marshalers.get(type); } - mono_assert(res_converter, () => `Unknown sub_converter for type ${MarshalerType[element_type]}. ${jsinteropDoc}`); + mono_assert(res_converter, () => `Unknown sub_converter for type ${MarshalerType[type]}. ${jsinteropDoc}`); + // this is already resolved const val = res_converter(arg); - return Promise.resolve(val); + return new Promise((resolve) => resolve(val)); } - const jsv_handle = get_arg_js_handle(arg); - const { promise, promise_control } = loaderHelpers.createPromiseController(); + const js_handle = get_arg_js_handle(arg); + if (js_handle == JSHandleNull) { + // this is already resolved void + return new Promise((resolve) => resolve(undefined)); + } + const promise = mono_wasm_get_jsobj_from_js_handle(js_handle); + mono_assert(!!promise, () => `ERR28: promise not found for js_handle: ${js_handle} `); + if (BuildConfiguration === "Debug") { + (promise as any)[proxy_debug_symbol] = `JS Promise with JSHandle ${js_handle}`; + } + loaderHelpers.assertIsControllablePromise(promise); + const promise_control = loaderHelpers.getPromiseController(promise); - const holder = new TaskHolder((type, argInner) => { - if (type === MarshalerType.TaskRejected) { - const reason = marshal_exception_to_js(argInner); - promise_control.reject(reason); - } else if (type === MarshalerType.TaskResolved) { - const type = get_arg_type(argInner); - if (type === MarshalerType.Void) { - promise_control.resolve(undefined); - } else { - if (!res_converter) { - // when we arrived here from _marshal_cs_object_to_js - res_converter = cs_to_js_marshalers.get(type); - } - mono_assert(res_converter, () => `Unknown sub_converter for type ${MarshalerType[type]}. ${jsinteropDoc}`); - - const js_value = res_converter!(argInner); - promise_control.resolve(js_value); - } + const orig_resolve = promise_control.resolve; + promise_control.resolve = (argInner: JSMarshalerArgument) => { + const type = get_arg_type(argInner); + if (type === MarshalerType.None) { + orig_resolve(null); + return; } - else { - mono_assert(false, () => `Unexpected type ${MarshalerType[type]}`); + + if (!res_converter) { + // when we arrived here from _marshal_cs_object_to_js + res_converter = cs_to_js_marshalers.get(type); } - mono_wasm_release_cs_owned_object(jsv_handle); - }); - if (BuildConfiguration === "Debug") { - (holder as any)[proxy_debug_symbol] = `TaskHolder with JSVHandle ${jsv_handle}`; - } + mono_assert(res_converter, () => `Unknown sub_converter for type ${MarshalerType[type]}. ${jsinteropDoc}`); - register_with_jsv_handle(holder, jsv_handle); + const js_value = res_converter!(argInner); + orig_resolve(js_value); + }; return promise; } -export function mono_wasm_resolve_or_reject_promise(args: JSMarshalerArguments): void { +export function mono_wasm_marshal_promise(args: JSMarshalerArguments): void { const exc = get_arg(args, 0); - try { - loaderHelpers.assert_runtime_running(); - - const res = get_arg(args, 1); - const arg_handle = get_arg(args, 2); - const arg_value = get_arg(args, 3); - - const type = get_arg_type(arg_handle); - const jsv_handle = get_arg_js_handle(arg_handle); - - const holder = mono_wasm_get_jsobj_from_js_handle(jsv_handle) as TaskHolder; - mono_assert(holder, () => `Cannot find Promise for JSVHandle ${jsv_handle}`); - - holder.resolve_or_reject(type, arg_value); - - set_arg_type(res, MarshalerType.Void); - set_arg_type(exc, MarshalerType.None); + const res = get_arg(args, 1); + const arg_handle = get_arg(args, 2); + const arg_value = get_arg(args, 3); + + const exc_type = get_arg_type(exc); + const value_type = get_arg_type(arg_value); + const js_handle = get_arg_js_handle(arg_handle); + + if (js_handle === JSHandleNull) { + const { promise, promise_control } = createPromiseController(); + const js_handle = mono_wasm_get_js_handle(promise)!; + if (BuildConfiguration === "Debug" && Object.isExtensible(promise)) { + (promise as any)[proxy_debug_symbol] = `JS Promise with JSHandle ${js_handle}`; + } + set_js_handle(res, js_handle); - } catch (ex: any) { - marshal_exception_to_cs(exc, ex); + if (exc_type !== MarshalerType.None) { + // this is already failed task + const reason = marshal_exception_to_js(exc); + promise_control.reject(reason); + } + else if (value_type !== MarshalerType.Task) { + // this is already resolved task + const sub_converter = cs_to_js_marshalers.get(value_type); + mono_assert(sub_converter, () => `Unknown sub_converter for type ${MarshalerType[value_type]}. ${jsinteropDoc}`); + const data = sub_converter(arg_value); + promise_control.resolve(data); + } + } else { + // resolve existing promise + const promise = mono_wasm_get_jsobj_from_js_handle(js_handle); + mono_assert(!!promise, () => `ERR25: promise not found for js_handle: ${js_handle} `); + loaderHelpers.assertIsControllablePromise(promise); + const promise_control = loaderHelpers.getPromiseController(promise); + + if (exc_type !== MarshalerType.None) { + const reason = marshal_exception_to_js(exc); + promise_control.reject(reason); + } + else if (value_type !== MarshalerType.Task) { + // here we assume that resolve was wrapped with sub_converter inside _marshal_task_to_js + promise_control.resolve(arg_value); + } } + set_arg_type(res, MarshalerType.Task); + set_arg_type(exc, MarshalerType.None); } export function marshal_string_to_js(arg: JSMarshalerArgument): string | null { diff --git a/src/mono/wasm/runtime/marshal.ts b/src/mono/wasm/runtime/marshal.ts index 07afc1b2f67e4..262051dab6fb3 100644 --- a/src/mono/wasm/runtime/marshal.ts +++ b/src/mono/wasm/runtime/marshal.ts @@ -8,7 +8,7 @@ import { Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8, localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory"; import { mono_wasm_new_external_root } from "./roots"; import { GCHandle, JSHandle, MonoObject, MonoString, GCHandleNull, JSMarshalerArguments, JSFunctionSignature, JSMarshalerType, JSMarshalerArgument, MarshalerToJs, MarshalerToCs, WasmRoot, MarshalerType } from "./types/internal"; -import { TypedArray, VoidPtr } from "./types/emscripten"; +import { CharPtr, TypedArray, VoidPtr } from "./types/emscripten"; export const cs_to_js_marshalers = new Map(); export const js_to_cs_marshalers = new Map(); @@ -17,6 +17,28 @@ export const bound_js_function_symbol = Symbol.for("wasm bound_js_function"); export const imported_js_function_symbol = Symbol.for("wasm imported_js_function"); export const proxy_debug_symbol = Symbol.for("wasm proxy_debug"); +/** + * JSFunctionSignature is pointer to [ + * Version: number, + * ArgumentCount: number, + * exc: { jsType: JsTypeFlags, type:MarshalerType, restype:MarshalerType, arg1type:MarshalerType, arg2type:MarshalerType, arg3type:MarshalerType} + * res: { jsType: JsTypeFlags, type:MarshalerType, restype:MarshalerType, arg1type:MarshalerType, arg2type:MarshalerType, arg3type:MarshalerType} + * arg1: { jsType: JsTypeFlags, type:MarshalerType, restype:MarshalerType, arg1type:MarshalerType, arg2type:MarshalerType, arg3type:MarshalerType} + * arg2: { jsType: JsTypeFlags, type:MarshalerType, restype:MarshalerType, arg1type:MarshalerType, arg2type:MarshalerType, arg3type:MarshalerType} + * ... + * ] + * + * Layout of the call stack frame buffers is array of JSMarshalerArgument + * JSMarshalerArguments is pointer to [ + * exc: {type:MarshalerType, handle: IntPtr, data: Int64|Ref*|Void* }, + * res: {type:MarshalerType, handle: IntPtr, data: Int64|Ref*|Void* }, + * arg1: {type:MarshalerType, handle: IntPtr, data: Int64|Ref*|Void* }, + * arg2: {type:MarshalerType, handle: IntPtr, data: Int64|Ref*|Void* }, + * ... + * ] + */ + + export const JavaScriptMarshalerArgSize = 16; export const JSMarshalerTypeSize = 32; export const JSMarshalerSignatureHeaderSize = 4 + 4; // without Exception and Result @@ -49,27 +71,37 @@ export function get_sig(signature: JSFunctionSignature, index: number): JSMarsha export function get_signature_type(sig: JSMarshalerType): MarshalerType { mono_assert(sig, "Null sig"); - return getU8(sig); + return getU32(sig); } export function get_signature_res_type(sig: JSMarshalerType): MarshalerType { mono_assert(sig, "Null sig"); - return getU8(sig + 16); + return getU32(sig + 16); +} + +export function get_signature_custom_code(sig: JSMarshalerType): CharPtr { + mono_assert(sig, "Null sig"); + return getU32(sig + 8); +} + +export function get_signature_custom_code_len(sig: JSMarshalerType): number { + mono_assert(sig, "Null sig"); + return getU32(sig + 12); } export function get_signature_arg1_type(sig: JSMarshalerType): MarshalerType { mono_assert(sig, "Null sig"); - return getU8(sig + 20); + return getU32(sig + 20); } export function get_signature_arg2_type(sig: JSMarshalerType): MarshalerType { mono_assert(sig, "Null sig"); - return getU8(sig + 24); + return getU32(sig + 24); } export function get_signature_arg3_type(sig: JSMarshalerType): MarshalerType { mono_assert(sig, "Null sig"); - return getU8(sig + 28); + return getU32(sig + 28); } export function get_signature_argument_count(signature: JSFunctionSignature): number { @@ -84,29 +116,29 @@ export function get_signature_version(signature: JSFunctionSignature): number { export function get_sig_type(sig: JSMarshalerType): MarshalerType { mono_assert(sig, "Null signatures"); - return getU8(sig); + return getU32(sig); } export function get_arg_type(arg: JSMarshalerArgument): MarshalerType { mono_assert(arg, "Null arg"); - const type = getU8(arg + 12); + const type = getU32(arg + 12); return type; } export function get_arg_element_type(arg: JSMarshalerArgument): MarshalerType { mono_assert(arg, "Null arg"); - const type = getU8(arg + 13); + const type = getU32(arg + 4); return type; } export function set_arg_type(arg: JSMarshalerArgument, type: MarshalerType): void { mono_assert(arg, "Null arg"); - setU8(arg + 12, type); + setU32(arg + 12, type); } export function set_arg_element_type(arg: JSMarshalerArgument, type: MarshalerType): void { mono_assert(arg, "Null arg"); - setU8(arg + 13, type); + setU32(arg + 4, type); } export function get_arg_b8(arg: JSMarshalerArgument): boolean { @@ -136,7 +168,7 @@ export function get_arg_i32(arg: JSMarshalerArgument): number { export function get_arg_intptr(arg: JSMarshalerArgument): number { mono_assert(arg, "Null arg"); - return getI32(arg); + return getU32(arg); } export function get_arg_i52(arg: JSMarshalerArgument): number { @@ -195,7 +227,7 @@ export function set_arg_i32(arg: JSMarshalerArgument, value: number): void { export function set_arg_intptr(arg: JSMarshalerArgument, value: VoidPtr): void { mono_assert(arg, "Null arg"); - setI32(arg, value); + setU32(arg, value); } export function set_arg_i52(arg: JSMarshalerArgument, value: number): void { @@ -229,22 +261,22 @@ export function set_arg_f32(arg: JSMarshalerArgument, value: number): void { export function get_arg_js_handle(arg: JSMarshalerArgument): JSHandle { mono_assert(arg, "Null arg"); - return getI32(arg + 4); + return getU32(arg + 4); } export function set_js_handle(arg: JSMarshalerArgument, jsHandle: JSHandle): void { mono_assert(arg, "Null arg"); - setI32(arg + 4, jsHandle); + setU32(arg + 4, jsHandle); } export function get_arg_gc_handle(arg: JSMarshalerArgument): GCHandle { mono_assert(arg, "Null arg"); - return getI32(arg + 4); + return getU32(arg + 4); } export function set_gc_handle(arg: JSMarshalerArgument, gcHandle: GCHandle): void { mono_assert(arg, "Null arg"); - setI32(arg + 4, gcHandle); + setU32(arg + 4, gcHandle); } export function get_string_root(arg: JSMarshalerArgument): WasmRoot { diff --git a/src/mono/wasm/runtime/net6-legacy/method-calls.ts b/src/mono/wasm/runtime/net6-legacy/method-calls.ts index a2033b256f000..825e5e7d4107f 100644 --- a/src/mono/wasm/runtime/net6-legacy/method-calls.ts +++ b/src/mono/wasm/runtime/net6-legacy/method-calls.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { mono_wasm_get_jsobj_from_js_handle } from "../gc-handles"; +import { get_js_obj, mono_wasm_get_jsobj_from_js_handle } from "../gc-handles"; import { Module, INTERNAL, loaderHelpers } from "../globals"; import { wrap_error_root, wrap_no_error_root } from "../invoke-js"; import { _release_temp_frame } from "../memory"; @@ -103,7 +103,7 @@ export function mono_wasm_invoke_js_with_args_ref(js_handle: JSHandle, method_na return; } - const obj = mono_wasm_get_jsobj_from_js_handle(js_handle); + const obj = get_js_obj(js_handle); if (is_nullish(obj)) { wrap_error_root(is_exception, "ERR13: Invalid JS object handle '" + js_handle + "' while invoking '" + js_name + "'", resultRoot); return; diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 52fec4b1acfd5..4b209c8f1fa3a 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -105,12 +105,12 @@ export function configureEmscriptenStartup(module: DotnetModuleInternal): void { // execution order == [*] == if (!module.onAbort) { module.onAbort = (error) => { - loaderHelpers.mono_exit(1, loaderHelpers.exitReason || error); + loaderHelpers.mono_exit(1, error); }; } if (!module.onExit) { module.onExit = (code) => { - loaderHelpers.mono_exit(code, loaderHelpers.exitReason); + loaderHelpers.mono_exit(code, null); }; } } @@ -236,7 +236,6 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { runtimeHelpers.mono_wasm_exit = cwraps.mono_wasm_exit; runtimeHelpers.abort = (reason: any) => { - loaderHelpers.exitReason = reason; if (!loaderHelpers.is_exited()) { cwraps.mono_wasm_abort(); } diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index a410af06f1cee..8167bb328b824 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -109,7 +109,6 @@ export type LoaderHelpers = { assertAfterExit: boolean; exitCode: number | undefined; - exitReason: any | undefined; loadedFiles: string[], _loaded_files: { url: string, file: string }[]; @@ -339,8 +338,11 @@ export interface JavaScriptExports { // the marshaled signature is: void ReleaseJSOwnedObjectByGCHandle(GCHandle gcHandle) release_js_owned_object_by_gc_handle(gc_handle: GCHandle): void; + // the marshaled signature is: GCHandle CreateTaskCallback() + create_task_callback(): GCHandle; + // the marshaled signature is: void CompleteTask(GCHandle holder, Exception? exceptionResult, T? result) - complete_task(holder_gcv_handle: GCHandle, error?: any, data?: any, res_converter?: MarshalerToCs): void; + complete_task(holder_gc_handle: GCHandle, error?: any, data?: any, res_converter?: MarshalerToCs): void; // the marshaled signature is: TRes? CallDelegate(GCHandle callback, T1? arg1, T2? arg2, T3? arg3) call_delegate(callback_gc_handle: GCHandle, arg1_js: any, arg2_js: any, arg3_js: any, @@ -398,8 +400,6 @@ export enum MarshalerType { // only on runtime JSException, - TaskResolved, - TaskRejected, } export interface JSMarshalerArguments extends NativePointer { @@ -513,7 +513,3 @@ export type RuntimeModuleExportsInternal = { export type NativeModuleExportsInternal = { default: (unificator: Function) => EmscriptenModuleInternal } - -export type WeakRefInternal = WeakRef & { - dispose?: () => void -} \ No newline at end of file diff --git a/src/mono/wasm/runtime/weak-ref.ts b/src/mono/wasm/runtime/weak-ref.ts index ebb4ab7f8e08b..f5c6187f12d82 100644 --- a/src/mono/wasm/runtime/weak-ref.ts +++ b/src/mono/wasm/runtime/weak-ref.ts @@ -1,11 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { WeakRefInternal } from "./types/internal"; - export const _use_weak_ref = typeof globalThis.WeakRef === "function"; -export function create_weak_ref(js_obj: T): WeakRefInternal { +export function create_weak_ref(js_obj: T): WeakRef { if (_use_weak_ref) { return new WeakRef(js_obj); }