[browser] introduce JSProxyContext (#95959)

This commit is contained in:
Pavel Savara 2023-12-20 08:57:32 +01:00 committed by GitHub
parent 5cf6892d8d
commit 8bd23f0792
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 1372 additions and 706 deletions

4
.gitignore vendored
View File

@ -187,10 +187,6 @@ node_modules/
*.metaproj *.metaproj
*.metaproj.tmp *.metaproj.tmp
bin.localpkg/ 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 # RIA/Silverlight projects
Generated_Code/ Generated_Code/

View File

@ -34,7 +34,7 @@ internal static partial class Interop
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
[MethodImpl(MethodImplOptions.InternalCall)] [MethodImpl(MethodImplOptions.InternalCall)]
public static extern void InstallWebWorkerInterop(); public static extern void InstallWebWorkerInterop(IntPtr proxyContextGCHandle);
[MethodImpl(MethodImplOptions.InternalCall)] [MethodImpl(MethodImplOptions.InternalCall)]
public static extern void UninstallWebWorkerInterop(); public static extern void UninstallWebWorkerInterop();
#endif #endif

View File

@ -16,6 +16,8 @@
<TargetPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))</TargetPlatformIdentifier> <TargetPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))</TargetPlatformIdentifier>
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'windows'">$(DefineConstants);TargetsWindows</DefineConstants> <DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'windows'">$(DefineConstants);TargetsWindows</DefineConstants>
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'browser'">$(DefineConstants);TARGETS_BROWSER</DefineConstants> <DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'browser'">$(DefineConstants);TARGETS_BROWSER</DefineConstants>
<!-- Active issue https://github.com/dotnet/runtime/issues/96173 -->
<_XUnitBackgroundExec>false</_XUnitBackgroundExec>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetOS)' == 'browser'"> <PropertyGroup Condition="'$(TargetOS)' == 'browser'">

View File

@ -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 --> <!-- 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> <WasmXHarnessTestsTimeout>01:15:00</WasmXHarnessTestsTimeout>
<!-- Active issue https://github.com/dotnet/runtime/issues/96173 -->
<_XUnitBackgroundExec>false</_XUnitBackgroundExec>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -21,6 +21,7 @@ namespace Microsoft.Interop.JavaScript
{ {
} }
// TODO order parameters in such way that affinity capturing parameters are emitted first
public override IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeContext context) public override IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeContext context)
{ {
string argName = context.GetAdditionalIdentifier(info, "js_arg"); string argName = context.GetAdditionalIdentifier(info, "js_arg");

View File

@ -46,6 +46,7 @@
<Compile Include="System\Runtime\InteropServices\JavaScript\JSImportAttribute.cs" /> <Compile Include="System\Runtime\InteropServices\JavaScript\JSImportAttribute.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\CancelablePromise.cs" /> <Compile Include="System\Runtime\InteropServices\JavaScript\CancelablePromise.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\SynchronizationContextExtensions.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\MarshalerType.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Marshaling\JSMarshalerArgument.BigInt64.cs" /> <Compile Include="System\Runtime\InteropServices\JavaScript\Marshaling\JSMarshalerArgument.BigInt64.cs" />

View File

@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace System.Runtime.InteropServices.JavaScript namespace System.Runtime.InteropServices.JavaScript
@ -21,13 +22,24 @@ namespace System.Runtime.InteropServices.JavaScript
JSHostImplementation.PromiseHolder? holder = promise.AsyncState as JSHostImplementation.PromiseHolder; JSHostImplementation.PromiseHolder? holder = promise.AsyncState as JSHostImplementation.PromiseHolder;
if (holder == null) throw new InvalidOperationException("Expected Task converted from JS Promise"); if (holder == null) throw new InvalidOperationException("Expected Task converted from JS Promise");
#if !FEATURE_WASM_THREADS
#if FEATURE_WASM_THREADS if (holder.IsDisposed)
holder.SynchronizationContext!.Send(static (JSHostImplementation.PromiseHolder holder) =>
{ {
#endif return;
}
_CancelPromise(holder.GCHandle); _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); }, holder);
#endif #endif
} }
@ -42,15 +54,27 @@ namespace System.Runtime.InteropServices.JavaScript
JSHostImplementation.PromiseHolder? holder = promise.AsyncState as JSHostImplementation.PromiseHolder; JSHostImplementation.PromiseHolder? holder = promise.AsyncState as JSHostImplementation.PromiseHolder;
if (holder == null) throw new InvalidOperationException("Expected Task converted from JS Promise"); if (holder == null) throw new InvalidOperationException("Expected Task converted from JS Promise");
#if !FEATURE_WASM_THREADS
#if FEATURE_WASM_THREADS if (holder.IsDisposed)
holder.SynchronizationContext!.Send((JSHostImplementation.PromiseHolder holder) =>
{ {
#endif return;
}
_CancelPromise(holder.GCHandle);
callback.Invoke(state);
#else
holder.ProxyContext.SynchronizationContext.Post(_ =>
{
lock (holder.ProxyContext)
{
if (holder.IsDisposed)
{
return;
}
}
_CancelPromise(holder.GCHandle); _CancelPromise(holder.GCHandle);
callback.Invoke(state); callback.Invoke(state);
#if FEATURE_WASM_THREADS }, null);
}, holder);
#endif #endif
} }
} }

View File

@ -26,6 +26,11 @@ namespace System.Runtime.InteropServices.JavaScript
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // initialized and set by caller ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // initialized and set by caller
try 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); arg_1.ToManaged(out IntPtr entrypointPtr);
if (entrypointPtr == IntPtr.Zero) if (entrypointPtr == IntPtr.Zero)
{ {
@ -103,6 +108,10 @@ namespace System.Runtime.InteropServices.JavaScript
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; ref JSMarshalerArgument arg_2 = ref arguments_buffer[3];
try 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_1.ToManaged(out byte[]? dllBytes);
arg_2.ToManaged(out byte[]? pdbBytes); arg_2.ToManaged(out byte[]? pdbBytes);
@ -121,6 +130,10 @@ namespace System.Runtime.InteropServices.JavaScript
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2]; ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];
try 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_1.ToManaged(out byte[]? dllBytes);
if (dllBytes != null) if (dllBytes != null)
@ -140,32 +153,12 @@ namespace System.Runtime.InteropServices.JavaScript
{ {
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame() 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 ref JSMarshalerArgument arg_1 = ref arguments_buffer[2]; // initialized and set by caller
try try
{ {
var gcHandle = arg_1.slot.GCHandle; // when we arrive here, we are on the thread which owns the proxies
if (IsGCVHandle(gcHandle)) var ctx = arg_exc.AssertCurrentThreadContext();
{ ctx.ReleaseJSOwnedObjectByGCHandle(arg_1.slot.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();
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -185,6 +178,11 @@ namespace System.Runtime.InteropServices.JavaScript
// arg_4 set by JS caller when there are arguments // arg_4 set by JS caller when there are arguments
try 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; GCHandle callback_gc_handle = (GCHandle)arg_1.slot.GCHandle;
if (callback_gc_handle.Target is ToManagedCallback callback) if (callback_gc_handle.Target is ToManagedCallback callback)
{ {
@ -210,34 +208,34 @@ namespace System.Runtime.InteropServices.JavaScript
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller
// arg_2 set by caller when this is SetException call // arg_2 set by caller when this is SetException call
// arg_3 set by caller when this is SetResult call // arg_3 set by caller when this is SetResult call
try try
{ {
var holderGCHandle = arg_1.slot.GCHandle; // when we arrive here, we are on the thread which owns the proxies
if (IsGCVHandle(holderGCHandle)) 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; holder.CallbackReady = new ManualResetEventSlim(false);
// arg_2, arg_3 are processed by the callback
holder.Callback!(arguments_buffer);
} }
} }
else if (holder.CallbackReady != null)
{ {
GCHandle handle = (GCHandle)holderGCHandle; #pragma warning disable CA1416 // Validate platform compatibility
var target = handle.Target!; holder.CallbackReady?.Wait();
if (target is PromiseHolder holder) #pragma warning restore CA1416 // Validate platform compatibility
{
holder.GCHandle = IntPtr.Zero;
// arg_2, arg_3 are processed by the callback
holder.Callback!(arguments_buffer);
}
else
{
ThreadJsOwnedObjects.Remove(target);
}
handle.Free();
} }
#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) catch (Exception ex)
{ {
@ -254,6 +252,9 @@ namespace System.Runtime.InteropServices.JavaScript
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller
try 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; GCHandle exception_gc_handle = (GCHandle)arg_1.slot.GCHandle;
if (exception_gc_handle.Target is Exception exception) if (exception_gc_handle.Target is Exception exception)
{ {
@ -275,17 +276,10 @@ namespace System.Runtime.InteropServices.JavaScript
// this is here temporarily, until JSWebWorker becomes public API // this is here temporarily, until JSWebWorker becomes public API
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker", "System.Runtime.InteropServices.JavaScript")] [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker", "System.Runtime.InteropServices.JavaScript")]
// the marshaled signature is: // the marshaled signature is:
// void InstallSynchronizationContext() // void InstallMainSynchronizationContext()
public static void InstallSynchronizationContext (JSMarshalerArgument* arguments_buffer) { public static void InstallMainSynchronizationContext()
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame() {
try InstallWebWorkerInterop(true);
{
InstallWebWorkerInterop(true);
}
catch (Exception ex)
{
arg_exc.ToJS(ex);
}
} }
#endif #endif

View File

@ -7,19 +7,6 @@ namespace System.Runtime.InteropServices.JavaScript
{ {
internal static unsafe partial class JavaScriptImports 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 #if !DISABLE_LEGACY_JS_INTEROP
#region legacy #region legacy

View File

@ -32,17 +32,10 @@ namespace System.Runtime.InteropServices.JavaScript
public static void GetCSOwnedObjectByJSHandleRef(nint jsHandle, int shouldAddInflight, out JSObject? result) public static void GetCSOwnedObjectByJSHandleRef(nint jsHandle, int shouldAddInflight, out JSObject? result)
{ {
if (JSHostImplementation.ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference)) #if FEATURE_WASM_THREADS
{ LegacyHostImplementation.ThrowIfLegacyWorkerThread();
reference.TryGetTarget(out JSObject? jsObject); #endif
if (shouldAddInflight != 0) result = JSProxyContext.MainThreadContext.GetCSOwnedObjectByJSHandle(jsHandle, shouldAddInflight);
{
jsObject?.AddInFlight();
}
result = jsObject;
return;
}
result = null;
} }
public static IntPtr GetCSOwnedObjectJSHandleRef(in JSObject jsObject, int shouldAddInflight) public static IntPtr GetCSOwnedObjectJSHandleRef(in JSObject jsObject, int shouldAddInflight)
@ -71,32 +64,7 @@ namespace System.Runtime.InteropServices.JavaScript
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
LegacyHostImplementation.ThrowIfLegacyWorkerThread(); LegacyHostImplementation.ThrowIfLegacyWorkerThread();
#endif #endif
jsObject = JSProxyContext.MainThreadContext.CreateCSOwnedProxy(jsHandle, mappedType, shouldAddInflight);
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;
} }
public static void GetJSOwnedObjectByGCHandleRef(int gcHandle, out object result) public static void GetJSOwnedObjectByGCHandleRef(int gcHandle, out object result)
@ -107,7 +75,7 @@ namespace System.Runtime.InteropServices.JavaScript
public static IntPtr GetJSOwnedObjectGCHandleRef(in object obj) public static IntPtr GetJSOwnedObjectGCHandleRef(in object obj)
{ {
return JSHostImplementation.GetJSOwnedObjectGCHandle(obj, GCHandleType.Normal); return JSProxyContext.MainThreadContext.GetJSOwnedObjectGCHandle(obj, GCHandleType.Normal);
} }
public static IntPtr CreateTaskSource() public static IntPtr CreateTaskSource()

View File

@ -47,10 +47,10 @@ namespace System.Runtime.InteropServices.JavaScript
} }
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
var currentTID = JSSynchronizationContext.CurrentJSSynchronizationContext?.TargetTID; if (!jsException.ProxyContext.IsCurrentThread())
if (jsException.OwnerTID != currentTID)
{ {
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 #endif
string? jsStackTrace = jsException.GetPropertyAsString("stack"); string? jsStackTrace = jsException.GetPropertyAsString("stack");

View File

@ -30,9 +30,6 @@ namespace System.Runtime.InteropServices.JavaScript
internal static volatile uint nextImportHandle = 1; internal static volatile uint nextImportHandle = 1;
internal int ImportHandle; internal int ImportHandle;
internal bool IsAsync; internal bool IsAsync;
#if FEATURE_WASM_THREADS
internal bool IsThreadCaptured;
#endif
[StructLayout(LayoutKind.Sequential, Pack = 4)] [StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct JSBindingHeader internal struct JSBindingHeader
@ -177,7 +174,7 @@ namespace System.Runtime.InteropServices.JavaScript
if (RuntimeInformation.OSArchitecture != Architecture.Wasm) if (RuntimeInformation.OSArchitecture != Architecture.Wasm)
throw new PlatformNotSupportedException(); throw new PlatformNotSupportedException();
return BindJSFunctionImpl(functionName, moduleName, signatures); return BindJSImportImpl(functionName, moduleName, signatures);
} }
/// <summary> /// <summary>
@ -220,15 +217,19 @@ namespace System.Runtime.InteropServices.JavaScript
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span<JSMarshalerArgument> arguments) internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span<JSMarshalerArgument> arguments)
{ {
#if FEATURE_WASM_THREADS
var targetContext = JSProxyContext.SealJSImportCapturing();
JSProxyContext.AssertIsInteropThread();
arguments[0].slot.ContextHandle = targetContext.ContextHandle;
arguments[1].slot.ContextHandle = targetContext.ContextHandle;
#else
var targetContext = JSProxyContext.MainThreadContext;
#endif
if (signature.IsAsync) if (signature.IsAsync)
{ {
// pre-allocate the result handle and Task // pre-allocate the result handle and Task
#if FEATURE_WASM_THREADS var holder = new JSHostImplementation.PromiseHolder(targetContext);
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.Type = MarshalerType.TaskPreCreated;
arguments[1].slot.GCHandle = holder.GCHandle; arguments[1].slot.GCHandle = holder.GCHandle;
} }
@ -253,10 +254,10 @@ namespace System.Runtime.InteropServices.JavaScript
} }
} }
internal static unsafe JSFunctionBinding BindJSFunctionImpl(string functionName, string moduleName, ReadOnlySpan<JSMarshalerType> signatures) internal static unsafe JSFunctionBinding BindJSImportImpl(string functionName, string moduleName, ReadOnlySpan<JSMarshalerType> signatures)
{ {
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
JSSynchronizationContext.AssertWebWorkerContext(); JSProxyContext.AssertIsInteropThread();
#endif #endif
var signature = JSHostImplementation.GetMethodSignature(signatures, functionName, moduleName); var signature = JSHostImplementation.GetMethodSignature(signatures, functionName, moduleName);
@ -284,5 +285,19 @@ namespace System.Runtime.InteropServices.JavaScript
return signature; return signature;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe 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);
}
}
}
} }
} }

View File

