Skip to content

Commit

Permalink
[browser] introduce JSProxyContext (#95959)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelsavara authored Dec 20, 2023
1 parent 5cf6892 commit 8bd23f0
Show file tree
Hide file tree
Showing 54 changed files with 1,369 additions and 703 deletions.
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,6 @@ node_modules/
*.metaproj
*.metaproj.tmp
bin.localpkg/
src/mono/wasm/runtime/dotnet.d.ts.sha256
src/mono/wasm/runtime/dotnet-legacy.d.ts.sha256

src/mono/sample/wasm/browser-nextjs/public/

# RIA/Silverlight projects
Generated_Code/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ internal static unsafe partial class Runtime

#if FEATURE_WASM_THREADS
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void InstallWebWorkerInterop();
public static extern void InstallWebWorkerInterop(IntPtr proxyContextGCHandle);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void UninstallWebWorkerInterop();
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<TargetPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))</TargetPlatformIdentifier>
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'windows'">$(DefineConstants);TargetsWindows</DefineConstants>
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'browser'">$(DefineConstants);TARGETS_BROWSER</DefineConstants>
<!-- Active issue https://github.com/dotnet/runtime/issues/96173 -->
<_XUnitBackgroundExec>false</_XUnitBackgroundExec>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetOS)' == 'browser'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

<!-- This WASM test is problematic and slow right now. This sets the xharness timeout but there is also override in sendtohelix-browser.targets -->
<WasmXHarnessTestsTimeout>01:15:00</WasmXHarnessTestsTimeout>

<!-- Active issue https://github.com/dotnet/runtime/issues/96173 -->
<_XUnitBackgroundExec>false</_XUnitBackgroundExec>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public PrimitiveJSGenerator(MarshalerType marshalerType)
{
}

