Skip to content

Commit

Permalink
[browser] eager allocation of Promise/Task result for async JSImport/…
Browse files Browse the repository at this point in the history
…JSExport (#95411)
  • Loading branch information
pavelsavara committed Dec 2, 2023
1 parent 712c5e9 commit 5004872
Show file tree
Hide file tree
Showing 25 changed files with 420 additions and 128 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{569E6837-077
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{67F3A00A-AE6C-434C-927D-E5D38DE2DA2C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "tools\gen", "{39B30F44-B141-44E9-B7A7-B1A9EDB1A61C}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{39B30F44-B141-44E9-B7A7-B1A9EDB1A61C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "tools\src", "{D819DEB6-6F6B-484B-9F0F-BDBCEDC83A1A}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D819DEB6-6F6B-484B-9F0F-BDBCEDC83A1A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "tools\ref", "{32CDDDCD-5319-494C-AB41-42B87C467F04}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{32CDDDCD-5319-494C-AB41-42B87C467F04}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{F92020A9-28BE-4398-86FF-5CFE44C94882}"
EndProject
Expand Down Expand Up @@ -135,28 +135,32 @@ Global
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{ED86AB26-1CFB-457D-BF87-B7A0D8FAF272} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C}
{8B1D80E9-AE0D-4E3C-9F91-E6862B49AEF3} = {1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF}
{CE5E53C1-F9B5-41EE-8D00-837913EC57D1} = {569E6837-0771-4C08-BB09-460281030538}
{28278E01-BF5C-4AB6-AA7A-8DD4E6C04DB1} = {1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF}
{71A845ED-4344-41FC-8FCA-3AC9B6BA6C45} = {67F3A00A-AE6C-434C-927D-E5D38DE2DA2C}
{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} = {1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF}
{28278E01-BF5C-4AB6-AA7A-8DD4E6C04DB1} = {1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF}
{44BAE6F1-94C2-415B-9A16-3B8EC429B09B} = {1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF}
{CE5E53C1-F9B5-41EE-8D00-837913EC57D1} = {569E6837-0771-4C08-BB09-460281030538}
{FB12C247-AFEF-4772-BB0C-983969B6CF32} = {569E6837-0771-4C08-BB09-460281030538}
{09AA6758-0BD3-4312-9C07-AE9F1D50A3AD} = {569E6837-0771-4C08-BB09-460281030538}
{B4E3E774-2C16-4CBF-87EF-88C547529B94} = {569E6837-0771-4C08-BB09-460281030538}
{71A845ED-4344-41FC-8FCA-3AC9B6BA6C45} = {67F3A00A-AE6C-434C-927D-E5D38DE2DA2C}
{EC3ADEFA-1FF3-482C-8CCB-FE4C77292532} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C}
{44BAE6F1-94C2-415B-9A16-3B8EC429B09B} = {1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF}
{4D8B7538-D933-4F3A-818D-4E19ABA7E182} = {39B30F44-B141-44E9-B7A7-B1A9EDB1A61C}
{6C60944F-4FE1-450F-884B-D523EDFCFAB3} = {39B30F44-B141-44E9-B7A7-B1A9EDB1A61C}
{39B30F44-B141-44E9-B7A7-B1A9EDB1A61C} = {F92020A9-28BE-4398-86FF-5CFE44C94882}
{B8F2E56D-6571-466D-9EF2-9FCAD3FC6E5B} = {D819DEB6-6F6B-484B-9F0F-BDBCEDC83A1A}
{42F9A600-BEC3-4F87-97EE-38E0DCAABC5A} = {D819DEB6-6F6B-484B-9F0F-BDBCEDC83A1A}
{D819DEB6-6F6B-484B-9F0F-BDBCEDC83A1A} = {F92020A9-28BE-4398-86FF-5CFE44C94882}
{008873D5-9028-4FF3-8354-71F713748625} = {32CDDDCD-5319-494C-AB41-42B87C467F04}
{39B30F44-B141-44E9-B7A7-B1A9EDB1A61C} = {F92020A9-28BE-4398-86FF-5CFE44C94882}
{D819DEB6-6F6B-484B-9F0F-BDBCEDC83A1A} = {F92020A9-28BE-4398-86FF-5CFE44C94882}
{32CDDDCD-5319-494C-AB41-42B87C467F04} = {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
Original file line number Diff line number Diff line change
Expand Up @@ -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 gcHandle);

public static void CancelPromise(Task promise)
{
Expand All @@ -26,7 +26,7 @@ public static void CancelPromise(Task promise)
holder.SynchronizationContext!.Send(static (JSHostImplementation.PromiseHolder holder) =>
{
#endif
_CancelPromise(holder.GCVHandle);
_CancelPromise(holder.GCHandle);
#if FEATURE_WASM_THREADS
}, holder);
#endif
Expand All @@ -47,7 +47,7 @@ public static void CancelPromise<T>(Task promise, Action<T> callback, T state)
holder.SynchronizationContext!.Send((JSHostImplementation.PromiseHolder holder) =>
{
#endif
_CancelPromise(holder.GCVHandle);
_CancelPromise(holder.GCHandle);
callback.Invoke(state);
#if FEATURE_WASM_THREADS
}, holder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,27 @@ public static void ReleaseJSOwnedObjectByGCHandle(JSMarshalerArgument* arguments
try
{
var gcHandle = arg_1.slot.GCHandle;
if (IsGCVHandle(gcHandle) && ThreadJsOwnedHolders.Remove(gcHandle, out PromiseHolder? holder))
if (IsGCVHandle(gcHandle))
{
holder.GCVHandle = IntPtr.Zero;
holder.Callback!(null);
if (ThreadJsOwnedHolders.Remove(gcHandle, out PromiseHolder? holder))
{
holder.GCHandle = IntPtr.Zero;
holder.Callback!(null);
}
}
else
{
GCHandle handle = (GCHandle)gcHandle;
ThreadJsOwnedObjects.Remove(handle.Target!);
var target = handle.Target!;
if (target is PromiseHolder holder)
{
holder.GCHandle = IntPtr.Zero;
holder.Callback!(null);
}
else
{
ThreadJsOwnedObjects.Remove(target);
}
handle.Free();
}
}
Expand Down Expand Up @@ -191,7 +203,7 @@ public static void CallDelegate(JSMarshalerArgument* arguments_buffer)
}

// the marshaled signature is:
// void CompleteTask<T>(GCVHandle holder, Exception? exceptionResult, T? result)
// void CompleteTask<T>(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()
Expand All @@ -200,17 +212,31 @@ 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)
var holderGCHandle = arg_1.slot.GCHandle;
if (IsGCVHandle(holderGCHandle))
{
promiseHolder.GCVHandle = IntPtr.Zero;

// arg_2, arg_3 are processed by the callback
promiseHolder.Callback(arguments_buffer);
if (ThreadJsOwnedHolders.Remove(holderGCHandle, out PromiseHolder? holder))
{
holder.GCHandle = IntPtr.Zero;
// arg_2, arg_3 are processed by the callback
holder.Callback!(arguments_buffer);
}
}
else
{
throw new InvalidOperationException(SR.NullPromiseHolder);
GCHandle handle = (GCHandle)holderGCHandle;
var target = handle.Target!;
if (target is PromiseHolder holder)
{
holder.GCHandle = IntPtr.Zero;
// arg_2, arg_3 are processed by the callback
holder.Callback!(arguments_buffer);
}
else
{
ThreadJsOwnedObjects.Remove(target);
}
handle.Free();
}
}
catch (Exception ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ public override string? StackTrace
}

#if FEATURE_WASM_THREADS
if (jsException.OwnerThreadId != Thread.CurrentThread.ManagedThreadId)
var currentTID = JSSynchronizationContext.CurrentJSSynchronizationContext?.TargetTID;
if (jsException.OwnerTID != currentTID)
{
return bs;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ public static JSFunctionBinding BindManagedFunction(string fullyQualifiedName, i
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe void InvokeJSImpl(JSObject jsFunction, Span<JSMarshalerArgument> arguments)
internal static unsafe void InvokeJSFunction(JSObject jsFunction, Span<JSMarshalerArgument> arguments)
{
ObjectDisposedException.ThrowIf(jsFunction.IsDisposed, jsFunction);
#if FEATURE_WASM_THREADS
Expand All @@ -220,6 +220,19 @@ internal static unsafe void InvokeJSImpl(JSObject jsFunction, Span<JSMarshalerAr
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span<JSMarshalerArgument> arguments)
{
if (signature.IsAsync)
{
// pre-allocate the result handle and Task
#if FEATURE_WASM_THREADS
JSSynchronizationContext.AssertWebWorkerContext();
var holder = new JSHostImplementation.PromiseHolder(JSSynchronizationContext.CurrentJSSynchronizationContext!);
#else
var holder = new JSHostImplementation.PromiseHolder();
#endif
arguments[1].slot.Type = MarshalerType.TaskPreCreated;
arguments[1].slot.GCHandle = holder.GCHandle;
}

fixed (JSMarshalerArgument* ptr = arguments)
{
Interop.Runtime.InvokeJSImport(signature.ImportHandle, ptr);
Expand All @@ -229,6 +242,15 @@ internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span
JSHostImplementation.ThrowException(ref exceptionArg);
}
}
if (signature.IsAsync)
{
// if js synchronously returned null
if (arguments[1].slot.Type == MarshalerType.None)
{
var holderHandle = (GCHandle)arguments[1].slot.GCHandle;
holderHandle.Free();
}
}
}

internal static unsafe JSFunctionBinding BindJSFunctionImpl(string functionName, string moduleName, ReadOnlySpan<JSMarshalerType> signatures)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,33 @@ internal static partial class JSHostImplementation

public sealed class PromiseHolder
{
public nint GCVHandle;
public nint GCHandle; // could be also virtual GCVHandle
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;
internal JSSynchronizationContext SynchronizationContext;
#endif

#if FEATURE_WASM_THREADS
// TODO possibly unify signature with non-MT and pass null
public PromiseHolder(JSSynchronizationContext targetContext)
{
GCHandle = (IntPtr)InteropServices.GCHandle.Alloc(this, GCHandleType.Normal);
SynchronizationContext = targetContext;
}
#else
public PromiseHolder()
{
GCHandle = (IntPtr)InteropServices.GCHandle.Alloc(this, GCHandleType.Normal);
}
#endif

public PromiseHolder(nint gcvHandle)
{
this.GCVHandle = gcvHandle;
GCHandle = gcvHandle;
#if FEATURE_WASM_THREADS
this.OwnerThreadId = Thread.CurrentThread.ManagedThreadId;
this.SynchronizationContext = SynchronizationContext.Current ?? new SynchronizationContext();
JSSynchronizationContext.AssertWebWorkerContext();
SynchronizationContext = JSSynchronizationContext.CurrentJSSynchronizationContext!;
#endif
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ public static List<nint> JSVHandleFreeList

public static nint AllocJSVHandle()
{
#if FEATURE_WASM_THREADS
// TODO, when Task is passed to JSImport as parameter, it could be sent from another thread (in the future)
// and so we need to use JSVHandleFreeList of the target thread
JSSynchronizationContext.AssertWebWorkerContext();
#endif

if (JSVHandleFreeList.Count > 0)
{
var jsvHandle = JSVHandleFreeList[JSVHandleFreeList.Count];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public SynchronizationContext SynchronizationContext

#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 nint OwnerTID;
#endif
#if !DISABLE_LEGACY_JS_INTEROP
internal GCHandle? InFlight;
Expand All @@ -42,12 +42,13 @@ internal JSObject(IntPtr jsHandle)
{
JSHandle = jsHandle;
#if FEATURE_WASM_THREADS
OwnerThreadId = Thread.CurrentThread.ManagedThreadId;
m_SynchronizationContext = JSSynchronizationContext.CurrentJSSynchronizationContext;
if (m_SynchronizationContext == null)
var ctx = JSSynchronizationContext.CurrentJSSynchronizationContext;
if (ctx == null)
{
throw new InvalidOperationException(); // should not happen
Environment.FailFast("Missing CurrentJSSynchronizationContext");
}
m_SynchronizationContext = ctx;
OwnerTID = ctx!.TargetTID;
#endif
}

Expand Down Expand Up @@ -93,16 +94,19 @@ internal static void AssertThreadAffinity(object value)
{
return;
}
else if (value is JSObject jsObject)
JSSynchronizationContext.AssertWebWorkerContext();
var currentTID = JSSynchronizationContext.CurrentJSSynchronizationContext!.TargetTID;

if (value is JSObject jsObject)
{
if (jsObject.OwnerThreadId != Thread.CurrentThread.ManagedThreadId)
if (jsObject.OwnerTID != currentTID)
{
throw new InvalidOperationException("The JavaScript object can be used only on the thread where it was created.");
}
}
else if (value is JSException jsException)
{
if (jsException.jsException != null && jsException.jsException.OwnerThreadId != Thread.CurrentThread.ManagedThreadId)
if (jsException.jsException != null && jsException.jsException.OwnerTID != currentTID)
{
throw new InvalidOperationException("The JavaScript object can be used only on the thread where it was created.");
}
Expand Down Expand Up @@ -131,8 +135,21 @@ private void DisposeThis()
if (!_isDisposed)
{
#if FEATURE_WASM_THREADS
SynchronizationContext.Send(static (JSObject self) =>
if (SynchronizationContext == SynchronizationContext.Current)
{
lock (_thisLock)
{
JSHostImplementation.ReleaseCSOwnedObject(JSHandle);
_isDisposed = true;
JSHandle = IntPtr.Zero;
m_SynchronizationContext = null;
} //lock
return;
}

SynchronizationContext.Post(static (object? s) =>
{
var self = (JSObject)s!;
lock (self._thisLock)
{
JSHostImplementation.ReleaseCSOwnedObject(self.JSHandle);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ internal enum MarshalerType : byte
JSException,
TaskResolved,
TaskRejected,
TaskPreCreated,
#endif
}
}
Loading

0 comments on commit 5004872

Please sign in to comment.