@ -22,7 +22,7 @@ namespace System.Runtime.InteropServices.JavaScript
get get
{ {
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
JSSynchronizationContext.AssertWebWorkerContext(); JSProxyContext.AssertIsInteropThread();
#endif #endif
return JavaScriptImports.GetGlobalThis(); return JavaScriptImports.GetGlobalThis();
} }
@ -36,7 +36,7 @@ namespace System.Runtime.InteropServices.JavaScript
get get
{ {
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
JSSynchronizationContext.AssertWebWorkerContext(); JSProxyContext.AssertIsInteropThread();
#endif #endif
return JavaScriptImports.GetDotnetInstance(); return JavaScriptImports.GetDotnetInstance();
} }
@ -54,7 +54,7 @@ namespace System.Runtime.InteropServices.JavaScript
public static Task<JSObject> ImportAsync(string moduleName, string moduleUrl, CancellationToken cancellationToken = default) public static Task<JSObject> ImportAsync(string moduleName, string moduleUrl, CancellationToken cancellationToken = default)
{ {
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
JSSynchronizationContext.AssertWebWorkerContext(); JSProxyContext.AssertIsInteropThread();
#endif #endif
return JSHostImplementation.ImportAsync(moduleName, moduleUrl, cancellationToken); return JSHostImplementation.ImportAsync(moduleName, moduleUrl, cancellationToken);
} }
@ -65,7 +65,7 @@ namespace System.Runtime.InteropServices.JavaScript
get get
{ {
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
return JSSynchronizationContext.CurrentJSSynchronizationContext ?? JSSynchronizationContext.MainJSSynchronizationContext!; return (JSProxyContext.ExecutionContext ?? JSProxyContext.MainThreadContext).SynchronizationContext;
#else #else
return null!; return null!;
#endif #endif

View File

@ -12,34 +12,24 @@ namespace System.Runtime.InteropServices.JavaScript
public sealed class PromiseHolder public sealed class PromiseHolder
{ {
public nint GCHandle; // could be also virtual GCVHandle public readonly nint GCHandle; // could be also virtual GCVHandle
public ToManagedCallback? Callback; public ToManagedCallback? Callback;
public JSProxyContext ProxyContext;
public bool IsDisposed;
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
// the JavaScript object could only exist on the single web worker and can't migrate to other workers public ManualResetEventSlim? CallbackReady;
internal JSSynchronizationContext SynchronizationContext;
#endif #endif
#if FEATURE_WASM_THREADS public PromiseHolder(JSProxyContext targetContext)
// TODO possibly unify signature with non-MT and pass null
public PromiseHolder(JSSynchronizationContext targetContext)
{ {
GCHandle = (IntPtr)InteropServices.GCHandle.Alloc(this, GCHandleType.Normal); GCHandle = (IntPtr)InteropServices.GCHandle.Alloc(this, GCHandleType.Normal);
SynchronizationContext = targetContext; ProxyContext = targetContext;
} }
#else
public PromiseHolder()
{
GCHandle = (IntPtr)InteropServices.GCHandle.Alloc(this, GCHandleType.Normal);
}
#endif
public PromiseHolder(nint gcvHandle) public PromiseHolder(JSProxyContext targetContext, nint gcvHandle)
{ {
GCHandle = gcvHandle; GCHandle = gcvHandle;
#if FEATURE_WASM_THREADS ProxyContext = targetContext;
JSSynchronizationContext.AssertWebWorkerContext();
SynchronizationContext = JSSynchronizationContext.CurrentJSSynchronizationContext!;
#endif
} }
} }

View File

@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements. // Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
@ -16,115 +15,6 @@ namespace System.Runtime.InteropServices.JavaScript
{ {
private const string TaskGetResultName = "get_Result"; private const string TaskGetResultName = "get_Result";
private static MethodInfo? s_taskGetResultMethodInfo; private static MethodInfo? s_taskGetResultMethodInfo;
// we use this to maintain identity of JSHandle for a JSObject proxy
#if FEATURE_WASM_THREADS
[ThreadStatic]
#endif
private static Dictionary<nint, WeakReference<JSObject>>? s_csOwnedObjects;
public static Dictionary<nint, WeakReference<JSObject>> ThreadCsOwnedObjects
{
get
{
s_csOwnedObjects ??= new();
return s_csOwnedObjects;
}
}
// we use this to maintain identity of GCHandle for a managed object
#if FEATURE_WASM_THREADS
[ThreadStatic]
#endif
private static Dictionary<object, nint>? s_jsOwnedObjects;
public static Dictionary<object, nint> ThreadJsOwnedObjects
{
get
{
s_jsOwnedObjects ??= new Dictionary<object, nint>(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<nint, PromiseHolder>? s_jsOwnedHolders;
public static Dictionary<nint, PromiseHolder> ThreadJsOwnedHolders
{
get
{
s_jsOwnedHolders ??= new Dictionary<nint, PromiseHolder>();
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<nint>? s_JSVHandleFreeList;
public static List<nint> JSVHandleFreeList
{
get
{
s_JSVHandleFreeList ??= new();
return s_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];
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)
{
if (jsHandle != IntPtr.Zero)
{
#if FEATURE_WASM_THREADS
JSSynchronizationContext.AssertWebWorkerContext();
#endif
ThreadCsOwnedObjects.Remove(jsHandle);
Interop.Runtime.ReleaseCSOwnedObject(jsHandle);
}
}
public static bool GetTaskResultDynamic(Task task, out object? value) public static bool GetTaskResultDynamic(Task task, out object? value)
{ {
@ -143,30 +33,6 @@ namespace System.Runtime.InteropServices.JavaScript
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
// A JSOwnedObject is a managed object with its lifetime controlled by javascript.
// The managed side maintains a strong reference to the object, while the JS side
// maintains a weak reference and notifies the managed side if the JS wrapper object
// has been reclaimed by the JS GC. At that point, the managed side will release its
// strong references, allowing the managed object to be collected.
// This ensures that things like delegates and promises will never 'go away' while JS
// is expecting to be able to invoke or await them.
public static IntPtr GetJSOwnedObjectGCHandle(object obj, GCHandleType handleType = GCHandleType.Normal)
{
if (obj == null)
{
return IntPtr.Zero;
}
IntPtr gcHandle;
if (ThreadJsOwnedObjects.TryGetValue(obj, out gcHandle))
{
return gcHandle;
}
IntPtr result = (IntPtr)GCHandle.Alloc(obj, handleType);
ThreadJsOwnedObjects[obj] = result;
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RuntimeMethodHandle GetMethodHandleFromIntPtr(IntPtr ptr) public static RuntimeMethodHandle GetMethodHandleFromIntPtr(IntPtr ptr)
@ -282,7 +148,6 @@ namespace System.Runtime.InteropServices.JavaScript
signature.Result = types[0]._signatureType; signature.Result = types[0]._signatureType;
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
signature.ImportHandle = (int)Interlocked.Increment(ref JSFunctionBinding.nextImportHandle); signature.ImportHandle = (int)Interlocked.Increment(ref JSFunctionBinding.nextImportHandle);
signature.IsThreadCaptured = false;
#else #else
signature.ImportHandle = (int)JSFunctionBinding.nextImportHandle++; signature.ImportHandle = (int)JSFunctionBinding.nextImportHandle++;
#endif #endif
@ -290,12 +155,6 @@ namespace System.Runtime.InteropServices.JavaScript
for (int i = 0; i < argsCount; i++) for (int i = 0; i < argsCount; i++)
{ {
var type = signature.Sigs[i] = types[i + 1]._signatureType; var type = signature.Sigs[i] = types[i + 1]._signatureType;
#if FEATURE_WASM_THREADS
if (i > 0 && (type.Type == MarshalerType.JSObject || type.Type == MarshalerType.JSException))
{
signature.IsThreadCaptured = true;
}
#endif
} }
signature.IsAsync = types[0]._signatureType.Type == MarshalerType.Task; signature.IsAsync = types[0]._signatureType.Type == MarshalerType.Task;
@ -330,24 +189,7 @@ namespace System.Runtime.InteropServices.JavaScript
signature.Sigs = null; signature.Sigs = null;
} }
public static JSObject CreateCSOwnedProxy(nint jsHandle) [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "It's always part of the single compilation (and trimming) unit.")]
{
#if FEATURE_WASM_THREADS
JSSynchronizationContext.AssertWebWorkerContext();
#endif
JSObject? res;
if (!ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference) ||
!reference.TryGetTarget(out res) ||
res.IsDisposed)
{
res = new JSObject(jsHandle);
ThreadCsOwnedObjects[jsHandle] = new WeakReference<JSObject>(res, trackResurrection: true);
}
return res;
}
[Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "It's always part of the single compilation (and trimming) unit.")]
public static void LoadLazyAssembly(byte[] dllBytes, byte[]? pdbBytes) public static void LoadLazyAssembly(byte[] dllBytes, byte[]? pdbBytes)
{ {
if (pdbBytes == null) if (pdbBytes == null)
@ -356,7 +198,7 @@ namespace System.Runtime.InteropServices.JavaScript
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes), new MemoryStream(pdbBytes)); AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes), new MemoryStream(pdbBytes));
} }
[Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "It's always part of the single compilation (and trimming) unit.")] [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "It's always part of the single compilation (and trimming) unit.")]
public static void LoadSatelliteAssembly(byte[] dllBytes) public static void LoadSatelliteAssembly(byte[] dllBytes)
{ {
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes)); AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes));
@ -365,92 +207,33 @@ namespace System.Runtime.InteropServices.JavaScript
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
public static void InstallWebWorkerInterop(bool isMainThread) public static void InstallWebWorkerInterop(bool isMainThread)
{ {
Interop.Runtime.InstallWebWorkerInterop(); var ctx = new JSSynchronizationContext(isMainThread);
var currentTID = GetNativeThreadId(); ctx.previousSynchronizationContext = SynchronizationContext.Current;
var ctx = JSSynchronizationContext.CurrentJSSynchronizationContext; SynchronizationContext.SetSynchronizationContext(ctx);
if (ctx == null)
var proxyContext = ctx.ProxyContext;
JSProxyContext.CurrentThreadContext = proxyContext;
JSProxyContext.ExecutionContext = proxyContext;
if (isMainThread)
{ {
ctx = new JSSynchronizationContext(Thread.CurrentThread, currentTID); JSProxyContext.MainThreadContext = proxyContext;
ctx.previousSynchronizationContext = SynchronizationContext.Current;
JSSynchronizationContext.CurrentJSSynchronizationContext = ctx;
SynchronizationContext.SetSynchronizationContext(ctx);
if (isMainThread)
{
JSSynchronizationContext.MainJSSynchronizationContext = ctx;
}
}
else if (ctx.TargetTID != currentTID)
{
Environment.FailFast($"JSSynchronizationContext.Install has wrong native thread id {ctx.TargetTID} != {currentTID}");
} }
ctx.AwaitNewData(); ctx.AwaitNewData();
Interop.Runtime.InstallWebWorkerInterop(proxyContext.ContextHandle);
} }
public static void UninstallWebWorkerInterop() public static void UninstallWebWorkerInterop()
{ {
var ctx = JSSynchronizationContext.CurrentJSSynchronizationContext; var ctx = JSProxyContext.CurrentThreadContext;
var uninstallJSSynchronizationContext = ctx != null; if (ctx == null) throw new InvalidOperationException();
if (uninstallJSSynchronizationContext) var syncContext = ctx.SynchronizationContext;
if (SynchronizationContext.Current == syncContext)
{ {
try SynchronizationContext.SetSynchronizationContext(syncContext.previousSynchronizationContext);
{
foreach (var jsObjectWeak in ThreadCsOwnedObjects.Values)
{
if (jsObjectWeak.TryGetTarget(out var jso))
{
jso.Dispose();
}
}
SynchronizationContext.SetSynchronizationContext(ctx!.previousSynchronizationContext);
JSSynchronizationContext.CurrentJSSynchronizationContext = null;
ctx.isDisposed = true;
}
catch (Exception ex)
{
Environment.FailFast($"Unexpected error in UninstallWebWorkerInterop, ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}. " + ex);
}
} }
else ctx.Dispose();
{
if (ThreadCsOwnedObjects.Count > 0)
{
Environment.FailFast($"There should be no JSObjects proxies on this thread, ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}");
}
if (ThreadJsOwnedObjects.Count > 0)
{
Environment.FailFast($"There should be no JS proxies of managed objects on this thread, ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}");
}
}
Interop.Runtime.UninstallWebWorkerInterop();
if (uninstallJSSynchronizationContext)
{
try
{
foreach (var gch in ThreadJsOwnedObjects.Values)
{
GCHandle gcHandle = (GCHandle)gch;
gcHandle.Free();
}
foreach (var holder in ThreadJsOwnedHolders.Values)
{
unsafe
{
holder.Callback!.Invoke(null);
}
}
}
catch (Exception ex)
{
Environment.FailFast($"Unexpected error in UninstallWebWorkerInterop, ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}. " + ex);
}
}
ThreadCsOwnedObjects.Clear();
ThreadJsOwnedObjects.Clear();
JSVHandleFreeList.Clear();
NextJSVHandle = IntPtr.Zero;
} }
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "external_eventloop")] [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "external_eventloop")]
@ -460,15 +243,6 @@ namespace System.Runtime.InteropServices.JavaScript
{ {
GetThreadExternalEventloop(thread) = true; GetThreadExternalEventloop(thread) = true;
} }
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "thread_id")]
private static extern ref long GetThreadNativeThreadId(Thread @this);
public static IntPtr GetNativeThreadId()
{
return (int)GetThreadNativeThreadId(Thread.CurrentThread);
}
#endif #endif
} }

View File