// TODO order parameters in such way that affinity capturing parameters are emitted first
public override IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeContext context)
{
string argName = context.GetAdditionalIdentifier(info, "js_arg");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<Compile Include="System\Runtime\InteropServices\JavaScript\JSImportAttribute.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\CancelablePromise.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\SynchronizationContextExtensions.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JSProxyContext.cs" />

<Compile Include="System\Runtime\InteropServices\JavaScript\MarshalerType.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Marshaling\JSMarshalerArgument.BigInt64.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.


using System.Threading;
using System.Threading.Tasks;

namespace System.Runtime.InteropServices.JavaScript
Expand All @@ -21,13 +22,24 @@ public static void CancelPromise(Task promise)
JSHostImplementation.PromiseHolder? holder = promise.AsyncState as JSHostImplementation.PromiseHolder;
if (holder == null) throw new InvalidOperationException("Expected Task converted from JS Promise");


#if FEATURE_WASM_THREADS
holder.SynchronizationContext!.Send(static (JSHostImplementation.PromiseHolder holder) =>
#if !FEATURE_WASM_THREADS
if (holder.IsDisposed)
{
#endif
return;
}
_CancelPromise(holder.GCHandle);
#if FEATURE_WASM_THREADS
#else
holder.ProxyContext.SynchronizationContext.Post(static (object? h) =>
{
var holder = (JSHostImplementation.PromiseHolder)h!;
lock (holder.ProxyContext)
{
if (holder.IsDisposed)
{
return;
}
}
_CancelPromise(holder.GCHandle);
}, holder);
#endif
}
Expand All @@ -42,15 +54,27 @@ public static void CancelPromise<T>(Task promise, Action<T> callback, T state)
JSHostImplementation.PromiseHolder? holder = promise.AsyncState as JSHostImplementation.PromiseHolder;
if (holder == null) throw new InvalidOperationException("Expected Task converted from JS Promise");


#if FEATURE_WASM_THREADS
holder.SynchronizationContext!.Send((JSHostImplementation.PromiseHolder holder) =>
#if !FEATURE_WASM_THREADS
if (holder.IsDisposed)
{
#endif
return;
}
_CancelPromise(holder.GCHandle);
callback.Invoke(state);
#else
holder.ProxyContext.SynchronizationContext.Post(_ =>
{
lock (holder.ProxyContext)
{
if (holder.IsDisposed)
{
return;
}
}
_CancelPromise(holder.GCHandle);
callback.Invoke(state);
#if FEATURE_WASM_THREADS
}, holder);
}, null);
#endif
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer)
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // initialized and set by caller
try
{
#if FEATURE_WASM_THREADS
// when we arrive here, we are on the thread which owns the proxies
arg_exc.AssertCurrentThreadContext();
#endif

arg_1.ToManaged(out IntPtr entrypointPtr);
if (entrypointPtr == IntPtr.Zero)
{
Expand Down Expand Up @@ -103,6 +108,10 @@ public static void LoadLazyAssembly(JSMarshalerArgument* arguments_buffer)
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3];
try
{
#if FEATURE_WASM_THREADS
// when we arrive here, we are on the thread which owns the proxies
arg_exc.AssertCurrentThreadContext();
#endif
arg_1.ToManaged(out byte[]? dllBytes);
arg_2.ToManaged(out byte[]? pdbBytes);

Expand All @@ -121,6 +130,10 @@ public static void LoadSatelliteAssembly(JSMarshalerArgument* arguments_buffer)
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];
try
{
#if FEATURE_WASM_THREADS
// when we arrive here, we are on the thread which owns the proxies
arg_exc.AssertCurrentThreadContext();
#endif
arg_1.ToManaged(out byte[]? dllBytes);

if (dllBytes != null)
Expand All @@ -140,32 +153,12 @@ public static void ReleaseJSOwnedObjectByGCHandle(JSMarshalerArgument* arguments
{
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2]; // initialized and set by caller

try
{
var gcHandle = arg_1.slot.GCHandle;
if (IsGCVHandle(gcHandle))
{
if (ThreadJsOwnedHolders.Remove(gcHandle, out PromiseHolder? holder))
{
holder.GCHandle = IntPtr.Zero;
holder.Callback!(null);
}
}
else
{
GCHandle handle = (GCHandle)gcHandle;
var target = handle.Target!;
if (target is PromiseHolder holder)
{
holder.GCHandle = IntPtr.Zero;
holder.Callback!(null);
}
else
{
ThreadJsOwnedObjects.Remove(target);
}
handle.Free();
}
// when we arrive here, we are on the thread which owns the proxies
var ctx = arg_exc.AssertCurrentThreadContext();
ctx.ReleaseJSOwnedObjectByGCHandle(arg_1.slot.GCHandle);
}
catch (Exception ex)
{
Expand All @@ -185,6 +178,11 @@ public static void CallDelegate(JSMarshalerArgument* arguments_buffer)
// arg_4 set by JS caller when there are arguments
try
{
#if FEATURE_WASM_THREADS
// when we arrive here, we are on the thread which owns the proxies
arg_exc.AssertCurrentThreadContext();
#endif

GCHandle callback_gc_handle = (GCHandle)arg_1.slot.GCHandle;
if (callback_gc_handle.Target is ToManagedCallback callback)
{
Expand All @@ -210,34 +208,34 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer)
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller
// arg_2 set by caller when this is SetException call
// arg_3 set by caller when this is SetResult call

try
{
var holderGCHandle = arg_1.slot.GCHandle;
if (IsGCVHandle(holderGCHandle))
// when we arrive here, we are on the thread which owns the proxies
var ctx = arg_exc.AssertCurrentThreadContext();
var holder = ctx.GetPromiseHolder(arg_1.slot.GCHandle);

#if FEATURE_WASM_THREADS
lock (ctx)
{
if (ThreadJsOwnedHolders.Remove(holderGCHandle, out PromiseHolder? holder))
if (holder.Callback == null)
{
holder.GCHandle = IntPtr.Zero;
// arg_2, arg_3 are processed by the callback
holder.Callback!(arguments_buffer);
holder.CallbackReady = new ManualResetEventSlim(false);
}
}
else
if (holder.CallbackReady != null)
{
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();
#pragma warning disable CA1416 // Validate platform compatibility
holder.CallbackReady?.Wait();
#pragma warning restore CA1416 // Validate platform compatibility
}
#endif
var callback = holder.Callback!;
ctx.ReleasePromiseHolder(arg_1.slot.GCHandle);

// arg_2, arg_3 are processed by the callback
// JSProxyContext.PopOperation() is called by the callback
callback!(arguments_buffer);
}
catch (Exception ex)
{
Expand All @@ -254,6 +252,9 @@ public static void GetManagedStackTrace(JSMarshalerArgument* arguments_buffer)
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller
try
{
// when we arrive here, we are on the thread which owns the proxies
arg_exc.AssertCurrentThreadContext();

GCHandle exception_gc_handle = (GCHandle)arg_1.slot.GCHandle;
if (exception_gc_handle.Target is Exception exception)
{
Expand All @@ -275,17 +276,10 @@ public static void GetManagedStackTrace(JSMarshalerArgument* arguments_buffer)
// this is here temporarily, until JSWebWorker becomes public API
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker", "System.Runtime.InteropServices.JavaScript")]
// the marshaled signature is:
// void InstallSynchronizationContext()
public static void InstallSynchronizationContext (JSMarshalerArgument* arguments_buffer) {
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
try
{
InstallWebWorkerInterop(true);
}
catch (Exception ex)
{
arg_exc.ToJS(ex);
}
// void InstallMainSynchronizationContext()
public static void InstallMainSynchronizationContext()
{
InstallWebWorkerInterop(true);
}

#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,6 @@ namespace System.Runtime.InteropServices.JavaScript
{
internal static unsafe partial class JavaScriptImports
{
public static void ResolveOrRejectPromise(Span<JSMarshalerArgument> arguments)
{
fixed (JSMarshalerArgument* ptr = arguments)
{
Interop.Runtime.ResolveOrRejectPromise(ptr);
ref JSMarshalerArgument exceptionArg = ref arguments[0];
if (exceptionArg.slot.Type != MarshalerType.None)
{
JSHostImplementation.ThrowException(ref exceptionArg);
}
}
}

#if !DISABLE_LEGACY_JS_INTEROP
#region legacy

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,10 @@ internal static void PreventTrimming()

public static void GetCSOwnedObjectByJSHandleRef(nint jsHandle, int shouldAddInflight, out JSObject? result)
{
if (JSHostImplementation.ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference))
{
reference.TryGetTarget(out JSObject? jsObject);
if (shouldAddInflight != 0)
{
jsObject?.AddInFlight();
}
result = jsObject;
return;
}
result = null;
#if FEATURE_WASM_THREADS
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
#endif
result = JSProxyContext.MainThreadContext.GetCSOwnedObjectByJSHandle(jsHandle, shouldAddInflight);
}

public static IntPtr GetCSOwnedObjectJSHandleRef(in JSObject jsObject, int shouldAddInflight)
Expand Down Expand Up @@ -71,32 +64,7 @@ public static void CreateCSOwnedProxyRef(nint jsHandle, LegacyHostImplementation
#if FEATURE_WASM_THREADS
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
#endif

JSObject? res = null;

if (!JSHostImplementation.ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference) ||
!reference.TryGetTarget(out res) ||
res.IsDisposed)
{
#pragma warning disable CS0612 // Type or member is obsolete
res = mappedType switch
{
LegacyHostImplementation.MappedType.JSObject => new JSObject(jsHandle),
LegacyHostImplementation.MappedType.Array => new Array(jsHandle),
LegacyHostImplementation.MappedType.ArrayBuffer => new ArrayBuffer(jsHandle),
LegacyHostImplementation.MappedType.DataView => new DataView(jsHandle),
LegacyHostImplementation.MappedType.Function => new Function(jsHandle),
LegacyHostImplementation.MappedType.Uint8Array => new Uint8Array(jsHandle),
_ => throw new ArgumentOutOfRangeException(nameof(mappedType))
};
#pragma warning restore CS0612 // Type or member is obsolete
JSHostImplementation.ThreadCsOwnedObjects[jsHandle] = new WeakReference<JSObject>(res, trackResurrection: true);
}
if (shouldAddInflight != 0)
{
res.AddInFlight();
}
jsObject = res;
jsObject = JSProxyContext.MainThreadContext.CreateCSOwnedProxy(jsHandle, mappedType, shouldAddInflight);
}

public static void GetJSOwnedObjectByGCHandleRef(int gcHandle, out object result)
Expand All @@ -107,7 +75,7 @@ public static void GetJSOwnedObjectByGCHandleRef(int gcHandle, out object result

public static IntPtr GetJSOwnedObjectGCHandleRef(in object obj)
{
return JSHostImplementation.GetJSOwnedObjectGCHandle(obj, GCHandleType.Normal);
return JSProxyContext.MainThreadContext.GetJSOwnedObjectGCHandle(obj, GCHandleType.Normal);
}

public static IntPtr CreateTaskSource()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ public override string? StackTrace
}

#if FEATURE_WASM_THREADS
var currentTID = JSSynchronizationContext.CurrentJSSynchronizationContext?.TargetTID;
if (jsException.OwnerTID != currentTID)
if (!jsException.ProxyContext.IsCurrentThread())
{
return bs;
// if we are on another thread, it would be too expensive and risky to obtain lazy stack trace.
return bs + Environment.NewLine + "... omitted JavaScript stack trace from another thread.";
}
#endif
string? jsStackTrace = jsException.GetPropertyAsString("stack");
Expand Down
Loading

0 comments on commit 8bd23f0

Please sign in to comment.