Skip to content

Commit 8bd23f0

Browse files
authored
[browser] introduce JSProxyContext (#95959)
1 parent 5cf6892 commit 8bd23f0

File tree

54 files changed

+1369
-703
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1369
-703
lines changed

.gitignore

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,10 +187,6 @@ node_modules/
187187
*.metaproj
188188
*.metaproj.tmp
189189
bin.localpkg/
190-
src/mono/wasm/runtime/dotnet.d.ts.sha256
191-
src/mono/wasm/runtime/dotnet-legacy.d.ts.sha256
192-
193-
src/mono/sample/wasm/browser-nextjs/public/
194190

195191
# RIA/Silverlight projects
196192
Generated_Code/

src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ internal static unsafe partial class Runtime
3434

3535
#if FEATURE_WASM_THREADS
3636
[MethodImpl(MethodImplOptions.InternalCall)]
37-
public static extern void InstallWebWorkerInterop();
37+
public static extern void InstallWebWorkerInterop(IntPtr proxyContextGCHandle);
3838
[MethodImpl(MethodImplOptions.InternalCall)]
3939
public static extern void UninstallWebWorkerInterop();
4040
#endif

src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
<TargetPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))</TargetPlatformIdentifier>
1717
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'windows'">$(DefineConstants);TargetsWindows</DefineConstants>
1818
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'browser'">$(DefineConstants);TARGETS_BROWSER</DefineConstants>
19+
<!-- Active issue https://github.com/dotnet/runtime/issues/96173 -->
20+
<_XUnitBackgroundExec>false</_XUnitBackgroundExec>
1921
</PropertyGroup>
2022

2123
<PropertyGroup Condition="'$(TargetOS)' == 'browser'">

src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
<!-- This WASM test is problematic and slow right now. This sets the xharness timeout but there is also override in sendtohelix-browser.targets -->
1919
<WasmXHarnessTestsTimeout>01:15:00</WasmXHarnessTestsTimeout>
20+
21+
<!-- Active issue https://github.com/dotnet/runtime/issues/96173 -->
22+
<_XUnitBackgroundExec>false</_XUnitBackgroundExec>
2023
</PropertyGroup>
2124

2225
<ItemGroup>

src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/Marshaling/PrimitiveJSGenerator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public PrimitiveJSGenerator(MarshalerType marshalerType)
2121
{
2222
}
2323

24+
// TODO order parameters in such way that affinity capturing parameters are emitted first
2425
public override IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeContext context)
2526
{
2627
string argName = context.GetAdditionalIdentifier(info, "js_arg");

src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
<Compile Include="System\Runtime\InteropServices\JavaScript\JSImportAttribute.cs" />
4747
<Compile Include="System\Runtime\InteropServices\JavaScript\CancelablePromise.cs" />
4848
<Compile Include="System\Runtime\InteropServices\JavaScript\SynchronizationContextExtensions.cs" />
49+
<Compile Include="System\Runtime\InteropServices\JavaScript\JSProxyContext.cs" />
4950

5051
<Compile Include="System\Runtime\InteropServices\JavaScript\MarshalerType.cs" />
5152
<Compile Include="System\Runtime\InteropServices\JavaScript\Marshaling\JSMarshalerArgument.BigInt64.cs" />

src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CancelablePromise.cs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44

5+
using System.Threading;
56
using System.Threading.Tasks;
67

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

24-
25-
#if FEATURE_WASM_THREADS
26-
holder.SynchronizationContext!.Send(static (JSHostImplementation.PromiseHolder holder) =>
25+
#if !FEATURE_WASM_THREADS
26+
if (holder.IsDisposed)
2727
{
28-
#endif
28+
return;
29+
}
2930
_CancelPromise(holder.GCHandle);
30-
#if FEATURE_WASM_THREADS
31+
#else
32+
holder.ProxyContext.SynchronizationContext.Post(static (object? h) =>
33+
{
34+
var holder = (JSHostImplementation.PromiseHolder)h!;
35+
lock (holder.ProxyContext)
36+
{
37+
if (holder.IsDisposed)
38+
{
39+
return;
40+
}
41+
}
42+
_CancelPromise(holder.GCHandle);
3143
}, holder);
3244
#endif
3345
}
@@ -42,15 +54,27 @@ public static void CancelPromise<T>(Task promise, Action<T> callback, T state)
4254
JSHostImplementation.PromiseHolder? holder = promise.AsyncState as JSHostImplementation.PromiseHolder;
4355
if (holder == null) throw new InvalidOperationException("Expected Task converted from JS Promise");
4456

45-
46-
#if FEATURE_WASM_THREADS
47-
holder.SynchronizationContext!.Send((JSHostImplementation.PromiseHolder holder) =>
57+
#if !FEATURE_WASM_THREADS
58+
if (holder.IsDisposed)
4859
{
49-
#endif
60+
return;
61+
}
62+
_CancelPromise(holder.GCHandle);
63+
callback.Invoke(state);
64+
#else
65+
holder.ProxyContext.SynchronizationContext.Post(_ =>
66+
{
67+
lock (holder.ProxyContext)
68+
{
69+
if (holder.IsDisposed)
70+
{
71+
return;
72+
}
73+
}
74+
5075
_CancelPromise(holder.GCHandle);
5176
callback.Invoke(state);
52-
#if FEATURE_WASM_THREADS
53-
}, holder);
77+
}, null);
5478
#endif
5579
}
5680
}