@ -3,7 +3,9 @@
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Threading;
namespace System.Runtime.InteropServices.JavaScript namespace System.Runtime.InteropServices.JavaScript
{ {
@ -18,7 +20,7 @@ namespace System.Runtime.InteropServices.JavaScript
{ {
internal JSMarshalerArgumentImpl slot; internal JSMarshalerArgumentImpl slot;
[StructLayout(LayoutKind.Explicit, Pack = 16, Size = 16)] [StructLayout(LayoutKind.Explicit, Pack = 32, Size = 32)]
internal struct JSMarshalerArgumentImpl internal struct JSMarshalerArgumentImpl
{ {
[FieldOffset(0)] [FieldOffset(0)]
@ -55,6 +57,9 @@ namespace System.Runtime.InteropServices.JavaScript
internal MarshalerType Type; internal MarshalerType Type;
[FieldOffset(13)] [FieldOffset(13)]
internal MarshalerType ElementType; internal MarshalerType ElementType;
[FieldOffset(16)]
internal IntPtr ContextHandle;
} }
/// <summary> /// <summary>
@ -64,6 +69,98 @@ namespace System.Runtime.InteropServices.JavaScript
public unsafe void Initialize() public unsafe void Initialize()
{ {
slot.Type = MarshalerType.None; slot.Type = MarshalerType.None;
#if FEATURE_WASM_THREADS
// we know that this is at the start of some JSImport call, but we don't know yet what would be the target thread
// also this is called multiple times
JSProxyContext.JSImportWithUnknownContext();
slot.ContextHandle = IntPtr.Zero;
#endif
}
#if FEATURE_WASM_THREADS
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal unsafe void InitializeWithContext(JSProxyContext knownProxyContext)
{
slot.Type = MarshalerType.None;
slot.ContextHandle = knownProxyContext.ContextHandle;
}
#endif
// this is always called from ToManaged() marshaler
#pragma warning disable CA1822 // Mark members as static
internal JSProxyContext ToManagedContext
#pragma warning restore CA1822 // Mark members as static
{
get
{
#if !FEATURE_WASM_THREADS
return JSProxyContext.MainThreadContext;
#else
// ContextHandle always has to be set
// during JSImport, this is marshaling result/exception and it would be set by:
// - InvokeJSImport implementation
// - ActionJS.InvokeJS
// - ResolveVoidPromise/ResolvePromise/RejectPromise
// during JSExport, this is marshaling parameters and it would be set by:
// - alloc_stack_frame
// - set_js_handle/set_gc_handle
var proxyContextGCHandle = (GCHandle)slot.ContextHandle;
if (proxyContextGCHandle == default)
{
Environment.FailFast($"ContextHandle not set, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
}
var argumentContext = (JSProxyContext)proxyContextGCHandle.Target!;
return argumentContext;
#endif
}
}
// this is always called from ToJS() marshaler
#pragma warning disable CA1822 // Mark members as static
internal JSProxyContext ToJSContext
#pragma warning restore CA1822 // Mark members as static
{
get
{
#if !FEATURE_WASM_THREADS
return JSProxyContext.MainThreadContext;
#else
if (JSProxyContext.CapturingState == JSProxyContext.JSImportOperationState.JSImportParams)
{
// we are called from ToJS, during JSImport
// we need to check for captured or default context
return JSProxyContext.CurrentOperationContext;
}
// ContextHandle must be set be set by JS side of JSExport, and we are marshaling result of JSExport
var proxyContextGCHandle = slot.ContextHandle;
if (proxyContextGCHandle == IntPtr.Zero)
{
Environment.FailFast($"ContextHandle not set, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
}
var argumentContext = (JSProxyContext)((GCHandle)proxyContextGCHandle).Target!;
return argumentContext;
#endif
}
}
// make sure that we are on a thread with JS interop and that it matches the target of the argument
#pragma warning disable CA1822 // Mark members as static
internal JSProxyContext AssertCurrentThreadContext()
#pragma warning restore CA1822 // Mark members as static
{
#if !FEATURE_WASM_THREADS
return JSProxyContext.MainThreadContext;
#else
var currentThreadContext = JSProxyContext.CurrentThreadContext;
if (currentThreadContext == null)
{
Environment.FailFast($"Must be called on same thread with JS interop, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
}
if (slot.ContextHandle != currentThreadContext.ContextHandle)
{
Environment.FailFast($"Must be called on same thread which created the stack frame, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
}
return currentThreadContext;
#endif
} }
} }
} }

View File

@ -10,53 +10,37 @@ namespace System.Runtime.InteropServices.JavaScript
public partial class JSObject public partial class JSObject
{ {
internal nint JSHandle; internal nint JSHandle;
internal JSProxyContext ProxyContext;
#if FEATURE_WASM_THREADS
private readonly object _thisLock = new object();
private SynchronizationContext? m_SynchronizationContext;
#endif
public SynchronizationContext SynchronizationContext public SynchronizationContext SynchronizationContext
{ {
get get
{ {
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
return m_SynchronizationContext!; return ProxyContext.SynchronizationContext;
#else #else
throw new PlatformNotSupportedException(); throw new PlatformNotSupportedException();
#endif #endif
} }
} }
#if FEATURE_WASM_THREADS
// the JavaScript object could only exist on the single web worker and can't migrate to other workers
internal nint OwnerTID;
#endif
#if !DISABLE_LEGACY_JS_INTEROP #if !DISABLE_LEGACY_JS_INTEROP
internal GCHandle? InFlight; internal GCHandle? InFlight;
internal int InFlightCounter; internal int InFlightCounter;
#endif #endif
private bool _isDisposed; internal bool _isDisposed;
internal JSObject(IntPtr jsHandle) internal JSObject(IntPtr jsHandle, JSProxyContext ctx)
{ {
ProxyContext = ctx;
JSHandle = jsHandle; JSHandle = jsHandle;
#if FEATURE_WASM_THREADS
var ctx = JSSynchronizationContext.CurrentJSSynchronizationContext;
if (ctx == null)
{
Environment.FailFast("Missing CurrentJSSynchronizationContext");
}
m_SynchronizationContext = ctx;
OwnerTID = ctx!.TargetTID;
#endif
} }
#if !DISABLE_LEGACY_JS_INTEROP #if !DISABLE_LEGACY_JS_INTEROP
internal void AddInFlight() internal void AddInFlight()
{ {
ObjectDisposedException.ThrowIf(IsDisposed, this); ObjectDisposedException.ThrowIf(IsDisposed, this);
lock (this) lock (ProxyContext)
{ {
InFlightCounter++; InFlightCounter++;
if (InFlightCounter == 1) if (InFlightCounter == 1)
@ -72,7 +56,7 @@ namespace System.Runtime.InteropServices.JavaScript
// we only want JSObject to be disposed (from GC finalizer) once there is no in-flight reference and also no natural C# reference // we only want JSObject to be disposed (from GC finalizer) once there is no in-flight reference and also no natural C# reference
internal void ReleaseInFlight() internal void ReleaseInFlight()
{ {
lock (this) lock (ProxyContext)
{ {
Debug.Assert(InFlightCounter != 0, "InFlightCounter != 0"); Debug.Assert(InFlightCounter != 0, "InFlightCounter != 0");
@ -94,19 +78,17 @@ namespace System.Runtime.InteropServices.JavaScript
{ {
return; return;
} }
JSSynchronizationContext.AssertWebWorkerContext();
var currentTID = JSSynchronizationContext.CurrentJSSynchronizationContext!.TargetTID;
if (value is JSObject jsObject) if (value is JSObject jsObject)
{ {
if (jsObject.OwnerTID != currentTID) if (!jsObject.ProxyContext.IsCurrentThread())
{ {
throw new InvalidOperationException("The JavaScript object can be used only on the thread where it was created."); throw new InvalidOperationException("The JavaScript object can be used only on the thread where it was created.");
} }
} }
else if (value is JSException jsException) else if (value is JSException jsException)
{ {
if (jsException.jsException != null && jsException.jsException.OwnerTID != currentTID) if (jsException.jsException != null && !jsException.jsException.ProxyContext.IsCurrentThread())
{ {
throw new InvalidOperationException("The JavaScript object can be used only on the thread where it was created."); throw new InvalidOperationException("The JavaScript object can be used only on the thread where it was created.");
} }
@ -123,43 +105,24 @@ namespace System.Runtime.InteropServices.JavaScript
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() => $"(js-obj js '{JSHandle}')"; public override string ToString() => $"(js-obj js '{JSHandle}')";
internal void DisposeLocal() internal void DisposeImpl(bool skipJsCleanup = false)
{
JSHostImplementation.ThreadCsOwnedObjects.Remove(JSHandle);
_isDisposed = true;
JSHandle = IntPtr.Zero;
}
private void DisposeThis()
{ {
if (!_isDisposed) if (!_isDisposed)
{ {
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
if (SynchronizationContext == SynchronizationContext.Current) if (ProxyContext.IsCurrentThread())
{ {
lock (_thisLock) JSProxyContext.ReleaseCSOwnedObject(this, skipJsCleanup);
{
JSHostImplementation.ReleaseCSOwnedObject(JSHandle);
_isDisposed = true;
JSHandle = IntPtr.Zero;
m_SynchronizationContext = null;
} //lock
return; return;
} }
SynchronizationContext.Post(static (object? s) => ProxyContext.SynchronizationContext.Post(static (object? s) =>
{ {
var self = (JSObject)s!; var x = ((JSObject self, bool skipJS))s!;
lock (self._thisLock) JSProxyContext.ReleaseCSOwnedObject(x.self, x.skipJS);
{ }, (this, skipJsCleanup));
JSHostImplementation.ReleaseCSOwnedObject(self.JSHandle);
self._isDisposed = true;
self.JSHandle = IntPtr.Zero;
self.m_SynchronizationContext = null;
} //lock
}, this);
#else #else
JSHostImplementation.ReleaseCSOwnedObject(JSHandle); JSProxyContext.ReleaseCSOwnedObject(this, skipJsCleanup);
_isDisposed = true; _isDisposed = true;
JSHandle = IntPtr.Zero; JSHandle = IntPtr.Zero;
#endif #endif
@ -168,7 +131,7 @@ namespace System.Runtime.InteropServices.JavaScript
~JSObject() ~JSObject()
{ {
DisposeThis(); DisposeImpl();
} }
/// <summary> /// <summary>
@ -176,8 +139,7 @@ namespace System.Runtime.InteropServices.JavaScript
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
DisposeThis(); DisposeImpl();
GC.SuppressFinalize(this);
} }
} }
} }

View File

@ -0,0 +1,596 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using static System.Runtime.InteropServices.JavaScript.JSHostImplementation;
namespace System.Runtime.InteropServices.JavaScript
{
internal sealed class JSProxyContext : IDisposable
{
private bool _isDisposed;
// we use this to maintain identity of JSHandle for a JSObject proxy
private readonly Dictionary<nint, WeakReference<JSObject>> ThreadCsOwnedObjects = new();
// we use this to maintain identity of GCHandle for a managed object
private readonly Dictionary<object, nint> ThreadJsOwnedObjects = new(ReferenceEqualityComparer.Instance);
// this is similar to GCHandle, but the GCVHandle is allocated on JS side and this keeps the C# proxy alive
private readonly Dictionary<nint, PromiseHolder> ThreadJsOwnedHolders = new();
// 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.
private nint NextJSVHandle = -2;
private readonly List<nint> JSVHandleFreeList = new();
#if !FEATURE_WASM_THREADS
private JSProxyContext()
{
}
#else
public nint ContextHandle;
public nint NativeTID;
public int ManagedTID;
public bool IsMainThread;
public JSSynchronizationContext SynchronizationContext;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsCurrentThread()
{
return ManagedTID == Thread.CurrentThread.ManagedThreadId;
}
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "thread_id")]
private static extern ref long GetThreadNativeThreadId(Thread @this);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr GetNativeThreadId()
{
return (int)GetThreadNativeThreadId(Thread.CurrentThread);
}
public JSProxyContext(bool isMainThread, JSSynchronizationContext synchronizationContext)
{
SynchronizationContext = synchronizationContext;
NativeTID = GetNativeThreadId();
ManagedTID = Thread.CurrentThread.ManagedThreadId;
IsMainThread = isMainThread;
ContextHandle = (nint)GCHandle.Alloc(this, GCHandleType.Normal);
}
#endif
#region Current operation context
#if !FEATURE_WASM_THREADS
public static readonly JSProxyContext MainThreadContext = new();
public static JSProxyContext CurrentThreadContext => MainThreadContext;
public static JSProxyContext CurrentOperationContext => MainThreadContext;
public static JSProxyContext PushOperationWithCurrentThreadContext()
{
// in single threaded build we don't have to keep stack of operations and the context/thread is always the same
return MainThreadContext;
}
#else
// Context of the main thread
private static JSProxyContext? _MainThreadContext;
public static JSProxyContext MainThreadContext
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _MainThreadContext!;
set => _MainThreadContext = value;
}
public enum JSImportOperationState
{
None,
JSImportParams,
}
[ThreadStatic]
private static JSProxyContext? _CapturedOperationContext;
[ThreadStatic]
private static JSImportOperationState _CapturingState;
public static JSImportOperationState CapturingState => _CapturingState;
// there will be call to JS from JSImport generated code, but we don't know which target thread yet
public static void JSImportWithUnknownContext()
{
// it would be ideal to assert here, that we arrived here with JSImportOperationState.None
// but any exception during JSImportOperationState.JSImportParams phase could make this state un-balanced
// typically this would be exception which is validating the marshaled value
// manually re-setting _CapturingState on each throw site would be possible, but fragile
// luckily, we always reset it here before any new JSImport call
// so the code which could interact with _CapturedOperationContext value will receive fresh values
_CapturingState = JSImportOperationState.JSImportParams;
_CapturedOperationContext = null;
}
// there will be no capture during following call to JS
public static void JSImportNoCapture()
{
_CapturingState = JSImportOperationState.None;
_CapturedOperationContext = null;
}
// we are at the end of marshaling of the JSImport parameters
public static JSProxyContext SealJSImportCapturing()
{
if (_CapturingState != JSImportOperationState.JSImportParams)
{
Environment.FailFast($"Method only allowed during JSImport capturing phase, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
}
_CapturingState = JSImportOperationState.None;
var capturedOperationContext = _CapturedOperationContext;
_CapturedOperationContext = null;
if (capturedOperationContext != null)
{
return capturedOperationContext;
}
// it could happen that we are in operation, in which we didn't capture target thread/context
var executionContext = ExecutionContext;
if (executionContext != null)
{
// we could will call JS on the current thread (or child task), if it has the JS interop installed
return executionContext;
}
// otherwise we will call JS on the main thread, which always has JS interop
return MainThreadContext;
}
// this is called only during marshaling (in) parameters of JSImport, which have existing ProxyContext (thread affinity)
// together with CurrentOperationContext is will validate that all parameters of the call have same context/affinity
public static void CaptureContextFromParameter(JSProxyContext parameterContext)
{
if (_CapturingState != JSImportOperationState.JSImportParams)
{
Environment.FailFast($"Method only allowed during JSImport capturing phase, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
}
var capturedContext = _CapturedOperationContext;
if (capturedContext == null)
{
_CapturedOperationContext = capturedContext;
}
else if (parameterContext != capturedContext)
{
_CapturedOperationContext = null;
_CapturingState = JSImportOperationState.None;
throw new InvalidOperationException("All JSObject proxies need to have same thread affinity. See https://aka.ms/dotnet-JS-interop-threads");
}
}
// Context flowing from parent thread into child tasks.
// Could be null on threads which don't have JS interop, like managed thread pool threads. Unless they inherit it from the current Task
// TODO flow it also with ExecutionContext to child threads ?
private static readonly AsyncLocal<JSProxyContext?> _currentThreadContext = new AsyncLocal<JSProxyContext?>();
public static JSProxyContext? ExecutionContext
{
get => _currentThreadContext.Value;
set => _currentThreadContext.Value = value;
}
[ThreadStatic]
public static JSProxyContext? CurrentThreadContext;
// This is context to dispatch into. In order of preference
// - captured context by arguments of current/pending JSImport call
// - current thread context, for calls from JSWebWorker threads with the interop installed
// - main thread, for calls from any other thread, like managed thread pool or `new Thread`
public static JSProxyContext CurrentOperationContext
{
get
{
if (_CapturingState != JSImportOperationState.JSImportParams)
{
Environment.FailFast($"Method only allowed during JSImport capturing phase, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
}
var capturedOperationContext = _CapturedOperationContext;
if (capturedOperationContext != null)
{
return capturedOperationContext;
}
// it could happen that we are in operation, in which we didn't capture target thread/context
var executionContext = ExecutionContext;
if (executionContext != null)
{
// capture this fallback for validation of all other parameters
_CapturedOperationContext = executionContext;
// we could will call JS on the current thread (or child task), if it has the JS interop installed
return executionContext;
}
// otherwise we will call JS on the main thread, which always has JS interop
var mainThreadContext = MainThreadContext;
// capture this fallback for validation of all other parameters
// such validation could fail if Task is marshaled earlier than JSObject and uses different target context
_CapturedOperationContext = mainThreadContext;
return mainThreadContext;
}
}
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static JSProxyContext AssertIsInteropThread()
{
#if FEATURE_WASM_THREADS
var ctx = CurrentThreadContext;
if (ctx == null)
{
throw new InvalidOperationException($"Please use dedicated worker for working with JavaScript interop, ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}. See https://aka.ms/dotnet-JS-interop-threads");
}
if (ctx._isDisposed)
{
ObjectDisposedException.ThrowIf(ctx._isDisposed, ctx);
}
return ctx;
#else
return MainThreadContext;
#endif
}
#endregion
#region Handles
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsJSVHandle(nint jsHandle)
{
return jsHandle < -1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsGCVHandle(nint gcHandle)
{
return gcHandle < -1;
}
public nint AllocJSVHandle()
{
lock (this)
{
if (JSVHandleFreeList.Count > 0)
{
var jsvHandle = JSVHandleFreeList[JSVHandleFreeList.Count - 1];
JSVHandleFreeList.RemoveAt(JSVHandleFreeList.Count - 1);
return jsvHandle;
}
if (NextJSVHandle == IntPtr.Zero)
{
NextJSVHandle = -2;
}
return NextJSVHandle--;
}
}
public void FreeJSVHandle(nint jsvHandle)
{
lock (this)
{
JSVHandleFreeList.Add(jsvHandle);
}
}
// A JSOwnedObject is a managed object with its lifetime controlled by javascript.
// The managed side maintains a strong reference to the object, while the JS side
// maintains a weak reference and notifies the managed side if the JS wrapper object
// has been reclaimed by the JS GC. At that point, the managed side will release its
// strong references, allowing the managed object to be collected.
// This ensures that things like delegates and promises will never 'go away' while JS
// is expecting to be able to invoke or await them.
public IntPtr GetJSOwnedObjectGCHandle(object obj, GCHandleType handleType = GCHandleType.Normal)
{
if (obj == null)
{
return IntPtr.Zero;
}
lock (this)
{
if (ThreadJsOwnedObjects.TryGetValue(obj, out IntPtr gcHandle))
{
return gcHandle;
}
IntPtr result = (IntPtr)GCHandle.Alloc(obj, handleType);
ThreadJsOwnedObjects[obj] = result;
return result;
}
}
public PromiseHolder CreatePromiseHolder()
{
lock (this)
{
return new PromiseHolder(this);
}
}
public PromiseHolder GetPromiseHolder(nint gcHandle)
{
lock (this)
{
PromiseHolder? holder;
if (IsGCVHandle(gcHandle))
{
if (!ThreadJsOwnedHolders.TryGetValue(gcHandle, out holder))
{
holder = new PromiseHolder(this, gcHandle);
ThreadJsOwnedHolders.Add(gcHandle, holder);
}
}
else
{
holder = (PromiseHolder)((GCHandle)gcHandle).Target!;
}
return holder;
}
}
public unsafe void ReleasePromiseHolder(nint holderGCHandle)
{
lock (this)
{
PromiseHolder? holder;
if (IsGCVHandle(holderGCHandle))
{
if (!ThreadJsOwnedHolders.Remove(holderGCHandle, out holder))
{
throw new InvalidOperationException("ReleasePromiseHolder expected PromiseHolder " + holderGCHandle);
}
}
else
{
GCHandle handle = (GCHandle)holderGCHandle;
var target = handle.Target!;
if (target is PromiseHolder holder2)
{
holder = holder2;
}
else
{
throw new InvalidOperationException("ReleasePromiseHolder expected PromiseHolder" + holderGCHandle);
}
handle.Free();
}
holder.IsDisposed = true;
}
}
public unsafe void ReleaseJSOwnedObjectByGCHandle(nint gcHandle)
{
ToManagedCallback? holderCallback = null;
lock (this)
{
PromiseHolder? holder = null;
if (IsGCVHandle(gcHandle))
{
if (!ThreadJsOwnedHolders.Remove(gcHandle, out holder))
{
throw new InvalidOperationException("ReleaseJSOwnedObjectByGCHandle expected in ThreadJsOwnedHolders");
}
}
else
{
GCHandle handle = (GCHandle)gcHandle;
var target = handle.Target!;
if (target is PromiseHolder holder2)
{
holder = holder2;
}
else
{
if (!ThreadJsOwnedObjects.Remove(target))
{
throw new InvalidOperationException("ReleaseJSOwnedObjectByGCHandle expected in ThreadJsOwnedObjects");
}
}
handle.Free();
}
if (holder != null)
{
holderCallback = holder.Callback;
holder.IsDisposed = true;
}
}
holderCallback?.Invoke(null);
}
public JSObject CreateCSOwnedProxy(nint jsHandle)
{
lock (this)
{
JSObject? res;
if (!ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference) ||
!reference.TryGetTarget(out res) ||
res.IsDisposed)
{
res = new JSObject(jsHandle, this);
ThreadCsOwnedObjects[jsHandle] = new WeakReference<JSObject>(res, trackResurrection: true);
}
return res;
}
}
public static void ReleaseCSOwnedObject(JSObject proxy, bool skipJS)
{
if (proxy.IsDisposed)
{
return;
}
var ctx = proxy.ProxyContext;
#if FEATURE_WASM_THREADS
if (!ctx.IsCurrentThread())
{
throw new InvalidOperationException($"ReleaseCSOwnedObject has to run on the thread with same affinity as the proxy. ManagedThreadId: {Environment.CurrentManagedThreadId} JSHandle: {proxy.JSHandle}");
}
#endif
lock (ctx)
{
if (proxy.IsDisposed)
{
return;
}
proxy._isDisposed = true;
GC.SuppressFinalize(proxy);
var jsHandle = proxy.JSHandle;
if (!ctx.ThreadCsOwnedObjects.Remove(jsHandle))
{
Environment.FailFast($"ReleaseCSOwnedObject expected to find registration for JSHandle: {jsHandle}, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
};
if (!skipJS)
{
Interop.Runtime.ReleaseCSOwnedObject(jsHandle);
}
if (IsJSVHandle(jsHandle))
{
ctx.FreeJSVHandle(jsHandle);
}
}
}
#endregion
#region Legacy
// legacy
public void RegisterCSOwnedObject(JSObject proxy)
{
lock (this)
{
ThreadCsOwnedObjects[(int)proxy.JSHandle] = new WeakReference<JSObject>(proxy, trackResurrection: true);
}
}
// legacy
public JSObject? GetCSOwnedObjectByJSHandle(nint jsHandle, int shouldAddInflight)
{
lock (this)
{
if (ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference))
{
reference.TryGetTarget(out JSObject? jsObject);
if (shouldAddInflight != 0)
{
jsObject?.AddInFlight();
}
return jsObject;
}
}
return null;
}
// legacy
public JSObject CreateCSOwnedProxy(nint jsHandle, LegacyHostImplementation.MappedType mappedType, int shouldAddInflight)
{
lock (this)
{
JSObject? res = null;
if (!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, JSProxyContext.MainThreadContext),
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
ThreadCsOwnedObjects[jsHandle] = new WeakReference<JSObject>(res, trackResurrection: true);
}
if (shouldAddInflight != 0)
{
res.AddInFlight();
}
return res;
}
}
#endregion
#region Dispose
private void Dispose(bool disposing)
{
lock (this)
{
if (!_isDisposed)
{
#if FEATURE_WASM_THREADS
if (!IsCurrentThread())
{
Environment.FailFast($"JSProxyContext must be disposed on the thread which owns it, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
}
((GCHandle)ContextHandle).Free();
#endif
List<WeakReference<JSObject>> copy = new(ThreadCsOwnedObjects.Values);
foreach (var jsObjectWeak in copy)
{
if (jsObjectWeak.TryGetTarget(out var jso))
{
jso.Dispose();
}
}
#if FEATURE_WASM_THREADS
Interop.Runtime.UninstallWebWorkerInterop();
#endif
foreach (var gch in ThreadJsOwnedObjects.Values)
{
GCHandle gcHandle = (GCHandle)gch;
gcHandle.Free();
}
foreach (var holder in ThreadJsOwnedHolders.Values)
{
unsafe
{
holder.Callback!.Invoke(null);
}
}
ThreadCsOwnedObjects.Clear();
ThreadJsOwnedObjects.Clear();
JSVHandleFreeList.Clear();
NextJSVHandle = IntPtr.Zero;
if (disposing)
{
#if FEATURE_WASM_THREADS
SynchronizationContext.Dispose();
#endif
}
_isDisposed = true;
}
}
}
~JSProxyContext()
{
Dispose(disposing: false);
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#endregion
}
}

View File

@ -6,9 +6,8 @@
using System.Threading; using System.Threading;
using System.Threading.Channels; using System.Threading.Channels;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using WorkItemQueueType = System.Threading.Channels.Channel<System.Runtime.InteropServices.JavaScript.JSSynchronizationContext.WorkItem>;
using static System.Runtime.InteropServices.JavaScript.JSHostImplementation;
using System.Collections.Generic; using System.Collections.Generic;
using WorkItemQueueType = System.Threading.Channels.Channel<System.Runtime.InteropServices.JavaScript.JSSynchronizationContext.WorkItem>;
namespace System.Runtime.InteropServices.JavaScript namespace System.Runtime.InteropServices.JavaScript
{ {
@ -21,17 +20,12 @@ namespace System.Runtime.InteropServices.JavaScript
/// </summary> /// </summary>
internal sealed class JSSynchronizationContext : SynchronizationContext internal sealed class JSSynchronizationContext : SynchronizationContext
{ {
internal readonly JSProxyContext ProxyContext;
private readonly Action _DataIsAvailable;// don't allocate Action on each call to UnsafeOnCompleted private readonly Action _DataIsAvailable;// don't allocate Action on each call to UnsafeOnCompleted
public readonly Thread TargetThread;
public readonly IntPtr TargetTID;
private readonly WorkItemQueueType Queue; private readonly WorkItemQueueType Queue;
internal static JSSynchronizationContext? MainJSSynchronizationContext;
[ThreadStatic]
internal static JSSynchronizationContext? CurrentJSSynchronizationContext;
internal SynchronizationContext? previousSynchronizationContext; internal SynchronizationContext? previousSynchronizationContext;
internal bool isDisposed; internal bool _isDisposed;
internal readonly struct WorkItem internal readonly struct WorkItem
{ {
@ -47,42 +41,33 @@ namespace System.Runtime.InteropServices.JavaScript
} }
} }
internal JSSynchronizationContext(Thread targetThread, IntPtr targetThreadId) public JSSynchronizationContext(bool isMainThread)
: this(
targetThread, targetThreadId,
Channel.CreateUnbounded<WorkItem>(
new UnboundedChannelOptions { SingleWriter = false, SingleReader = true, AllowSynchronousContinuations = true }
)
)
{ {
} ProxyContext = new JSProxyContext(isMainThread, this);
Queue = Channel.CreateUnbounded<WorkItem>(new UnboundedChannelOptions { SingleWriter = false, SingleReader = true, AllowSynchronousContinuations = true });
internal static void AssertWebWorkerContext()
{
#if FEATURE_WASM_THREADS
if (CurrentJSSynchronizationContext == null)
{
throw new InvalidOperationException("Please use dedicated worker for working with JavaScript interop. See https://aka.ms/dotnet-JS-interop-threads");
}
#endif
}
private JSSynchronizationContext(Thread targetThread, IntPtr targetTID, WorkItemQueueType queue)
{
TargetThread = targetThread;
TargetTID = targetTID;
Queue = queue;
_DataIsAvailable = DataIsAvailable; _DataIsAvailable = DataIsAvailable;
} }
internal JSSynchronizationContext(JSProxyContext proxyContext, WorkItemQueueType queue, Action dataIsAvailable)
{
ProxyContext = proxyContext;
Queue = queue;
_DataIsAvailable = dataIsAvailable;
}
public override SynchronizationContext CreateCopy() public override SynchronizationContext CreateCopy()
{ {
return new JSSynchronizationContext(TargetThread, TargetTID, Queue); return new JSSynchronizationContext(ProxyContext, Queue, _DataIsAvailable);
} }
internal void AwaitNewData() internal void AwaitNewData()
{ {
ObjectDisposedException.ThrowIf(isDisposed, this); if (_isDisposed)
{
// FIXME: there could be abandoned work, but here we have no way how to propagate the failure
// ObjectDisposedException.ThrowIf(_isDisposed, this);
return;
}
var vt = Queue.Reader.WaitToReadAsync(); var vt = Queue.Reader.WaitToReadAsync();
if (vt.IsCompleted) if (vt.IsCompleted)
@ -103,16 +88,16 @@ namespace System.Runtime.InteropServices.JavaScript
{ {
// While we COULD pump here, we don't want to. We want the pump to happen on the next event loop turn. // While we COULD pump here, we don't want to. We want the pump to happen on the next event loop turn.
// Otherwise we could get a chain where a pump generates a new work item and that makes us pump again, forever. // Otherwise we could get a chain where a pump generates a new work item and that makes us pump again, forever.
TargetThreadScheduleBackgroundJob(TargetTID, (void*)(delegate* unmanaged[Cdecl]<void>)&BackgroundJobHandler); TargetThreadScheduleBackgroundJob(ProxyContext.NativeTID, (void*)(delegate* unmanaged[Cdecl]<void>)&BackgroundJobHandler);
} }
public override void Post(SendOrPostCallback d, object? state) public override void Post(SendOrPostCallback d, object? state)
{ {
ObjectDisposedException.ThrowIf(isDisposed, this); ObjectDisposedException.ThrowIf(_isDisposed, this);
var workItem = new WorkItem(d, state, null); var workItem = new WorkItem(d, state, null);
if (!Queue.Writer.TryWrite(workItem)) if (!Queue.Writer.TryWrite(workItem))
Environment.FailFast("JSSynchronizationContext.Post failed"); Environment.FailFast($"JSSynchronizationContext.Post failed, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
} }
// This path can only run when threading is enabled // This path can only run when threading is enabled
@ -120,9 +105,9 @@ namespace System.Runtime.InteropServices.JavaScript
public override void Send(SendOrPostCallback d, object? state) public override void Send(SendOrPostCallback d, object? state)
{ {
ObjectDisposedException.ThrowIf(isDisposed, this); ObjectDisposedException.ThrowIf(_isDisposed, this);
if (Thread.CurrentThread == TargetThread) if (ProxyContext.IsCurrentThread())
{ {
d(state); d(state);
return; return;
@ -132,7 +117,7 @@ namespace System.Runtime.InteropServices.JavaScript
{ {
var workItem = new WorkItem(d, state, signal); var workItem = new WorkItem(d, state, signal);
if (!Queue.Writer.TryWrite(workItem)) if (!Queue.Writer.TryWrite(workItem))
Environment.FailFast("JSSynchronizationContext.Send failed"); Environment.FailFast($"JSSynchronizationContext.Send failed, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
signal.Wait(); signal.Wait();
} }
@ -147,14 +132,16 @@ namespace System.Runtime.InteropServices.JavaScript
// this callback will arrive on the target thread, called from mono_background_exec // this callback will arrive on the target thread, called from mono_background_exec
private static void BackgroundJobHandler() private static void BackgroundJobHandler()
{ {
CurrentJSSynchronizationContext!.Pump(); var ctx = JSProxyContext.AssertIsInteropThread();
ctx.SynchronizationContext.Pump();
} }
private void Pump() private void Pump()
{ {
if (isDisposed) if (_isDisposed)
{ {
// FIXME: there could be abandoned work, but here we have no way how to propagate the failure // FIXME: there could be abandoned work, but here we have no way how to propagate the failure
// ObjectDisposedException.ThrowIf(_isDisposed, this);
return; return;
} }
try try
@ -181,9 +168,28 @@ namespace System.Runtime.InteropServices.JavaScript
finally finally
{ {
// If an item throws, we want to ensure that the next pump gets scheduled appropriately regardless. // If an item throws, we want to ensure that the next pump gets scheduled appropriately regardless.
if(!isDisposed) AwaitNewData(); if (!_isDisposed) AwaitNewData();
} }
} }
private void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing)
{
Queue.Writer.Complete();
}
previousSynchronizationContext = null;
_isDisposed = true;
}
}
internal void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
} }
} }

View File

@ -32,15 +32,19 @@ namespace System.Runtime.InteropServices.JavaScript
public static async Task<T> RunAsync<T>(Func<Task<T>> body, CancellationToken cancellationToken) public static async Task<T> RunAsync<T>(Func<Task<T>> body, CancellationToken cancellationToken)
{ {
// TODO remove main thread condition later if (JSProxyContext.MainThreadContext.IsCurrentThread())
if (Thread.CurrentThread.ManagedThreadId == 1) await JavaScriptImports.ThreadAvailable().ConfigureAwait(false); {
await JavaScriptImports.ThreadAvailable().ConfigureAwait(false);
}
return await RunAsyncImpl(body, cancellationToken).ConfigureAwait(false); return await RunAsyncImpl(body, cancellationToken).ConfigureAwait(false);
} }
public static async Task RunAsync(Func<Task> body, CancellationToken cancellationToken) public static async Task RunAsync(Func<Task> body, CancellationToken cancellationToken)
{ {
// TODO remove main thread condition later if (JSProxyContext.MainThreadContext.IsCurrentThread())
if (Thread.CurrentThread.ManagedThreadId == 1) await JavaScriptImports.ThreadAvailable().ConfigureAwait(false); {
await JavaScriptImports.ThreadAvailable().ConfigureAwait(false);
}
await RunAsyncImpl(body, cancellationToken).ConfigureAwait(false); await RunAsyncImpl(body, cancellationToken).ConfigureAwait(false);
} }

View File

@ -16,19 +16,19 @@ namespace System.Runtime.InteropServices.JavaScript
/// </summary> /// </summary>
/// <param name="_params">Parameters.</param> /// <param name="_params">Parameters.</param>
public Array(params object[] _params) public Array(params object[] _params)
: base(JavaScriptImports.CreateCSOwnedObject(nameof(Array), _params)) : base(JavaScriptImports.CreateCSOwnedObject(nameof(Array), _params), JSProxyContext.MainThreadContext)
{ {
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
LegacyHostImplementation.ThrowIfLegacyWorkerThread(); LegacyHostImplementation.ThrowIfLegacyWorkerThread();
#endif #endif
LegacyHostImplementation.RegisterCSOwnedObject(this); JSProxyContext.MainThreadContext.RegisterCSOwnedObject(this);
} }
/// <summary> /// <summary>
/// Initializes a new instance of the Array/> class. /// Initializes a new instance of the Array/> class.
/// </summary> /// </summary>
/// <param name="jsHandle">Js handle.</param> /// <param name="jsHandle">Js handle.</param>
internal Array(IntPtr jsHandle) : base(jsHandle) internal Array(IntPtr jsHandle) : base(jsHandle, JSProxyContext.MainThreadContext)
{ } { }
/// <summary> /// <summary>

View File

@ -11,19 +11,19 @@ namespace System.Runtime.InteropServices.JavaScript
/// </summary> /// </summary>
/// <param name="length">Length.</param> /// <param name="length">Length.</param>
public ArrayBuffer(int length) public ArrayBuffer(int length)
: base(JavaScriptImports.CreateCSOwnedObject(nameof(ArrayBuffer), new object[] { length })) : base(JavaScriptImports.CreateCSOwnedObject(nameof(ArrayBuffer), new object[] { length }), JSProxyContext.MainThreadContext)
{ {
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
LegacyHostImplementation.ThrowIfLegacyWorkerThread(); LegacyHostImplementation.ThrowIfLegacyWorkerThread();
#endif #endif
LegacyHostImplementation.RegisterCSOwnedObject(this); JSProxyContext.MainThreadContext.RegisterCSOwnedObject(this);
} }
/// <summary> /// <summary>
/// Initializes a new instance of the JavaScript Core ArrayBuffer class. /// Initializes a new instance of the JavaScript Core ArrayBuffer class.
/// </summary> /// </summary>
/// <param name="jsHandle">Js handle.</param> /// <param name="jsHandle">Js handle.</param>
internal ArrayBuffer(IntPtr jsHandle) : base(jsHandle) internal ArrayBuffer(IntPtr jsHandle) : base(jsHandle, JSProxyContext.MainThreadContext)
{ } { }
/// <summary> /// <summary>

View File

@ -15,12 +15,12 @@ namespace System.Runtime.InteropServices.JavaScript
/// </summary> /// </summary>
/// <param name="buffer">ArrayBuffer to use as the storage backing the new DataView object.</param> /// <param name="buffer">ArrayBuffer to use as the storage backing the new DataView object.</param>
public DataView(ArrayBuffer buffer) public DataView(ArrayBuffer buffer)
: base(JavaScriptImports.CreateCSOwnedObject(nameof(DataView), new object[] { buffer })) : base(JavaScriptImports.CreateCSOwnedObject(nameof(DataView), new object[] { buffer }), JSProxyContext.MainThreadContext)
{ {
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
LegacyHostImplementation.ThrowIfLegacyWorkerThread(); LegacyHostImplementation.ThrowIfLegacyWorkerThread();
#endif #endif
LegacyHostImplementation.RegisterCSOwnedObject(this); JSProxyContext.MainThreadContext.RegisterCSOwnedObject(this);
} }
/// <summary> /// <summary>
@ -29,12 +29,12 @@ namespace System.Runtime.InteropServices.JavaScript
/// <param name="buffer">ArrayBuffer to use as the storage backing the new DataView object.</param> /// <param name="buffer">ArrayBuffer to use as the storage backing the new DataView object.</param>
/// <param name="byteOffset">The offset, in bytes, to the first byte in the above buffer for the new view to reference. If unspecified, the buffer view starts with the first byte.</param> /// <param name="byteOffset">The offset, in bytes, to the first byte in the above buffer for the new view to reference. If unspecified, the buffer view starts with the first byte.</param>
public DataView(ArrayBuffer buffer, int byteOffset) public DataView(ArrayBuffer buffer, int byteOffset)
: base(JavaScriptImports.CreateCSOwnedObject(nameof(DataView), new object[] { buffer, byteOffset })) : base(JavaScriptImports.CreateCSOwnedObject(nameof(DataView), new object[] { buffer, byteOffset }), JSProxyContext.MainThreadContext)
{ {
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
LegacyHostImplementation.ThrowIfLegacyWorkerThread(); LegacyHostImplementation.ThrowIfLegacyWorkerThread();
#endif #endif
LegacyHostImplementation.RegisterCSOwnedObject(this); JSProxyContext.MainThreadContext.RegisterCSOwnedObject(this);
} }
/// <summary> /// <summary>
@ -44,19 +44,19 @@ namespace System.Runtime.InteropServices.JavaScript
/// <param name="byteOffset">The offset, in bytes, to the first byte in the above buffer for the new view to reference. If unspecified, the buffer view starts with the first byte.</param> /// <param name="byteOffset">The offset, in bytes, to the first byte in the above buffer for the new view to reference. If unspecified, the buffer view starts with the first byte.</param>
/// <param name="byteLength">The number of elements in the byte array. If unspecified, the view's length will match the buffer's length.</param> /// <param name="byteLength">The number of elements in the byte array. If unspecified, the view's length will match the buffer's length.</param>
public DataView(ArrayBuffer buffer, int byteOffset, int byteLength) public DataView(ArrayBuffer buffer, int byteOffset, int byteLength)
: base(JavaScriptImports.CreateCSOwnedObject(nameof(DataView), new object[] { buffer, byteOffset, byteLength })) : base(JavaScriptImports.CreateCSOwnedObject(nameof(DataView), new object[] { buffer, byteOffset, byteLength }), JSProxyContext.MainThreadContext)
{ {
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
LegacyHostImplementation.ThrowIfLegacyWorkerThread(); LegacyHostImplementation.ThrowIfLegacyWorkerThread();
#endif #endif
LegacyHostImplementation.RegisterCSOwnedObject(this); JSProxyContext.MainThreadContext.RegisterCSOwnedObject(this);
} }
/// <summary> /// <summary>
/// Initializes a new instance of the DataView class. /// Initializes a new instance of the DataView class.
/// </summary> /// </summary>
/// <param name="jsHandle">Js handle.</param> /// <param name="jsHandle">Js handle.</param>
internal DataView(IntPtr jsHandle) : base(jsHandle) internal DataView(IntPtr jsHandle) : base(jsHandle, JSProxyContext.MainThreadContext)
{ } { }
/// <summary> /// <summary>

View File

@ -16,15 +16,15 @@ namespace System.Runtime.InteropServices.JavaScript
public class Function : JSObject public class Function : JSObject
{ {
public Function(params object[] args) public Function(params object[] args)
: base(JavaScriptImports.CreateCSOwnedObject(nameof(Function), args)) : base(JavaScriptImports.CreateCSOwnedObject(nameof(Function), args), JSProxyContext.MainThreadContext)
{ {
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
LegacyHostImplementation.ThrowIfLegacyWorkerThread(); LegacyHostImplementation.ThrowIfLegacyWorkerThread();
#endif #endif
LegacyHostImplementation.RegisterCSOwnedObject(this); JSProxyContext.MainThreadContext.RegisterCSOwnedObject(this);
} }
internal Function(IntPtr jsHandle) : base(jsHandle) internal Function(IntPtr jsHandle) : base(jsHandle, JSProxyContext.MainThreadContext)
{ } { }
/// <summary> /// <summary>

View File

@ -18,12 +18,6 @@ namespace System.Runtime.InteropServices.JavaScript
jsObj?.ReleaseInFlight(); jsObj?.ReleaseInFlight();
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegisterCSOwnedObject(JSObject proxy)
{
JSHostImplementation.ThreadCsOwnedObjects[(int)proxy.JSHandle] = new WeakReference<JSObject>(proxy, trackResurrection: true);
}
public static MarshalType GetMarshalTypeFromType(Type type) public static MarshalType GetMarshalTypeFromType(Type type)
{ {
if (type is null) if (type is null)

View File

@ -9,24 +9,24 @@ namespace System.Runtime.InteropServices.JavaScript
public sealed class Uint8Array : JSObject public sealed class Uint8Array : JSObject
{ {
public Uint8Array(int length) public Uint8Array(int length)
: base(JavaScriptImports.CreateCSOwnedObject(nameof(Uint8Array), new object[] { length })) : base(JavaScriptImports.CreateCSOwnedObject(nameof(Uint8Array), new object[] { length }), JSProxyContext.MainThreadContext)
{ {
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
LegacyHostImplementation.ThrowIfLegacyWorkerThread(); LegacyHostImplementation.ThrowIfLegacyWorkerThread();
#endif #endif
LegacyHostImplementation.RegisterCSOwnedObject(this); JSProxyContext.MainThreadContext.RegisterCSOwnedObject(this);
} }
public Uint8Array(ArrayBuffer buffer) public Uint8Array(ArrayBuffer buffer)
: base(JavaScriptImports.CreateCSOwnedObject(nameof(Uint8Array), new object[] { buffer })) : base(JavaScriptImports.CreateCSOwnedObject(nameof(Uint8Array), new object[] { buffer }), JSProxyContext.MainThreadContext)
{ {
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
LegacyHostImplementation.ThrowIfLegacyWorkerThread(); LegacyHostImplementation.ThrowIfLegacyWorkerThread();
#endif #endif
LegacyHostImplementation.RegisterCSOwnedObject(this); JSProxyContext.MainThreadContext.RegisterCSOwnedObject(this);
} }
internal Uint8Array(IntPtr jsHandle) : base(jsHandle) internal Uint8Array(IntPtr jsHandle) : base(jsHandle, JSProxyContext.MainThreadContext)
{ } { }
public int Length public int Length

View File

@ -133,7 +133,8 @@ namespace System.Runtime.InteropServices.JavaScript
return; return;
} }
slot.Type = MarshalerType.ArraySegment; slot.Type = MarshalerType.ArraySegment;
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(value.Array, GCHandleType.Pinned); var ctx = ToJSContext;
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(value.Array, GCHandleType.Pinned);
var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(value.Array)); var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(value.Array));
slot.IntPtrValue = refPtr + value.Offset; slot.IntPtrValue = refPtr + value.Offset;
slot.Length = value.Count; slot.Length = value.Count;

View File

@ -135,7 +135,8 @@ namespace System.Runtime.InteropServices.JavaScript
return; return;
} }
slot.Type = MarshalerType.ArraySegment; slot.Type = MarshalerType.ArraySegment;
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(value.Array, GCHandleType.Pinned); var ctx = ToJSContext;
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(value.Array, GCHandleType.Pinned);
var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(value.Array)); var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(value.Array));
slot.IntPtrValue = refPtr + (value.Offset * sizeof(double)); slot.IntPtrValue = refPtr + (value.Offset * sizeof(double));
slot.Length = value.Count; slot.Length = value.Count;

View File

@ -33,7 +33,8 @@ namespace System.Runtime.InteropServices.JavaScript
if (slot.JSHandle != IntPtr.Zero) if (slot.JSHandle != IntPtr.Zero)
{ {
// this is JSException round-trip // this is JSException round-trip
jsException = JSHostImplementation.CreateCSOwnedProxy(slot.JSHandle); var ctx = ToManagedContext;
jsException = ctx.CreateCSOwnedProxy(slot.JSHandle);
} }
string? message; string? message;
@ -65,11 +66,21 @@ namespace System.Runtime.InteropServices.JavaScript
var jse = cpy as JSException; var jse = cpy as JSException;
if (jse != null && jse.jsException != null) if (jse != null && jse.jsException != null)
{ {
ObjectDisposedException.ThrowIf(jse.jsException.IsDisposed, value);
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
JSObject.AssertThreadAffinity(value); JSObject.AssertThreadAffinity(value);
var ctx = jse.jsException.ProxyContext;
if (JSProxyContext.CapturingState == JSProxyContext.JSImportOperationState.JSImportParams)
{
JSProxyContext.CaptureContextFromParameter(ctx);
slot.ContextHandle = ctx.ContextHandle;
}
else if (slot.ContextHandle != ctx.ContextHandle)
{
Environment.FailFast($"ContextHandle mismatch, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
}
#endif #endif
// this is JSException roundtrip // this is JSException roundtrip
ObjectDisposedException.ThrowIf(jse.jsException.IsDisposed, value);
slot.Type = MarshalerType.JSException; slot.Type = MarshalerType.JSException;
slot.JSHandle = jse.jsException.JSHandle; slot.JSHandle = jse.jsException.JSHandle;
} }
@ -77,7 +88,9 @@ namespace System.Runtime.InteropServices.JavaScript
{ {
ToJS(cpy.Message); ToJS(cpy.Message);
slot.Type = MarshalerType.Exception; slot.Type = MarshalerType.Exception;
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cpy);
var ctx = ToJSContext;
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cpy);
} }
} }
} }

View File

@ -9,9 +9,9 @@ namespace System.Runtime.InteropServices.JavaScript
{ {
private JSObject JSObject; private JSObject JSObject;
public ActionJS(IntPtr jsHandle) public ActionJS(JSObject holder)
{ {
JSObject = JSHostImplementation.CreateCSOwnedProxy(jsHandle); JSObject = holder;
} }
public void InvokeJS() public void InvokeJS()
@ -26,8 +26,14 @@ namespace System.Runtime.InteropServices.JavaScript
Span<JSMarshalerArgument> arguments = stackalloc JSMarshalerArgument[4]; Span<JSMarshalerArgument> arguments = stackalloc JSMarshalerArgument[4];
ref JSMarshalerArgument args_exception = ref arguments[0]; ref JSMarshalerArgument args_exception = ref arguments[0];
ref JSMarshalerArgument args_return = ref arguments[1]; ref JSMarshalerArgument args_return = ref arguments[1];
#if FEATURE_WASM_THREADS
args_exception.InitializeWithContext(JSObject.ProxyContext);
args_return.InitializeWithContext(JSObject.ProxyContext);
JSProxyContext.JSImportNoCapture();
#else
args_exception.Initialize(); args_exception.Initialize();
args_return.Initialize(); args_return.Initialize();
#endif
JSFunctionBinding.InvokeJSFunction(JSObject, arguments); JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
} }
@ -39,9 +45,9 @@ namespace System.Runtime.InteropServices.JavaScript
private ArgumentToJSCallback<T> Arg1Marshaler; private ArgumentToJSCallback<T> Arg1Marshaler;
private JSObject JSObject; private JSObject JSObject;
public ActionJS(IntPtr jsHandle, ArgumentToJSCallback<T> arg1Marshaler) public ActionJS(JSObject holder, ArgumentToJSCallback<T> arg1Marshaler)
{ {
JSObject = JSHostImplementation.CreateCSOwnedProxy(jsHandle); JSObject = holder;
Arg1Marshaler = arg1Marshaler; Arg1Marshaler = arg1Marshaler;
} }
@ -56,10 +62,18 @@ namespace System.Runtime.InteropServices.JavaScript
ref JSMarshalerArgument args_return = ref arguments[1]; ref JSMarshalerArgument args_return = ref arguments[1];
ref JSMarshalerArgument args_arg1 = ref arguments[2]; ref JSMarshalerArgument args_arg1 = ref arguments[2];
#if FEATURE_WASM_THREADS
args_exception.InitializeWithContext(JSObject.ProxyContext);
args_return.InitializeWithContext(JSObject.ProxyContext);
args_arg1.InitializeWithContext(JSObject.ProxyContext);
JSProxyContext.JSImportNoCapture();
#else
args_exception.Initialize(); args_exception.Initialize();
args_return.Initialize(); args_return.Initialize();
#endif
Arg1Marshaler(ref args_arg1, arg1); Arg1Marshaler(ref args_arg1, arg1);
JSFunctionBinding.InvokeJSFunction(JSObject, arguments); JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
} }
} }
@ -70,9 +84,9 @@ namespace System.Runtime.InteropServices.JavaScript
private ArgumentToJSCallback<T2> Arg2Marshaler; private ArgumentToJSCallback<T2> Arg2Marshaler;
private JSObject JSObject; private JSObject JSObject;
public ActionJS(IntPtr jsHandle, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler) public ActionJS(JSObject holder, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler)
{ {
JSObject = JSHostImplementation.CreateCSOwnedProxy(jsHandle); JSObject = holder;
Arg1Marshaler = arg1Marshaler; Arg1Marshaler = arg1Marshaler;
Arg2Marshaler = arg2Marshaler; Arg2Marshaler = arg2Marshaler;
} }
@ -89,11 +103,20 @@ namespace System.Runtime.InteropServices.JavaScript
ref JSMarshalerArgument args_arg1 = ref arguments[2]; ref JSMarshalerArgument args_arg1 = ref arguments[2];
ref JSMarshalerArgument args_arg2 = ref arguments[3]; ref JSMarshalerArgument args_arg2 = ref arguments[3];
#if FEATURE_WASM_THREADS
args_exception.InitializeWithContext(JSObject.ProxyContext);
args_return.InitializeWithContext(JSObject.ProxyContext);
args_arg1.InitializeWithContext(JSObject.ProxyContext);
args_arg2.InitializeWithContext(JSObject.ProxyContext);
JSProxyContext.JSImportNoCapture();
#else
args_exception.Initialize(); args_exception.Initialize();
args_return.Initialize(); args_return.Initialize();
#endif
Arg1Marshaler(ref args_arg1, arg1); Arg1Marshaler(ref args_arg1, arg1);
Arg2Marshaler(ref args_arg2, arg2); Arg2Marshaler(ref args_arg2, arg2);
JSFunctionBinding.InvokeJSFunction(JSObject, arguments); JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
} }
} }
@ -105,9 +128,9 @@ namespace System.Runtime.InteropServices.JavaScript
private ArgumentToJSCallback<T3> Arg3Marshaler; private ArgumentToJSCallback<T3> Arg3Marshaler;
private JSObject JSObject; private JSObject JSObject;
public ActionJS(IntPtr jsHandle, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToJSCallback<T3> arg3Marshaler) public ActionJS(JSObject holder, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToJSCallback<T3> arg3Marshaler)
{ {
JSObject = JSHostImplementation.CreateCSOwnedProxy(jsHandle); JSObject = holder;
Arg1Marshaler = arg1Marshaler; Arg1Marshaler = arg1Marshaler;
Arg2Marshaler = arg2Marshaler; Arg2Marshaler = arg2Marshaler;
Arg3Marshaler = arg3Marshaler; Arg3Marshaler = arg3Marshaler;
@ -126,12 +149,22 @@ namespace System.Runtime.InteropServices.JavaScript
ref JSMarshalerArgument args_arg2 = ref arguments[3]; ref JSMarshalerArgument args_arg2 = ref arguments[3];
ref JSMarshalerArgument args_arg3 = ref arguments[4]; ref JSMarshalerArgument args_arg3 = ref arguments[4];
#if FEATURE_WASM_THREADS
args_exception.InitializeWithContext(JSObject.ProxyContext);
args_return.InitializeWithContext(JSObject.ProxyContext);
args_arg1.InitializeWithContext(JSObject.ProxyContext);
args_arg2.InitializeWithContext(JSObject.ProxyContext);
args_arg3.InitializeWithContext(JSObject.ProxyContext);
JSProxyContext.JSImportNoCapture();
#else
args_exception.Initialize(); args_exception.Initialize();
args_return.Initialize(); args_return.Initialize();
#endif
Arg1Marshaler(ref args_arg1, arg1); Arg1Marshaler(ref args_arg1, arg1);
Arg2Marshaler(ref args_arg2, arg2); Arg2Marshaler(ref args_arg2, arg2);
Arg3Marshaler(ref args_arg3, arg3); Arg3Marshaler(ref args_arg3, arg3);
JSFunctionBinding.InvokeJSFunction(JSObject, arguments); JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
} }
} }
@ -149,7 +182,9 @@ namespace System.Runtime.InteropServices.JavaScript
return; return;
} }
value = new ActionJS(slot.JSHandle).InvokeJS; var ctx = ToManagedContext;
var holder = ctx.CreateCSOwnedProxy(slot.JSHandle);
value = new ActionJS(holder).InvokeJS;
} }
/// <summary> /// <summary>
@ -167,7 +202,9 @@ namespace System.Runtime.InteropServices.JavaScript
return; return;
} }
value = new ActionJS<T>(slot.JSHandle, arg1Marshaler).InvokeJS; var ctx = ToManagedContext;
var holder = ctx.CreateCSOwnedProxy(slot.JSHandle);
value = new ActionJS<T>(holder, arg1Marshaler).InvokeJS;
} }
/// <summary> /// <summary>
@ -187,7 +224,9 @@ namespace System.Runtime.InteropServices.JavaScript
return; return;
} }
value = new ActionJS<T1, T2>(slot.JSHandle, arg1Marshaler, arg2Marshaler).InvokeJS; var ctx = ToManagedContext;
var holder = ctx.CreateCSOwnedProxy(slot.JSHandle);
value = new ActionJS<T1, T2>(holder, arg1Marshaler, arg2Marshaler).InvokeJS;
} }
/// <summary> /// <summary>
@ -209,7 +248,9 @@ namespace System.Runtime.InteropServices.JavaScript
return; return;
} }
value = new ActionJS<T1, T2, T3>(slot.JSHandle, arg1Marshaler, arg2Marshaler, arg3Marshaler).InvokeJS; var ctx = ToManagedContext;
var holder = ctx.CreateCSOwnedProxy(slot.JSHandle);
value = new ActionJS<T1, T2, T3>(holder, arg1Marshaler, arg2Marshaler, arg3Marshaler).InvokeJS;
} }
private sealed class FuncJS<TResult> private sealed class FuncJS<TResult>
@ -217,9 +258,9 @@ namespace System.Runtime.InteropServices.JavaScript
private JSObject JSObject; private JSObject JSObject;
private ArgumentToManagedCallback<TResult> ResMarshaler; private ArgumentToManagedCallback<TResult> ResMarshaler;
public FuncJS(IntPtr jsHandle, ArgumentToManagedCallback<TResult> resMarshaler) public FuncJS(JSObject holder, ArgumentToManagedCallback<TResult> resMarshaler)
{ {
JSObject = JSHostImplementation.CreateCSOwnedProxy(jsHandle); JSObject = holder;
ResMarshaler = resMarshaler; ResMarshaler = resMarshaler;
} }
@ -235,12 +276,19 @@ namespace System.Runtime.InteropServices.JavaScript
Span<JSMarshalerArgument> arguments = stackalloc JSMarshalerArgument[4]; Span<JSMarshalerArgument> arguments = stackalloc JSMarshalerArgument[4];
ref JSMarshalerArgument args_exception = ref arguments[0]; ref JSMarshalerArgument args_exception = ref arguments[0];
ref JSMarshalerArgument args_return = ref arguments[1]; ref JSMarshalerArgument args_return = ref arguments[1];
#if FEATURE_WASM_THREADS
args_exception.InitializeWithContext(JSObject.ProxyContext);
args_return.InitializeWithContext(JSObject.ProxyContext);
JSProxyContext.JSImportNoCapture();
#else
args_exception.Initialize(); args_exception.Initialize();
args_return.Initialize(); args_return.Initialize();
#endif
JSFunctionBinding.InvokeJSFunction(JSObject, arguments); JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
ResMarshaler(ref args_return, out TResult res); ResMarshaler(ref args_return, out TResult res);
return res; return res;
} }
@ -252,9 +300,9 @@ namespace System.Runtime.InteropServices.JavaScript
private ArgumentToManagedCallback<TResult> ResMarshaler; private ArgumentToManagedCallback<TResult> ResMarshaler;
private JSObject JSObject; private JSObject JSObject;
public FuncJS(IntPtr jsHandle, ArgumentToJSCallback<T> arg1Marshaler, ArgumentToManagedCallback<TResult> resMarshaler) public FuncJS(JSObject holder, ArgumentToJSCallback<T> arg1Marshaler, ArgumentToManagedCallback<TResult> resMarshaler)
{ {
JSObject = JSHostImplementation.CreateCSOwnedProxy(jsHandle); JSObject = holder;
Arg1Marshaler = arg1Marshaler; Arg1Marshaler = arg1Marshaler;
ResMarshaler = resMarshaler; ResMarshaler = resMarshaler;
} }
@ -270,8 +318,15 @@ namespace System.Runtime.InteropServices.JavaScript
ref JSMarshalerArgument args_return = ref arguments[1]; ref JSMarshalerArgument args_return = ref arguments[1];
ref JSMarshalerArgument args_arg1 = ref arguments[2]; ref JSMarshalerArgument args_arg1 = ref arguments[2];
#if FEATURE_WASM_THREADS
args_exception.InitializeWithContext(JSObject.ProxyContext);
args_return.InitializeWithContext(JSObject.ProxyContext);
args_arg1.InitializeWithContext(JSObject.ProxyContext);
JSProxyContext.JSImportNoCapture();
#else
args_exception.Initialize(); args_exception.Initialize();
args_return.Initialize(); args_return.Initialize();
#endif
Arg1Marshaler(ref args_arg1, arg1); Arg1Marshaler(ref args_arg1, arg1);
JSFunctionBinding.InvokeJSFunction(JSObject, arguments); JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
@ -288,9 +343,9 @@ namespace System.Runtime.InteropServices.JavaScript
private ArgumentToManagedCallback<TResult> ResMarshaler; private ArgumentToManagedCallback<TResult> ResMarshaler;
private JSObject JSObject; private JSObject JSObject;
public FuncJS(IntPtr jsHandle, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToManagedCallback<TResult> resMarshaler) public FuncJS(JSObject holder, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToManagedCallback<TResult> resMarshaler)
{ {
JSObject = JSHostImplementation.CreateCSOwnedProxy(jsHandle); JSObject = holder;
Arg1Marshaler = arg1Marshaler; Arg1Marshaler = arg1Marshaler;
Arg2Marshaler = arg2Marshaler; Arg2Marshaler = arg2Marshaler;
ResMarshaler = resMarshaler; ResMarshaler = resMarshaler;
@ -308,8 +363,16 @@ namespace System.Runtime.InteropServices.JavaScript
ref JSMarshalerArgument args_arg1 = ref arguments[2]; ref JSMarshalerArgument args_arg1 = ref arguments[2];
ref JSMarshalerArgument args_arg2 = ref arguments[3]; ref JSMarshalerArgument args_arg2 = ref arguments[3];
#if FEATURE_WASM_THREADS
args_exception.InitializeWithContext(JSObject.ProxyContext);
args_return.InitializeWithContext(JSObject.ProxyContext);
args_arg1.InitializeWithContext(JSObject.ProxyContext);
args_arg2.InitializeWithContext(JSObject.ProxyContext);
JSProxyContext.JSImportNoCapture();
#else
args_exception.Initialize(); args_exception.Initialize();
args_return.Initialize(); args_return.Initialize();
#endif
Arg1Marshaler(ref args_arg1, arg1); Arg1Marshaler(ref args_arg1, arg1);
Arg2Marshaler(ref args_arg2, arg2); Arg2Marshaler(ref args_arg2, arg2);
@ -328,9 +391,9 @@ namespace System.Runtime.InteropServices.JavaScript
private ArgumentToManagedCallback<TResult> ResMarshaler; private ArgumentToManagedCallback<TResult> ResMarshaler;
private JSObject JSObject; private JSObject JSObject;
public FuncJS(IntPtr jsHandle, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToJSCallback<T3> arg3Marshaler, ArgumentToManagedCallback<TResult> resMarshaler) public FuncJS(JSObject holder, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToJSCallback<T3> arg3Marshaler, ArgumentToManagedCallback<TResult> resMarshaler)
{ {
JSObject = JSHostImplementation.CreateCSOwnedProxy(jsHandle); JSObject = holder;
Arg1Marshaler = arg1Marshaler; Arg1Marshaler = arg1Marshaler;
Arg2Marshaler = arg2Marshaler; Arg2Marshaler = arg2Marshaler;
Arg3Marshaler = arg3Marshaler; Arg3Marshaler = arg3Marshaler;
@ -350,15 +413,24 @@ namespace System.Runtime.InteropServices.JavaScript
ref JSMarshalerArgument args_arg2 = ref arguments[3]; ref JSMarshalerArgument args_arg2 = ref arguments[3];
ref JSMarshalerArgument args_arg3 = ref arguments[4]; ref JSMarshalerArgument args_arg3 = ref arguments[4];
#if FEATURE_WASM_THREADS
args_exception.InitializeWithContext(JSObject.ProxyContext);
args_return.InitializeWithContext(JSObject.ProxyContext);
args_arg1.InitializeWithContext(JSObject.ProxyContext);
args_arg2.InitializeWithContext(JSObject.ProxyContext);
args_arg3.InitializeWithContext(JSObject.ProxyContext);
JSProxyContext.JSImportNoCapture();
#else
args_exception.Initialize(); args_exception.Initialize();
args_return.Initialize(); args_return.Initialize();
#endif
Arg1Marshaler(ref args_arg1, arg1); Arg1Marshaler(ref args_arg1, arg1);
Arg2Marshaler(ref args_arg2, arg2); Arg2Marshaler(ref args_arg2, arg2);
Arg3Marshaler(ref args_arg3, arg3); Arg3Marshaler(ref args_arg3, arg3);
JSFunctionBinding.InvokeJSFunction(JSObject, arguments); JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
ResMarshaler(ref args_return, out TResult res); ResMarshaler(ref args_return, out TResult res);
return res; return res;
} }
} }
@ -378,7 +450,9 @@ namespace System.Runtime.InteropServices.JavaScript
return; return;
} }
value = new FuncJS<TResult>(slot.JSHandle, resMarshaler).InvokeJS; var ctx = ToManagedContext;
var holder = ctx.CreateCSOwnedProxy(slot.JSHandle);
value = new FuncJS<TResult>(holder, resMarshaler).InvokeJS;
} }
/// <summary> /// <summary>
@ -398,7 +472,9 @@ namespace System.Runtime.InteropServices.JavaScript
return; return;
} }
value = new FuncJS<T, TResult>(slot.JSHandle, arg1Marshaler, resMarshaler).InvokeJS; var ctx = ToManagedContext;
var holder = ctx.CreateCSOwnedProxy(slot.JSHandle);
value = new FuncJS<T, TResult>(holder, arg1Marshaler, resMarshaler).InvokeJS;
} }
@ -421,7 +497,9 @@ namespace System.Runtime.InteropServices.JavaScript
return; return;
} }
value = new FuncJS<T1, T2, TResult>(slot.JSHandle, arg1Marshaler, arg2Marshaler, resMarshaler).InvokeJS; var ctx = ToManagedContext;
var holder = ctx.CreateCSOwnedProxy(slot.JSHandle);
value = new FuncJS<T1, T2, TResult>(holder, arg1Marshaler, arg2Marshaler, resMarshaler).InvokeJS;
} }
/// <summary> /// <summary>
@ -444,8 +522,9 @@ namespace System.Runtime.InteropServices.JavaScript
value = null; value = null;
return; return;
} }
var ctx = ToManagedContext;
value = new FuncJS<T1, T2, T3, TResult>(slot.JSHandle, arg1Marshaler, arg2Marshaler, arg3Marshaler, resMarshaler).InvokeJS; var holder = ctx.CreateCSOwnedProxy(slot.JSHandle);
value = new FuncJS<T1, T2, T3, TResult>(holder, arg1Marshaler, arg2Marshaler, arg3Marshaler, resMarshaler).InvokeJS;
} }
/// <summary> /// <summary>
@ -463,7 +542,8 @@ namespace System.Runtime.InteropServices.JavaScript
// eventual exception is handled by C# caller // eventual exception is handled by C# caller
}; };
slot.Type = MarshalerType.Function; slot.Type = MarshalerType.Function;
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cb); var ctx = ToJSContext;
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cb);
} }
/// <summary> /// <summary>
@ -484,7 +564,8 @@ namespace System.Runtime.InteropServices.JavaScript
// eventual exception is handled by C# caller // eventual exception is handled by C# caller
}; };
slot.Type = MarshalerType.Action; slot.Type = MarshalerType.Action;
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cb); var ctx = ToJSContext;
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cb);
} }
/// <summary> /// <summary>
@ -509,7 +590,8 @@ namespace System.Runtime.InteropServices.JavaScript
// eventual exception is handled by C# caller // eventual exception is handled by C# caller
}; };
slot.Type = MarshalerType.Action; slot.Type = MarshalerType.Action;
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cb); var ctx = ToJSContext;
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cb);
} }
/// <summary> /// <summary>
@ -538,7 +620,8 @@ namespace System.Runtime.InteropServices.JavaScript
// eventual exception is handled by C# caller // eventual exception is handled by C# caller
}; };
slot.Type = MarshalerType.Action; slot.Type = MarshalerType.Action;
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cb); var ctx = ToJSContext;
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cb);
} }
/// <summary> /// <summary>
@ -559,7 +642,8 @@ namespace System.Runtime.InteropServices.JavaScript
// eventual exception is handled by C# caller // eventual exception is handled by C# caller
}; };
slot.Type = MarshalerType.Function; slot.Type = MarshalerType.Function;
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cb); var ctx = ToJSContext;
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cb);
} }
/// <summary> /// <summary>
@ -584,7 +668,8 @@ namespace System.Runtime.InteropServices.JavaScript
// eventual exception is handled by C# caller // eventual exception is handled by C# caller
}; };
slot.Type = MarshalerType.Function; slot.Type = MarshalerType.Function;
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cb); var ctx = ToJSContext;
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cb);
} }
/// <summary> /// <summary>
@ -613,7 +698,8 @@ namespace System.Runtime.InteropServices.JavaScript
// eventual exception is handled by C# caller // eventual exception is handled by C# caller
}; };
slot.Type = MarshalerType.Function; slot.Type = MarshalerType.Function;
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cb); var ctx = ToJSContext;
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cb);
} }
/// <summary> /// <summary>
@ -646,7 +732,8 @@ namespace System.Runtime.InteropServices.JavaScript
// eventual exception is handled by C# caller // eventual exception is handled by C# caller
}; };
slot.Type = MarshalerType.Function; slot.Type = MarshalerType.Function;
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cb); var ctx = ToJSContext;
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cb);
} }
} }
} }

