Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[browser] introduce JSProxyContext #95959

Merged
merged 26 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
return bs + Environment.NewLine + "... omitted JavaScript stack trace from another thread.";
}
#endif
string? jsStackTrace = jsException.GetPropertyAsString("stack");
Expand Down
Loading
Loading