src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs

Lines changed: 49 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer)
2626
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // initialized and set by caller
2727
try
2828
{
29+
#if FEATURE_WASM_THREADS
30+
// when we arrive here, we are on the thread which owns the proxies
31+
arg_exc.AssertCurrentThreadContext();
32+
#endif
33+
2934
arg_1.ToManaged(out IntPtr entrypointPtr);
3035
if (entrypointPtr == IntPtr.Zero)
3136
{
@@ -103,6 +108,10 @@ public static void LoadLazyAssembly(JSMarshalerArgument* arguments_buffer)
103108
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3];
104109
try
105110
{
111+
#if FEATURE_WASM_THREADS
112+
// when we arrive here, we are on the thread which owns the proxies
113+
arg_exc.AssertCurrentThreadContext();
114+
#endif
106115
arg_1.ToManaged(out byte[]? dllBytes);
107116
arg_2.ToManaged(out byte[]? pdbBytes);
108117

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

126139
if (dllBytes != null)
@@ -140,32 +153,12 @@ public static void ReleaseJSOwnedObjectByGCHandle(JSMarshalerArgument* arguments
140153
{
141154
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
142155
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2]; // initialized and set by caller
156+
143157
try
144158
{
145-
var gcHandle = arg_1.slot.GCHandle;
146-
if (IsGCVHandle(gcHandle))
147-
{
148-
if (ThreadJsOwnedHolders.Remove(gcHandle, out PromiseHolder? holder))
149-
{
150-
holder.GCHandle = IntPtr.Zero;
151-
holder.Callback!(null);
152-
}
153-
}
154-
else
155-
{
156-
GCHandle handle = (GCHandle)gcHandle;
157-
var target = handle.Target!;
158-
if (target is PromiseHolder holder)
159-
{
160-
holder.GCHandle = IntPtr.Zero;
161-
holder.Callback!(null);
162-
}
163-
else
164-
{
165-
ThreadJsOwnedObjects.Remove(target);
166-
}
167-
handle.Free();
168-
}
159+
// when we arrive here, we are on the thread which owns the proxies
160+
var ctx = arg_exc.AssertCurrentThreadContext();
161+
ctx.ReleaseJSOwnedObjectByGCHandle(arg_1.slot.GCHandle);
169162
}
170163
catch (Exception ex)
171164
{
@@ -185,6 +178,11 @@ public static void CallDelegate(JSMarshalerArgument* arguments_buffer)
185178
// arg_4 set by JS caller when there are arguments
186179
try
187180
{
181+
#if FEATURE_WASM_THREADS
182+
// when we arrive here, we are on the thread which owns the proxies
183+
arg_exc.AssertCurrentThreadContext();
184+
#endif
185+
188186
GCHandle callback_gc_handle = (GCHandle)arg_1.slot.GCHandle;
189187
if (callback_gc_handle.Target is ToManagedCallback callback)
190188
{
@@ -210,34 +208,34 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer)
210208
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller
211209
// arg_2 set by caller when this is SetException call
212210
// arg_3 set by caller when this is SetResult call
211+
213212
try
214213
{
215-
var holderGCHandle = arg_1.slot.GCHandle;
216-
if (IsGCVHandle(holderGCHandle))
214+
// when we arrive here, we are on the thread which owns the proxies
215+
var ctx = arg_exc.AssertCurrentThreadContext();
216+
var holder = ctx.GetPromiseHolder(arg_1.slot.GCHandle);
217+
218+
#if FEATURE_WASM_THREADS
219+
lock (ctx)
217220
{
218-
if (ThreadJsOwnedHolders.Remove(holderGCHandle, out PromiseHolder? holder))
221+
if (holder.Callback == null)
219222
{
220-
holder.GCHandle = IntPtr.Zero;
221-
// arg_2, arg_3 are processed by the callback
222-
holder.Callback!(arguments_buffer);
223+
holder.CallbackReady = new ManualResetEventSlim(false);
223224
}
224225
}
225-
else
226+
if (holder.CallbackReady != null)
226227
{
227-
GCHandle handle = (GCHandle)holderGCHandle;
228-
var target = handle.Target!;
229-
if (target is PromiseHolder holder)
230-
{
231-
holder.GCHandle = IntPtr.Zero;
232-
// arg_2, arg_3 are processed by the callback
233-
holder.Callback!(arguments_buffer);
234-
}
235-
else
236-
{
237-
ThreadJsOwnedObjects.Remove(target);
238-
}
239-
handle.Free();
228+
#pragma warning disable CA1416 // Validate platform compatibility
229+
holder.CallbackReady?.Wait();
230+
#pragma warning restore CA1416 // Validate platform compatibility
240231
}
232+
#endif
233+
var callback = holder.Callback!;
234+
ctx.ReleasePromiseHolder(arg_1.slot.GCHandle);
235+
236+
// arg_2, arg_3 are processed by the callback
237+
// JSProxyContext.PopOperation() is called by the callback
238+
callback!(arguments_buffer);
241239
}
242240
catch (Exception ex)
243241
{
@@ -254,6 +252,9 @@ public static void GetManagedStackTrace(JSMarshalerArgument* arguments_buffer)
254252
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller
255253
try
256254
{
255+
// when we arrive here, we are on the thread which owns the proxies
256+
arg_exc.AssertCurrentThreadContext();
257+
257258
GCHandle exception_gc_handle = (GCHandle)arg_1.slot.GCHandle;
258259
if (exception_gc_handle.Target is Exception exception)
259260
{
@@ -275,17 +276,10 @@ public static void GetManagedStackTrace(JSMarshalerArgument* arguments_buffer)
275276
// this is here temporarily, until JSWebWorker becomes public API
276277
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker", "System.Runtime.InteropServices.JavaScript")]
277278
// the marshaled signature is:
278-
// void InstallSynchronizationContext()
279-
public static void InstallSynchronizationContext (JSMarshalerArgument* arguments_buffer) {
280-
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
281-
try
282-
{
283-
InstallWebWorkerInterop(true);
284-
}
285-
catch (Exception ex)
286-
{
287-
arg_exc.ToJS(ex);
288-
}
279+
// void InstallMainSynchronizationContext()
280+
public static void InstallMainSynchronizationContext()
281+
{
282+
InstallWebWorkerInterop(true);
289283
}
290284

291285
#endif

src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.cs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,6 @@ namespace System.Runtime.InteropServices.JavaScript
77
{
88
internal static unsafe partial class JavaScriptImports
99
{
10-
public static void ResolveOrRejectPromise(Span<JSMarshalerArgument> arguments)
11-
{
12-
fixed (JSMarshalerArgument* ptr = arguments)
13-
{
14-
Interop.Runtime.ResolveOrRejectPromise(ptr);
15-
ref JSMarshalerArgument exceptionArg = ref arguments[0];
16-
if (exceptionArg.slot.Type != MarshalerType.None)
17-
{
18-
JSHostImplementation.ThrowException(ref exceptionArg);
19-
}
20-
}
21-
}
22-
2310
#if !DISABLE_LEGACY_JS_INTEROP
2411
#region legacy
2512

src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/LegacyExports.cs

Lines changed: 6 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,10 @@ internal static void PreventTrimming()
3232

3333
public static void GetCSOwnedObjectByJSHandleRef(nint jsHandle, int shouldAddInflight, out JSObject? result)
3434
{
35-
if (JSHostImplementation.ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference))
36-
{
37-
reference.TryGetTarget(out JSObject? jsObject);
38-
if (shouldAddInflight != 0)
39-
{
40-
jsObject?.AddInFlight();
41-
}
42-
result = jsObject;
43-
return;
44-
}
45-
result = null;
35+
#if FEATURE_WASM_THREADS
36+
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
37+
#endif
38+
result = JSProxyContext.MainThreadContext.GetCSOwnedObjectByJSHandle(jsHandle, shouldAddInflight);
4639
}
4740

4841
public static IntPtr GetCSOwnedObjectJSHandleRef(in JSObject jsObject, int shouldAddInflight)
@@ -71,32 +64,7 @@ public static void CreateCSOwnedProxyRef(nint jsHandle, LegacyHostImplementation
7164
#if FEATURE_WASM_THREADS
7265
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
7366
#endif
74-
75-
JSObject? res = null;
76-
77-
if (!JSHostImplementation.ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference) ||
78-
!reference.TryGetTarget(out res) ||
79-
res.IsDisposed)
80-
{
81-
#pragma warning disable CS0612 // Type or member is obsolete
82-
res = mappedType switch
83-
{
84-
LegacyHostImplementation.MappedType.JSObject => new JSObject(jsHandle),
85-
LegacyHostImplementation.MappedType.Array => new Array(jsHandle),
86-
LegacyHostImplementation.MappedType.ArrayBuffer => new ArrayBuffer(jsHandle),
87-
LegacyHostImplementation.MappedType.DataView => new DataView(jsHandle),
88-
LegacyHostImplementation.MappedType.Function => new Function(jsHandle),
89-
LegacyHostImplementation.MappedType.Uint8Array => new Uint8Array(jsHandle),
90-
_ => throw new ArgumentOutOfRangeException(nameof(mappedType))
91-
};
92-
#pragma warning restore CS0612 // Type or member is obsolete
93-
JSHostImplementation.ThreadCsOwnedObjects[jsHandle] = new WeakReference<JSObject>(res, trackResurrection: true);
94-
}
95-
if (shouldAddInflight != 0)
96-
{
97-
res.AddInFlight();
98-
}
99-
jsObject = res;
67+
jsObject = JSProxyContext.MainThreadContext.CreateCSOwnedProxy(jsHandle, mappedType, shouldAddInflight);
10068
}
10169

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

10876
public static IntPtr GetJSOwnedObjectGCHandleRef(in object obj)
10977
{
110-
return JSHostImplementation.GetJSOwnedObjectGCHandle(obj, GCHandleType.Normal);
78+
return JSProxyContext.MainThreadContext.GetJSOwnedObjectGCHandle(obj, GCHandleType.Normal);
11179
}
11280

11381
public static IntPtr CreateTaskSource()

src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSException.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ public override string? StackTrace
4747
}
4848

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

0 commit comments

Comments
 (0)