View File

@ -132,8 +132,9 @@ namespace System.Runtime.InteropServices.JavaScript
slot.Type = MarshalerType.None; slot.Type = MarshalerType.None;
return; return;
} }
var ctx = ToJSContext;
slot.Type = MarshalerType.ArraySegment; slot.Type = MarshalerType.ArraySegment;
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(value.Array, GCHandleType.Pinned); slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(value.Array, GCHandleType.Pinned);
var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(value.Array)); var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(value.Array));
slot.IntPtrValue = refPtr + (value.Offset * sizeof(int)); slot.IntPtrValue = refPtr + (value.Offset * sizeof(int));
slot.Length = value.Count; slot.Length = value.Count;

View File

@ -20,7 +20,8 @@ namespace System.Runtime.InteropServices.JavaScript
value = null; value = null;
return; return;
} }
value = JSHostImplementation.CreateCSOwnedProxy(slot.JSHandle); var ctx = ToManagedContext;
value = ctx.CreateCSOwnedProxy(slot.JSHandle);
} }
/// <summary> /// <summary>
@ -34,13 +35,25 @@ namespace System.Runtime.InteropServices.JavaScript
if (value == null) if (value == null)
{ {
slot.Type = MarshalerType.None; slot.Type = MarshalerType.None;
// Note: when null JSObject is passed as argument, it can't be used to capture the target thread in JSProxyContext.CapturedInstance
// in case there is no other argument to capture it from, the call will be dispatched according to JSProxyContext.Default
} }
else else
{ {
ObjectDisposedException.ThrowIf(value.IsDisposed, value);
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
JSObject.AssertThreadAffinity(value); JSObject.AssertThreadAffinity(value);
var ctx = value.ProxyContext;
if (JSProxyContext.CapturingState == JSProxyContext.JSImportOperationState.JSImportParams)
{
JSProxyContext.CaptureContextFromParameter(ctx);
slot.ContextHandle = ctx.ContextHandle;
}
else if (slot.ContextHandle != ctx.ContextHandle)
{
Environment.FailFast($"ContextHandle mismatch, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
}
#endif #endif
ObjectDisposedException.ThrowIf(value.IsDisposed, value);
slot.Type = MarshalerType.JSObject; slot.Type = MarshalerType.JSObject;
slot.JSHandle = value.JSHandle; slot.JSHandle = value.JSHandle;
} }

View File

@ -317,7 +317,8 @@ namespace System.Runtime.InteropServices.JavaScript
else else
{ {
slot.Type = MarshalerType.Object; slot.Type = MarshalerType.Object;
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(value); var ctx = ToJSContext;
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(value);
} }
} }

View File

@ -4,6 +4,8 @@
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.ComponentModel;
using System.Threading;
using static System.Runtime.InteropServices.JavaScript.JSHostImplementation; using static System.Runtime.InteropServices.JavaScript.JSHostImplementation;
namespace System.Runtime.InteropServices.JavaScript namespace System.Runtime.InteropServices.JavaScript
@ -17,7 +19,7 @@ namespace System.Runtime.InteropServices.JavaScript
/// <typeparam name="T">Type of the marshaled value.</typeparam> /// <typeparam name="T">Type of the marshaled value.</typeparam>
/// <param name="arg">The low-level argument representation.</param> /// <param name="arg">The low-level argument representation.</param>
/// <param name="value">The value to be marshaled.</param> /// <param name="value">The value to be marshaled.</param>
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] [EditorBrowsableAttribute(EditorBrowsableState.Never)]
public delegate void ArgumentToManagedCallback<T>(ref JSMarshalerArgument arg, out T value); public delegate void ArgumentToManagedCallback<T>(ref JSMarshalerArgument arg, out T value);
/// <summary> /// <summary>
@ -27,7 +29,7 @@ namespace System.Runtime.InteropServices.JavaScript
/// <typeparam name="T">Type of the marshaled value.</typeparam> /// <typeparam name="T">Type of the marshaled value.</typeparam>
/// <param name="arg">The low-level argument representation.</param> /// <param name="arg">The low-level argument representation.</param>
/// <param name="value">The value to be marshaled.</param> /// <param name="value">The value to be marshaled.</param>
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] [EditorBrowsableAttribute(EditorBrowsableState.Never)]
public delegate void ArgumentToJSCallback<T>(ref JSMarshalerArgument arg, T value); public delegate void ArgumentToJSCallback<T>(ref JSMarshalerArgument arg, T value);
/// <summary> /// <summary>
@ -43,30 +45,38 @@ namespace System.Runtime.InteropServices.JavaScript
value = null; value = null;
return; return;
} }
PromiseHolder holder = GetPromiseHolder(slot.GCHandle); var ctx = ToManagedContext;
TaskCompletionSource tcs = new TaskCompletionSource(holder); lock (ctx)
ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) =>
{ {
if (arguments_buffer == null) PromiseHolder holder = ctx.GetPromiseHolder(slot.GCHandle);
TaskCompletionSource tcs = new TaskCompletionSource(holder);
ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) =>
{ {
tcs.TrySetException(new TaskCanceledException("WebWorker which is origin of the Promise is being terminated.")); if (arguments_buffer == null)
return; {
} tcs.TrySetException(new TaskCanceledException("WebWorker which is origin of the Promise is being terminated."));
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // set by caller when this is SetException call return;
// arg_3 set by caller when this is SetResult call, un-used here }
if (arg_2.slot.Type != MarshalerType.None) ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // set by caller when this is SetException call
{ // arg_3 set by caller when this is SetResult call, un-used here
arg_2.ToManaged(out Exception? fail); if (arg_2.slot.Type != MarshalerType.None)
tcs.SetException(fail!); {
} arg_2.ToManaged(out Exception? fail);
else tcs.SetException(fail!);
{ }
tcs.SetResult(); else
} {
// eventual exception is handled by caller tcs.SetResult();
}; }
holder.Callback = callback; // eventual exception is handled by caller
value = tcs.Task; };
holder.Callback = callback;
value = tcs.Task;
#if FEATURE_WASM_THREADS
// if the other thread created it, signal that it's ready
holder.CallbackReady?.Set();
#endif
}
} }
/// <summary> /// <summary>
@ -74,8 +84,8 @@ namespace System.Runtime.InteropServices.JavaScript
/// It's used by JSImport code generator and should not be used by developers in source code. /// It's used by JSImport code generator and should not be used by developers in source code.
/// </summary> /// </summary>
/// <param name="value">The value to be marshaled.</param> /// <param name="value">The value to be marshaled.</param>
/// <param name="marshaler">The generated callback which marshals the result value of the <see cref="System.Threading.Tasks.Task"/>.</param> /// <param name="marshaler">The generated callback which marshals the result value of the <see cref="Task"/>.</param>
/// <typeparam name="T">Type of marshaled result of the <see cref="System.Threading.Tasks.Task"/>.</typeparam> /// <typeparam name="T">Type of marshaled result of the <see cref="Task"/>.</typeparam>
public unsafe void ToManaged<T>(out Task<T>? value, ArgumentToManagedCallback<T> marshaler) public unsafe void ToManaged<T>(out Task<T>? value, ArgumentToManagedCallback<T> marshaler)
{ {
// there is no nice way in JS how to check that JS promise is already resolved, to send MarshalerType.TaskRejected, MarshalerType.TaskResolved // there is no nice way in JS how to check that JS promise is already resolved, to send MarshalerType.TaskRejected, MarshalerType.TaskResolved
@ -84,52 +94,43 @@ namespace System.Runtime.InteropServices.JavaScript
value = null; value = null;
return; return;
} }
PromiseHolder holder = GetPromiseHolder(slot.GCHandle); var ctx = ToManagedContext;
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>(holder); lock (ctx)
ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) =>
{ {
if (arguments_buffer == null) var holder = ctx.GetPromiseHolder(slot.GCHandle);
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>(holder);
ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) =>
{ {
tcs.TrySetException(new TaskCanceledException("WebWorker which is origin of the Promise is being terminated.")); if (arguments_buffer == null)
return; {
} tcs.TrySetException(new TaskCanceledException("WebWorker which is origin of the Promise is being terminated."));
return;
}
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // set by caller when this is SetException call ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // set by caller when this is SetException call
ref JSMarshalerArgument arg_3 = ref arguments_buffer[4]; // set by caller when this is SetResult call ref JSMarshalerArgument arg_3 = ref arguments_buffer[4]; // set by caller when this is SetResult call
if (arg_2.slot.Type != MarshalerType.None) if (arg_2.slot.Type != MarshalerType.None)
{ {
arg_2.ToManaged(out Exception? fail); arg_2.ToManaged(out Exception? fail);
if (fail == null) throw new InvalidOperationException(SR.FailedToMarshalException); if (fail == null) throw new InvalidOperationException(SR.FailedToMarshalException);
tcs.SetException(fail); tcs.SetException(fail);
} }
else else
{ {
marshaler(ref arg_3, out T result); marshaler(ref arg_3, out T result);
tcs.SetResult(result); tcs.SetResult(result);
} }
// eventual exception is handled by caller // eventual exception is handled by caller
}; };
holder.Callback = callback; holder.Callback = callback;
value = tcs.Task; value = tcs.Task;
#if FEATURE_WASM_THREADS
// if the other thread created it, signal that it's ready
holder.CallbackReady?.Set();
#endif
}
} }
// TODO unregister and collect pending PromiseHolder also when no C# is awaiting ?
private static PromiseHolder GetPromiseHolder(nint gcHandle)
{
PromiseHolder holder;
if (IsGCVHandle(gcHandle))
{
// this path should only happen when the Promise is passed as argument of JSExport
holder = new PromiseHolder(gcHandle);
// TODO for MT this must hit the ThreadJsOwnedHolders in the correct thread
ThreadJsOwnedHolders.Add(gcHandle, holder);
}
else
{
holder = (PromiseHolder)((GCHandle)gcHandle).Target!;
}
return holder;
}
internal void ToJSDynamic(Task? value) internal void ToJSDynamic(Task? value)
{ {
@ -167,10 +168,12 @@ namespace System.Runtime.InteropServices.JavaScript
} }
} }
var ctx = ToJSContext;
if (slot.Type != MarshalerType.TaskPreCreated) if (slot.Type != MarshalerType.TaskPreCreated)
{ {
// this path should only happen when the Task is passed as argument of JSImport // this path should only happen when the Task is passed as argument of JSImport
slot.JSHandle = AllocJSVHandle(); slot.JSHandle = ctx.AllocJSVHandle();
slot.Type = MarshalerType.Task; slot.Type = MarshalerType.Task;
} }
else else
@ -179,7 +182,7 @@ namespace System.Runtime.InteropServices.JavaScript
// promise and handle is pre-allocated in slot.JSHandle // promise and handle is pre-allocated in slot.JSHandle
} }
var taskHolder = new JSObject(slot.JSHandle); var taskHolder = ctx.CreateCSOwnedProxy(slot.JSHandle);
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
task.ContinueWith(Complete, taskHolder, TaskScheduler.FromCurrentSynchronizationContext()); task.ContinueWith(Complete, taskHolder, TaskScheduler.FromCurrentSynchronizationContext());
@ -245,10 +248,12 @@ namespace System.Runtime.InteropServices.JavaScript
} }
} }
var ctx = ToJSContext;
if (slot.Type != MarshalerType.TaskPreCreated) if (slot.Type != MarshalerType.TaskPreCreated)
{ {
// this path should only happen when the Task is passed as argument of JSImport // this path should only happen when the Task is passed as argument of JSImport
slot.JSHandle = AllocJSVHandle(); slot.JSHandle = ctx.AllocJSVHandle();
slot.Type = MarshalerType.Task; slot.Type = MarshalerType.Task;
} }
else else
@ -257,7 +262,7 @@ namespace System.Runtime.InteropServices.JavaScript
// promise and handle is pre-allocated in slot.JSHandle // promise and handle is pre-allocated in slot.JSHandle
} }
var taskHolder = new JSObject(slot.JSHandle); var taskHolder = ctx.CreateCSOwnedProxy(slot.JSHandle);
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
task.ContinueWith(Complete, taskHolder, TaskScheduler.FromCurrentSynchronizationContext()); task.ContinueWith(Complete, taskHolder, TaskScheduler.FromCurrentSynchronizationContext());
@ -316,10 +321,11 @@ namespace System.Runtime.InteropServices.JavaScript
} }
} }
var ctx = ToJSContext;
if (slot.Type != MarshalerType.TaskPreCreated) if (slot.Type != MarshalerType.TaskPreCreated)
{ {
// this path should only happen when the Task is passed as argument of JSImport // this path should only happen when the Task is passed as argument of JSImport
slot.JSHandle = AllocJSVHandle(); slot.JSHandle = ctx.AllocJSVHandle();
slot.Type = MarshalerType.Task; slot.Type = MarshalerType.Task;
} }
else else
@ -328,7 +334,7 @@ namespace System.Runtime.InteropServices.JavaScript
// promise and handle is pre-allocated in slot.JSHandle // promise and handle is pre-allocated in slot.JSHandle
} }
var taskHolder = new JSObject(slot.JSHandle); var taskHolder = ctx.CreateCSOwnedProxy(slot.JSHandle);
#if FEATURE_WASM_THREADS #if FEATURE_WASM_THREADS
task.ContinueWith(Complete, new HolderAndMarshaler<T>(taskHolder, marshaler), TaskScheduler.FromCurrentSynchronizationContext()); task.ContinueWith(Complete, new HolderAndMarshaler<T>(taskHolder, marshaler), TaskScheduler.FromCurrentSynchronizationContext());
@ -357,14 +363,26 @@ namespace System.Runtime.InteropServices.JavaScript
{ {
holder.AssertNotDisposed(); holder.AssertNotDisposed();
#if FEATURE_WASM_THREADS
JSObject.AssertThreadAffinity(holder);
#endif
Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4]; Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4];
ref JSMarshalerArgument exc = ref args[0]; ref JSMarshalerArgument exc = ref args[0];
ref JSMarshalerArgument res = ref args[1]; ref JSMarshalerArgument res = ref args[1];
ref JSMarshalerArgument arg_handle = ref args[2]; ref JSMarshalerArgument arg_handle = ref args[2];
ref JSMarshalerArgument arg_value = ref args[3]; ref JSMarshalerArgument arg_value = ref args[3];
#if FEATURE_WASM_THREADS
exc.InitializeWithContext(holder.ProxyContext);
res.InitializeWithContext(holder.ProxyContext);
arg_value.InitializeWithContext(holder.ProxyContext);
arg_handle.InitializeWithContext(holder.ProxyContext);
JSProxyContext.JSImportNoCapture();
#else
exc.Initialize(); exc.Initialize();
res.Initialize(); res.Initialize();
#endif
// should update existing promise // should update existing promise
arg_handle.slot.Type = MarshalerType.TaskRejected; arg_handle.slot.Type = MarshalerType.TaskRejected;
@ -373,14 +391,24 @@ namespace System.Runtime.InteropServices.JavaScript
// should fail it with exception // should fail it with exception
arg_value.ToJS(ex); arg_value.ToJS(ex);
JavaScriptImports.ResolveOrRejectPromise(args); // we can free the JSHandle here and the holder.resolve_or_reject will do the rest
holder.DisposeImpl(skipJsCleanup: true);
holder.DisposeLocal(); #if !FEATURE_WASM_THREADS
// order of operations with DisposeImpl matters
JSFunctionBinding.ResolveOrRejectPromise(args);
#else
// order of operations with DisposeImpl matters
JSFunctionBinding.ResolveOrRejectPromise(args);
#endif
} }
private static void ResolveVoidPromise(JSObject holder) private static void ResolveVoidPromise(JSObject holder)
{ {
holder.AssertNotDisposed(); holder.AssertNotDisposed();
#if FEATURE_WASM_THREADS
JSObject.AssertThreadAffinity(holder);
#endif
Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4]; Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4];
ref JSMarshalerArgument exc = ref args[0]; ref JSMarshalerArgument exc = ref args[0];
@ -388,8 +416,16 @@ namespace System.Runtime.InteropServices.JavaScript
ref JSMarshalerArgument arg_handle = ref args[2]; ref JSMarshalerArgument arg_handle = ref args[2];
ref JSMarshalerArgument arg_value = ref args[3]; ref JSMarshalerArgument arg_value = ref args[3];
#if FEATURE_WASM_THREADS
exc.InitializeWithContext(holder.ProxyContext);
res.InitializeWithContext(holder.ProxyContext);
arg_value.InitializeWithContext(holder.ProxyContext);
arg_handle.InitializeWithContext(holder.ProxyContext);
JSProxyContext.JSImportNoCapture();
#else
exc.Initialize(); exc.Initialize();
res.Initialize(); res.Initialize();
#endif
// should update existing promise // should update existing promise
arg_handle.slot.Type = MarshalerType.TaskResolved; arg_handle.slot.Type = MarshalerType.TaskResolved;
@ -397,14 +433,24 @@ namespace System.Runtime.InteropServices.JavaScript
arg_value.slot.Type = MarshalerType.Void; arg_value.slot.Type = MarshalerType.Void;
JavaScriptImports.ResolveOrRejectPromise(args); // we can free the JSHandle here and the holder.resolve_or_reject will do the rest
holder.DisposeImpl(skipJsCleanup: true);
holder.DisposeLocal(); #if !FEATURE_WASM_THREADS
// order of operations with DisposeImpl matters
JSFunctionBinding.ResolveOrRejectPromise(args);
#else
// order of operations with DisposeImpl matters
JSFunctionBinding.ResolveOrRejectPromise(args);
#endif
} }
private static void ResolvePromise<T>(JSObject holder, T value, ArgumentToJSCallback<T> marshaler) private static void ResolvePromise<T>(JSObject holder, T value, ArgumentToJSCallback<T> marshaler)
{ {
holder.AssertNotDisposed(); holder.AssertNotDisposed();
#if FEATURE_WASM_THREADS
JSObject.AssertThreadAffinity(holder);
#endif
Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4]; Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4];
ref JSMarshalerArgument exc = ref args[0]; ref JSMarshalerArgument exc = ref args[0];
@ -412,8 +458,16 @@ namespace System.Runtime.InteropServices.JavaScript
ref JSMarshalerArgument arg_handle = ref args[2]; ref JSMarshalerArgument arg_handle = ref args[2];
ref JSMarshalerArgument arg_value = ref args[3]; ref JSMarshalerArgument arg_value = ref args[3];
#if FEATURE_WASM_THREADS
exc.InitializeWithContext(holder.ProxyContext);
res.InitializeWithContext(holder.ProxyContext);
arg_value.InitializeWithContext(holder.ProxyContext);
arg_handle.InitializeWithContext(holder.ProxyContext);
JSProxyContext.JSImportNoCapture();
#else
exc.Initialize(); exc.Initialize();
res.Initialize(); res.Initialize();
#endif
// should update existing promise // should update existing promise
arg_handle.slot.Type = MarshalerType.TaskResolved; arg_handle.slot.Type = MarshalerType.TaskResolved;
@ -422,9 +476,16 @@ namespace System.Runtime.InteropServices.JavaScript
// and resolve it with value // and resolve it with value
marshaler(ref arg_value, value); marshaler(ref arg_value, value);
JavaScriptImports.ResolveOrRejectPromise(args); // we can free the JSHandle here and the holder.resolve_or_reject will do the rest
holder.DisposeImpl(skipJsCleanup: true);
holder.DisposeLocal(); #if !FEATURE_WASM_THREADS
// order of operations with DisposeImpl matters
JSFunctionBinding.ResolveOrRejectPromise(args);
#else
// order of operations with DisposeImpl matters
JSFunctionBinding.ResolveOrRejectPromise(args);
#endif
} }
} }
} }

View File

@ -23,5 +23,6 @@
<None Include="$(CompilerGeneratedFilesOutputPath)\..\browser-wasm\generated\Microsoft.Interop.JavaScript.JSImportGenerator\Microsoft.Interop.JavaScript.JsExportGenerator\JSExports.g.cs" /> <None Include="$(CompilerGeneratedFilesOutputPath)\..\browser-wasm\generated\Microsoft.Interop.JavaScript.JSImportGenerator\Microsoft.Interop.JavaScript.JsExportGenerator\JSExports.g.cs" />
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\JavaScriptTestHelper.mjs" /> <WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\JavaScriptTestHelper.mjs" />
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\SecondRuntimeTest.js" /> <WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\SecondRuntimeTest.js" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -7,6 +7,7 @@ using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading; using System.Threading;
using Xunit; using Xunit;
using System.Diagnostics.CodeAnalysis;
#pragma warning disable xUnit1026 // Theory methods should use all of their parameters #pragma warning disable xUnit1026 // Theory methods should use all of their parameters
namespace System.Runtime.InteropServices.JavaScript.Tests namespace System.Runtime.InteropServices.JavaScript.Tests
@ -16,7 +17,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
[Fact] [Fact]
public unsafe void StructSize() public unsafe void StructSize()
{ {
Assert.Equal(16, sizeof(JSMarshalerArgument)); Assert.Equal(32, sizeof(JSMarshalerArgument));
} }
[Fact] [Fact]
@ -378,7 +379,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
[Theory] [Theory]
[MemberData(nameof(MarshalObjectArrayCasesThrow))] [MemberData(nameof(MarshalObjectArrayCasesThrow))]
public unsafe void JsImportObjectArrayThrows(object[]? expected) public void JsImportObjectArrayThrows(object[]? expected)
{ {
Assert.Throws<NotSupportedException>(() => JavaScriptTestHelper.echo1_ObjectArray(expected)); Assert.Throws<NotSupportedException>(() => JavaScriptTestHelper.echo1_ObjectArray(expected));
} }
@ -1959,15 +1960,15 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
#endregion #endregion
private void JsExportTest<T>(T value private void JsExportTest<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>(T value
, Func<T, string, T> invoke, string echoName, string jsType, string? jsClass = null) , Func<T, string, T> invoke, string echoName, string jsType, string? jsClass = null)
{ {
T res; T res;
res = invoke(value, echoName); res = invoke(value, echoName);
Assert.Equal(value, res); Assert.Equal<T>(value, res);
} }
private void JsImportTest<T>(T value private void JsImportTest<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>(T value
, Action<T> store1 , Action<T> store1
, Func<T> retrieve1 , Func<T> retrieve1
, Func<T, T> echo1 , Func<T, T> echo1

View File

@ -999,19 +999,20 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
{ {
if (_module == null) if (_module == null)
{ {
// Log("JavaScriptTestHelper.mjs importing"); _module = await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs"); ;
_module = await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs"); await Setup(); ;
await Setup();
// Log("JavaScriptTestHelper.mjs imported");
} }
var p = echopromise_String("aaa");
await p;
// this gives browser chance to serve UI thread event loop before every test // this gives browser chance to serve UI thread event loop before every test
await Task.Yield(); await Task.Yield();
} }
public static Task DisposeAsync() public static Task DisposeAsync()
{ {
_module.Dispose(); _module?.Dispose();
_module = null; _module = null;
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@ -367,7 +367,15 @@ export function backback(arg1, arg2, arg3) {
// console.log('backback A') // console.log('backback A')
return (brg1, brg2) => { return (brg1, brg2) => {
// console.log('backback B') // console.log('backback B')
return arg1(brg1 + arg2, brg2 + arg3); try {
var res = arg1(brg1 + arg2, brg2 + arg3);
// console.log('backback C')
return res
}
catch (e) {
// console.log('backback E ' + e)
throw e;
}
} }
} }

View File

@ -1,3 +1,6 @@
!Makefile !Makefile
.stamp-wasm-install-and-select* .stamp-wasm-install-and-select*
emsdk emsdk
runtime/dotnet.d.ts.sha256
runtime/dotnet-legacy.d.ts.sha256

View File

@ -42,7 +42,7 @@ extern void* mono_wasm_invoke_js_blazor (MonoString **exceptionMessage, void *ca
#endif /* DISABLE_LEGACY_JS_INTEROP */ #endif /* DISABLE_LEGACY_JS_INTEROP */
#ifndef DISABLE_THREADS #ifndef DISABLE_THREADS
extern void mono_wasm_install_js_worker_interop (); extern void mono_wasm_install_js_worker_interop (int context_gc_handle);
extern void mono_wasm_uninstall_js_worker_interop (); extern void mono_wasm_uninstall_js_worker_interop ();
#endif /* DISABLE_THREADS */ #endif /* DISABLE_THREADS */

View File

@ -88,6 +88,7 @@ const fn_signatures: SigLine[] = [
[true, "mono_wasm_profiler_init_browser", "void", ["number"]], [true, "mono_wasm_profiler_init_browser", "void", ["number"]],
[false, "mono_wasm_exec_regression", "number", ["number", "string"]], [false, "mono_wasm_exec_regression", "number", ["number", "string"]],
[false, "mono_wasm_invoke_method_bound", "number", ["number", "number", "number"]], [false, "mono_wasm_invoke_method_bound", "number", ["number", "number", "number"]],
[false, "mono_wasm_invoke_method_raw", "number", ["number", "number"]],
[true, "mono_wasm_write_managed_pointer_unsafe", "void", ["number", "number"]], [true, "mono_wasm_write_managed_pointer_unsafe", "void", ["number", "number"]],
[true, "mono_wasm_copy_managed_pointer", "void", ["number", "number"]], [true, "mono_wasm_copy_managed_pointer", "void", ["number", "number"]],
[true, "mono_wasm_i52_to_f64", "number", ["number", "number"]], [true, "mono_wasm_i52_to_f64", "number", ["number", "number"]],
@ -229,6 +230,7 @@ export interface t_Cwraps {
mono_wasm_set_main_args(argc: number, argv: VoidPtr): void; mono_wasm_set_main_args(argc: number, argv: VoidPtr): void;
mono_wasm_exec_regression(verbose_level: number, image: string): number; mono_wasm_exec_regression(verbose_level: number, image: string): number;
mono_wasm_invoke_method_bound(method: MonoMethod, args: JSMarshalerArguments, fail: MonoStringRef): number; mono_wasm_invoke_method_bound(method: MonoMethod, args: JSMarshalerArguments, fail: MonoStringRef): number;
mono_wasm_invoke_method_raw(method: MonoMethod, fail: MonoStringRef): number;
mono_wasm_write_managed_pointer_unsafe(destination: VoidPtr | MonoObjectRef, pointer: ManagedPointer): void; mono_wasm_write_managed_pointer_unsafe(destination: VoidPtr | MonoObjectRef, pointer: ManagedPointer): void;
mono_wasm_copy_managed_pointer(destination: VoidPtr | MonoObjectRef, source: VoidPtr | MonoObjectRef): void; mono_wasm_copy_managed_pointer(destination: VoidPtr | MonoObjectRef, source: VoidPtr | MonoObjectRef): void;
mono_wasm_i52_to_f64(source: VoidPtr, error: Int32Ptr): number; mono_wasm_i52_to_f64(source: VoidPtr, error: Int32Ptr): number;

View File

@ -322,6 +322,27 @@ mono_wasm_invoke_method_bound (MonoMethod *method, void* args /*JSMarshalerArgum
return is_err; return is_err;
} }
EMSCRIPTEN_KEEPALIVE int
mono_wasm_invoke_method_raw (MonoMethod *method, MonoString **out_exc)
{
PVOLATILE(MonoObject) temp_exc = NULL;
int is_err = 0;
MONO_ENTER_GC_UNSAFE;
mono_runtime_invoke (method, NULL, NULL, (MonoObject **)&temp_exc);
if (temp_exc && out_exc) {
PVOLATILE(MonoObject) exc2 = NULL;
store_volatile((MonoObject**)out_exc, (MonoObject*)mono_object_to_string ((MonoObject*)temp_exc, (MonoObject **)&exc2));
if (exc2)
store_volatile((MonoObject**)out_exc, (MonoObject*)mono_string_new (root_domain, "Exception Double Fault"));
is_err = 1;
}
MONO_EXIT_GC_UNSAFE;
return is_err;
}
EMSCRIPTEN_KEEPALIVE MonoMethod* EMSCRIPTEN_KEEPALIVE MonoMethod*
mono_wasm_assembly_get_entry_point (MonoAssembly *assembly, int auto_insert_breakpoint) mono_wasm_assembly_get_entry_point (MonoAssembly *assembly, int auto_insert_breakpoint)
{ {

View File

@ -98,6 +98,7 @@ export function register_with_jsv_handle(js_obj: any, jsv_handle: JSHandle) {
} }
} }
// note: in MT, this is called from locked JSProxyContext. Don't call anything that would need locking.
export function mono_wasm_release_cs_owned_object(js_handle: JSHandle): void { export function mono_wasm_release_cs_owned_object(js_handle: JSHandle): void {
let obj: any; let obj: any;
if (is_js_handle(js_handle)) { if (is_js_handle(js_handle)) {
@ -108,6 +109,7 @@ export function mono_wasm_release_cs_owned_object(js_handle: JSHandle): void {
else if (is_jsv_handle(js_handle)) { else if (is_jsv_handle(js_handle)) {
obj = _cs_owned_objects_by_jsv_handle[0 - <any>js_handle]; obj = _cs_owned_objects_by_jsv_handle[0 - <any>js_handle];
_cs_owned_objects_by_jsv_handle[0 - <any>js_handle] = undefined; _cs_owned_objects_by_jsv_handle[0 - <any>js_handle] = undefined;
// see free list in JSProxyContext.FreeJSVHandle
} }
mono_assert(obj !== undefined && obj !== null, "ObjectDisposedException"); mono_assert(obj !== undefined && obj !== null, "ObjectDisposedException");
if (typeof obj[cs_owned_js_handle_symbol] !== "undefined") { if (typeof obj[cs_owned_js_handle_symbol] !== "undefined") {

View File

@ -4,6 +4,7 @@
import { wrap_as_cancelable_promise } from "./cancelable-promise"; import { wrap_as_cancelable_promise } from "./cancelable-promise";
import { ENVIRONMENT_IS_NODE, Module, loaderHelpers, mono_assert } from "./globals"; import { ENVIRONMENT_IS_NODE, Module, loaderHelpers, mono_assert } from "./globals";
import { MemoryViewType, Span } from "./marshal"; import { MemoryViewType, Span } from "./marshal";
import { assert_synchronization_context } from "./pthreads/shared";
import type { VoidPtr } from "./types/emscripten"; import type { VoidPtr } from "./types/emscripten";
import { ControllablePromise } from "./types/internal"; import { ControllablePromise } from "./types/internal";
@ -112,6 +113,7 @@ export function http_wasm_fetch_bytes(url: string, header_names: string[], heade
export function http_wasm_fetch(url: string, header_names: string[], header_values: string[], option_names: string[], option_values: any[], abort_controller: AbortController, body: Uint8Array | ReadableStream | null): ControllablePromise<ResponseExtension> { export function http_wasm_fetch(url: string, header_names: string[], header_values: string[], option_names: string[], option_values: any[], abort_controller: AbortController, body: Uint8Array | ReadableStream | null): ControllablePromise<ResponseExtension> {
verifyEnvironment(); verifyEnvironment();
assert_synchronization_context();
mono_assert(url && typeof url === "string", "expected url string"); mono_assert(url && typeof url === "string", "expected url string");
mono_assert(header_names && header_values && Array.isArray(header_names) && Array.isArray(header_values) && header_names.length === header_values.length, "expected headerNames and headerValues arrays"); mono_assert(header_names && header_values && Array.isArray(header_names) && Array.isArray(header_values) && header_names.length === header_values.length, "expected headerNames and headerValues arrays");
mono_assert(option_names && option_values && Array.isArray(option_names) && Array.isArray(option_values) && option_names.length === option_values.length, "expected headerNames and headerValues arrays"); mono_assert(option_names && option_values && Array.isArray(option_names) && Array.isArray(option_values) && option_names.length === option_values.length, "expected headerNames and headerValues arrays");

View File

@ -9,7 +9,7 @@ import { bind_arg_marshal_to_cs } from "./marshal-to-cs";
import { marshal_exception_to_js, bind_arg_marshal_to_js, end_marshal_task_to_js } from "./marshal-to-js"; import { marshal_exception_to_js, bind_arg_marshal_to_js, end_marshal_task_to_js } from "./marshal-to-js";
import { import {
get_arg, get_sig, get_signature_argument_count, is_args_exception, get_arg, get_sig, get_signature_argument_count, is_args_exception,
bound_cs_function_symbol, get_signature_version, alloc_stack_frame, get_signature_type, bound_cs_function_symbol, get_signature_version, alloc_stack_frame, get_signature_type, set_args_context,
} from "./marshal"; } from "./marshal";
import { mono_wasm_new_external_root, mono_wasm_new_root } from "./roots"; import { mono_wasm_new_external_root, mono_wasm_new_root } from "./roots";
import { monoStringToString } from "./strings"; import { monoStringToString } from "./strings";
@ -356,8 +356,9 @@ export function invoke_method_and_handle_exception(method: MonoMethod, args: JSM
assert_bindings(); assert_bindings();
const fail_root = mono_wasm_new_root<MonoString>(); const fail_root = mono_wasm_new_root<MonoString>();
try { try {
set_args_context(args);
const fail = cwraps.mono_wasm_invoke_method_bound(method, args, fail_root.address); const fail = cwraps.mono_wasm_invoke_method_bound(method, args, fail_root.address);
if (fail) throw new Error("ERR24: Unexpected error: " + monoStringToString(fail_root)); if (fail) runtimeHelpers.abort("ERR24: Unexpected error: " + monoStringToString(fail_root));
if (is_args_exception(args)) { if (is_args_exception(args)) {
const exc = get_arg(args, 0); const exc = get_arg(args, 0);
throw marshal_exception_to_js(exc); throw marshal_exception_to_js(exc);
@ -368,6 +369,18 @@ export function invoke_method_and_handle_exception(method: MonoMethod, args: JSM
} }
} }
export function invoke_method_raw(method: MonoMethod): void {
assert_bindings();
const fail_root = mono_wasm_new_root<MonoString>();
try {
const fail = cwraps.mono_wasm_invoke_method_raw(method, fail_root.address);
if (fail) runtimeHelpers.abort("ERR24: Unexpected error: " + monoStringToString(fail_root));
}
finally {
fail_root.release();
}
}
export const exportsByAssembly: Map<string, any> = new Map(); export const exportsByAssembly: Map<string, any> = new Map();
function _walk_exports_to_set_function(assembly: string, namespace: string, classname: string, methodname: string, signature_hash: number, fn: Function): void { function _walk_exports_to_set_function(assembly: string, namespace: string, classname: string, methodname: string, signature_hash: number, fn: Function): void {
const parts = `${namespace}.${classname}`.replace(/\//g, ".").split("."); const parts = `${namespace}.${classname}`.replace(/\//g, ".").split(".");

View File

@ -7,7 +7,7 @@ import { GCHandle, MarshalerToCs, MarshalerToJs, MarshalerType, MonoMethod } fro
import cwraps from "./cwraps"; import cwraps from "./cwraps";
import { runtimeHelpers, Module, loaderHelpers, mono_assert } from "./globals"; 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, set_arg_type, set_gc_handle } from "./marshal";
import { invoke_method_and_handle_exception } from "./invoke-cs"; import { invoke_method_and_handle_exception, invoke_method_raw } 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_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, end_marshal_task_to_js, marshal_string_to_js, begin_marshal_task_to_js } from "./marshal-to-js"; import { marshal_int32_to_js, end_marshal_task_to_js, marshal_string_to_js, begin_marshal_task_to_js } from "./marshal-to-js";
import { do_not_force_dispose } from "./gc-handles"; import { do_not_force_dispose } from "./gc-handles";
@ -24,8 +24,8 @@ export function init_managed_exports(): void {
if (!runtimeHelpers.runtime_interop_exports_class) if (!runtimeHelpers.runtime_interop_exports_class)
throw "Can't find " + runtimeHelpers.runtime_interop_namespace + "." + runtimeHelpers.runtime_interop_exports_classname + " class"; throw "Can't find " + runtimeHelpers.runtime_interop_namespace + "." + runtimeHelpers.runtime_interop_exports_classname + " class";
const install_sync_context = MonoWasmThreads ? get_method("InstallSynchronizationContext") : undefined; const install_main_synchronization_context = MonoWasmThreads ? get_method("InstallMainSynchronizationContext") : undefined;
mono_assert(!MonoWasmThreads || install_sync_context, "Can't find InstallSynchronizationContext method"); mono_assert(!MonoWasmThreads || install_main_synchronization_context, "Can't find InstallMainSynchronizationContext method");
const call_entry_point = get_method("CallEntrypoint"); const call_entry_point = get_method("CallEntrypoint");
mono_assert(call_entry_point, "Can't find CallEntrypoint method"); mono_assert(call_entry_point, "Can't find CallEntrypoint method");
const release_js_owned_object_by_gc_handle_method = get_method("ReleaseJSOwnedObjectByGCHandle"); const release_js_owned_object_by_gc_handle_method = get_method("ReleaseJSOwnedObjectByGCHandle");
@ -188,17 +188,8 @@ export function init_managed_exports(): void {
Module.stackRestore(sp); Module.stackRestore(sp);
} }
}; };
if (MonoWasmThreads && install_main_synchronization_context) {
if (MonoWasmThreads && install_sync_context) { runtimeHelpers.javaScriptExports.install_main_synchronization_context = () => invoke_method_raw(install_main_synchronization_context);
runtimeHelpers.javaScriptExports.install_synchronization_context = () => {
const sp = Module.stackSave();
try {
const args = alloc_stack_frame(2);
invoke_method_and_handle_exception(install_sync_context, args);
} finally {
Module.stackRestore(sp);
}
};
} }
} }

View File

@ -337,10 +337,14 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise<any>, _?:
} }
try { try {
mono_assert(!holder.isDisposed, "This promise can't be propagated to managed code, because the Task was already freed."); mono_assert(!holder.isDisposed, "This promise can't be propagated to managed code, because the Task was already freed.");
if (MonoWasmThreads) if (MonoWasmThreads) {
settleUnsettledPromise(); settleUnsettledPromise();
}
// we can unregister the GC handle on JS side
teardown_managed_proxy(holder, gc_handle, true);
// order of operations with teardown_managed_proxy matters
// so that managed user code running in the continuation could allocate the same GCHandle number and the local registry would be already ok with that
runtimeHelpers.javaScriptExports.complete_task(gc_handle, null, data, res_converter || _marshal_cs_object_to_cs); 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)
} }
catch (ex) { catch (ex) {
runtimeHelpers.abort(ex); runtimeHelpers.abort(ex);
@ -352,10 +356,13 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise<any>, _?:
} }
try { try {
mono_assert(!holder.isDisposed, "This promise can't be propagated to managed code, because the Task was already freed."); mono_assert(!holder.isDisposed, "This promise can't be propagated to managed code, because the Task was already freed.");
if (MonoWasmThreads) if (MonoWasmThreads) {
settleUnsettledPromise(); settleUnsettledPromise();
}
// we can unregister the GC handle on JS side
teardown_managed_proxy(holder, gc_handle, true);
// order of operations with teardown_managed_proxy matters
runtimeHelpers.javaScriptExports.complete_task(gc_handle, reason, null, undefined); 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
} }
catch (ex) { catch (ex) {
runtimeHelpers.abort(ex); runtimeHelpers.abort(ex);

View File

@ -265,7 +265,7 @@ export function end_marshal_task_to_js(args: JSMarshalerArguments, res_converter
} }
// otherwise drop the eagerPromise's handle // otherwise drop the eagerPromise's handle
const js_handle = get_arg_js_handle(res); const js_handle = mono_wasm_get_js_handle(eagerPromise);
mono_wasm_release_cs_owned_object(js_handle); mono_wasm_release_cs_owned_object(js_handle);
// get the synchronous result // get the synchronous result

View File

@ -18,7 +18,7 @@ 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 imported_js_function_symbol = Symbol.for("wasm imported_js_function");
export const proxy_debug_symbol = Symbol.for("wasm proxy_debug"); export const proxy_debug_symbol = Symbol.for("wasm proxy_debug");
export const JavaScriptMarshalerArgSize = 16; export const JavaScriptMarshalerArgSize = 32;
export const JSMarshalerTypeSize = 32; export const JSMarshalerTypeSize = 32;
export const JSMarshalerSignatureHeaderSize = 4 * 8; // without Exception and Result export const JSMarshalerSignatureHeaderSize = 4 * 8; // without Exception and Result
@ -26,6 +26,7 @@ export function alloc_stack_frame(size: number): JSMarshalerArguments {
const bytes = JavaScriptMarshalerArgSize * size; const bytes = JavaScriptMarshalerArgSize * size;
const args = Module.stackAlloc(bytes) as any; const args = Module.stackAlloc(bytes) as any;
_zero_region(args, bytes); _zero_region(args, bytes);
set_args_context(args);
return args; return args;
} }
@ -40,6 +41,15 @@ export function is_args_exception(args: JSMarshalerArguments): boolean {
return exceptionType !== MarshalerType.None; return exceptionType !== MarshalerType.None;
} }
export function set_args_context(args: JSMarshalerArguments): void {
if (!MonoWasmThreads) return;
mono_assert(args, "Null args");
const exc = get_arg(args, 0);
const res = get_arg(args, 1);
set_arg_proxy_context(exc);
set_arg_proxy_context(res);
}
export function get_sig(signature: JSFunctionSignature, index: number): JSMarshalerType { export function get_sig(signature: JSFunctionSignature, index: number): JSMarshalerType {
mono_assert(signature, "Null signatures"); mono_assert(signature, "Null signatures");
return <any>signature + (index * JSMarshalerTypeSize) + JSMarshalerSignatureHeaderSize; return <any>signature + (index * JSMarshalerTypeSize) + JSMarshalerSignatureHeaderSize;
@ -252,9 +262,16 @@ export function get_arg_js_handle(arg: JSMarshalerArgument): JSHandle {
return <any>getI32(<any>arg + 4); return <any>getI32(<any>arg + 4);
} }
export function set_arg_proxy_context(arg: JSMarshalerArgument): void {
if (!MonoWasmThreads) return;
mono_assert(arg, "Null arg");
setI32(<any>arg + 16, <any>runtimeHelpers.proxy_context_gc_handle);
}
export function set_js_handle(arg: JSMarshalerArgument, jsHandle: JSHandle): void { export function set_js_handle(arg: JSMarshalerArgument, jsHandle: JSHandle): void {
mono_assert(arg, "Null arg"); mono_assert(arg, "Null arg");
setI32(<any>arg + 4, <any>jsHandle); setI32(<any>arg + 4, <any>jsHandle);
set_arg_proxy_context(arg);
} }
export function get_arg_gc_handle(arg: JSMarshalerArgument): GCHandle { export function get_arg_gc_handle(arg: JSMarshalerArgument): GCHandle {
@ -265,6 +282,7 @@ export function get_arg_gc_handle(arg: JSMarshalerArgument): GCHandle {
export function set_gc_handle(arg: JSMarshalerArgument, gcHandle: GCHandle): void { export function set_gc_handle(arg: JSMarshalerArgument, gcHandle: GCHandle): void {
mono_assert(arg, "Null arg"); mono_assert(arg, "Null arg");
setI32(<any>arg + 4, <any>gcHandle); setI32(<any>arg + 4, <any>gcHandle);
set_arg_proxy_context(arg);
} }
export function get_string_root(arg: JSMarshalerArgument): WasmRoot<MonoString> { export function get_string_root(arg: JSMarshalerArgument): WasmRoot<MonoString> {
@ -331,7 +349,7 @@ export class ManagedError extends Error implements IDisposable {
if (this.managed_stack) { if (this.managed_stack) {
return this.managed_stack; return this.managed_stack;
} }
if (loaderHelpers.is_runtime_running() && (!MonoWasmThreads || runtimeHelpers.jsSynchronizationContextInstalled)) { if (loaderHelpers.is_runtime_running() && (!MonoWasmThreads || runtimeHelpers.proxy_context_gc_handle)) {
const gc_handle = (<any>this)[js_owned_gc_handle_symbol]; const gc_handle = (<any>this)[js_owned_gc_handle_symbol];
if (gc_handle !== GCHandleNull) { if (gc_handle !== GCHandleNull) {
const managed_stack = runtimeHelpers.javaScriptExports.get_managed_stack_trace(gc_handle); const managed_stack = runtimeHelpers.javaScriptExports.get_managed_stack_trace(gc_handle);

View File

@ -11,6 +11,7 @@ import { mono_log_debug } from "../../logging";
import { bindings_init } from "../../startup"; import { bindings_init } from "../../startup";
import { forceDisposeProxies } from "../../gc-handles"; import { forceDisposeProxies } from "../../gc-handles";
import { pthread_self } from "../worker"; import { pthread_self } from "../worker";
import { GCHandle, GCHandleNull } from "../../types/internal";
export interface PThreadInfo { export interface PThreadInfo {
readonly pthreadId: pthreadPtr; readonly pthreadId: pthreadPtr;
@ -166,11 +167,11 @@ export function isMonoWorkerMessagePreload(message: MonoWorkerMessage): message
return false; return false;
} }
export function mono_wasm_install_js_worker_interop(): void { export function mono_wasm_install_js_worker_interop(context_gc_handle: GCHandle): void {
if (!MonoWasmThreads) return; if (!MonoWasmThreads) return;
bindings_init(); bindings_init();
if (!runtimeHelpers.jsSynchronizationContextInstalled) { if (!runtimeHelpers.proxy_context_gc_handle) {
runtimeHelpers.jsSynchronizationContextInstalled = true; runtimeHelpers.proxy_context_gc_handle = context_gc_handle;
mono_log_debug("Installed JSSynchronizationContext"); mono_log_debug("Installed JSSynchronizationContext");
} }
Module.runtimeKeepalivePush(); Module.runtimeKeepalivePush();
@ -184,19 +185,19 @@ export function mono_wasm_install_js_worker_interop(): void {
export function mono_wasm_uninstall_js_worker_interop(): void { export function mono_wasm_uninstall_js_worker_interop(): void {
if (!MonoWasmThreads) return; if (!MonoWasmThreads) return;
mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "JS interop is not installed on this worker."); mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "JS interop is not installed on this worker.");
mono_assert(runtimeHelpers.jsSynchronizationContextInstalled, "JSSynchronizationContext is not installed on this worker."); mono_assert(runtimeHelpers.proxy_context_gc_handle, "JSSynchronizationContext is not installed on this worker.");
forceDisposeProxies(true, runtimeHelpers.diagnosticTracing); forceDisposeProxies(true, runtimeHelpers.diagnosticTracing);
Module.runtimeKeepalivePop(); Module.runtimeKeepalivePop();
runtimeHelpers.jsSynchronizationContextInstalled = false; runtimeHelpers.proxy_context_gc_handle = GCHandleNull;
runtimeHelpers.mono_wasm_bindings_is_ready = false; runtimeHelpers.mono_wasm_bindings_is_ready = false;
set_thread_info(pthread_self ? pthread_self.pthreadId : 0, true, false, false); set_thread_info(pthread_self ? pthread_self.pthreadId : 0, true, false, false);
} }
export function assert_synchronization_context(): void { export function assert_synchronization_context(): void {
if (MonoWasmThreads) { if (MonoWasmThreads) {
mono_assert(runtimeHelpers.jsSynchronizationContextInstalled, "Please use dedicated worker for working with JavaScript interop. See https://github.com/dotnet/runtime/blob/main/src/mono/wasm/threads.md#JS-interop-on-dedicated-threads"); mono_assert(runtimeHelpers.proxy_context_gc_handle, "Please use dedicated worker for working with JavaScript interop. See https://github.com/dotnet/runtime/blob/main/src/mono/wasm/threads.md#JS-interop-on-dedicated-threads");
} }
} }

View File

@ -289,8 +289,7 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) {
} }
if (MonoWasmThreads) { if (MonoWasmThreads) {
runtimeHelpers.javaScriptExports.install_synchronization_context(); runtimeHelpers.javaScriptExports.install_main_synchronization_context();
runtimeHelpers.jsSynchronizationContextInstalled = true;
} }
if (!runtimeHelpers.mono_wasm_runtime_is_ready) mono_wasm_runtime_ready(); if (!runtimeHelpers.mono_wasm_runtime_is_ready) mono_wasm_runtime_ready();

View File

@ -194,7 +194,7 @@ export type RuntimeHelpers = {
getMemory(): WebAssembly.Memory, getMemory(): WebAssembly.Memory,
getWasmIndirectFunctionTable(): WebAssembly.Table, getWasmIndirectFunctionTable(): WebAssembly.Table,
runtimeReady: boolean, runtimeReady: boolean,
jsSynchronizationContextInstalled: boolean, proxy_context_gc_handle: GCHandle,
cspPolicy: boolean, cspPolicy: boolean,
allAssetsInMemory: PromiseAndController<void>, allAssetsInMemory: PromiseAndController<void>,
@ -348,8 +348,8 @@ export interface JavaScriptExports {
// the marshaled signature is: Task<int>? CallEntrypoint(MonoMethod* entrypointPtr, string[] args) // the marshaled signature is: Task<int>? CallEntrypoint(MonoMethod* entrypointPtr, string[] args)
call_entry_point(entry_point: MonoMethod, args?: string[]): Promise<number>; call_entry_point(entry_point: MonoMethod, args?: string[]): Promise<number>;
// the marshaled signature is: void InstallSynchronizationContext() // the marshaled signature is: void InstallMainSynchronizationContext()
install_synchronization_context(): void; install_main_synchronization_context(): void;
// the marshaled signature is: string GetManagedStackTrace(GCHandle exception) // the marshaled signature is: string GetManagedStackTrace(GCHandle exception)
get_managed_stack_trace(exception_gc_handle: GCHandle): string | null get_managed_stack_trace(exception_gc_handle: GCHandle): string | null

View File

@ -13,6 +13,7 @@ import { mono_log_warn } from "./logging";
import { viewOrCopy, utf8ToStringRelaxed, stringToUTF8 } from "./strings"; import { viewOrCopy, utf8ToStringRelaxed, stringToUTF8 } from "./strings";
import { IDisposable } from "./marshal"; import { IDisposable } from "./marshal";
import { wrap_as_cancelable } from "./cancelable-promise"; import { wrap_as_cancelable } from "./cancelable-promise";
import { assert_synchronization_context } from "./pthreads/shared";
const wasm_ws_pending_send_buffer = Symbol.for("wasm ws_pending_send_buffer"); const wasm_ws_pending_send_buffer = Symbol.for("wasm ws_pending_send_buffer");
const wasm_ws_pending_send_buffer_offset = Symbol.for("wasm ws_pending_send_buffer_offset"); const wasm_ws_pending_send_buffer_offset = Symbol.for("wasm ws_pending_send_buffer_offset");
@ -44,6 +45,7 @@ function verifyEnvironment() {
export function ws_wasm_create(uri: string, sub_protocols: string[] | null, receive_status_ptr: VoidPtr, onClosed: (code: number, reason: string) => void): WebSocketExtension { export function ws_wasm_create(uri: string, sub_protocols: string[] | null, receive_status_ptr: VoidPtr, onClosed: (code: number, reason: string) => void): WebSocketExtension {
verifyEnvironment(); verifyEnvironment();
assert_synchronization_context();
mono_assert(uri && typeof uri === "string", () => `ERR12: Invalid uri ${typeof uri}`); mono_assert(uri && typeof uri === "string", () => `ERR12: Invalid uri ${typeof uri}`);
mono_assert(typeof onClosed === "function", () => `ERR12: Invalid onClosed ${typeof onClosed}`); mono_assert(typeof onClosed === "function", () => `ERR12: Invalid onClosed ${typeof onClosed}`);

View File

@ -28,3 +28,5 @@ yarn-error.log*
.env.development.local .env.development.local
.env.test.local .env.test.local
.env.production.local .env.production.local
public