33
44#if FEATURE_WASM_THREADS
55
6- using System ;
76using System . Threading ;
87using System . Threading . Channels ;
9- using System . Runtime ;
10- using System . Runtime . InteropServices ;
118using System . Runtime . CompilerServices ;
12- using QueueType = System . Threading . Channels . Channel < System . Runtime . InteropServices . JavaScript . JSSynchronizationContext . WorkItem > ;
9+ using WorkItemQueueType = System . Threading . Channels . Channel < System . Runtime . InteropServices . JavaScript . JSSynchronizationContext . WorkItem > ;
1310
1411namespace System . Runtime . InteropServices . JavaScript
1512{
1613 /// <summary>
1714 /// Provides a thread-safe default SynchronizationContext for the browser that will automatically
18- /// route callbacks to the main browser thread where they can interact with the DOM and other
15+ /// route callbacks to the original browser thread where they can interact with the DOM and other
1916 /// thread-affinity-having APIs like WebSockets, fetch, WebGL, etc.
2017 /// Callbacks are processed during event loop turns via the runtime's background job system.
18+ /// See also https://github.com/dotnet/runtime/blob/main/src/mono/wasm/threads.md#JS-interop-on-dedicated-threads
2119 /// </summary>
2220 internal sealed class JSSynchronizationContext : SynchronizationContext
2321 {
24- public readonly Thread MainThread ;
22+ private readonly Action _DataIsAvailable ; // don't allocate Action on each call to UnsafeOnCompleted
23+ public readonly Thread TargetThread ;
24+ public readonly IntPtr TargetThreadId ;
25+ private readonly WorkItemQueueType Queue ;
26+
27+ [ ThreadStatic ]
28+ internal static JSSynchronizationContext ? CurrentJSSynchronizationContext ;
29+ internal SynchronizationContext ? previousSynchronizationContext ;
30+ internal bool isDisposed ;
2531
2632 internal readonly struct WorkItem
2733 {
@@ -37,34 +43,33 @@ public WorkItem(SendOrPostCallback callback, object? data, ManualResetEventSlim?
3743 }
3844 }
3945
40- private static JSSynchronizationContext ? MainThreadSynchronizationContext ;
41- private readonly QueueType Queue ;
42- private readonly Action _DataIsAvailable ; // don't allocate Action on each call to UnsafeOnCompleted
43-
44- private JSSynchronizationContext ( )
46+ internal JSSynchronizationContext ( Thread targetThread , IntPtr targetThreadId )
4547 : this (
46- Thread . CurrentThread ,
48+ targetThread , targetThreadId ,
4749 Channel . CreateUnbounded < WorkItem > (
4850 new UnboundedChannelOptions { SingleWriter = false , SingleReader = true , AllowSynchronousContinuations = true }
4951 )
5052 )
5153 {
5254 }
5355
54- private JSSynchronizationContext ( Thread mainThread , QueueType queue )
56+ private JSSynchronizationContext ( Thread targetThread , IntPtr targetThreadId , WorkItemQueueType queue )
5557 {
56- MainThread = mainThread ;
58+ TargetThread = targetThread ;
59+ TargetThreadId = targetThreadId ;
5760 Queue = queue ;
5861 _DataIsAvailable = DataIsAvailable ;
5962 }
6063
6164 public override SynchronizationContext CreateCopy ( )
6265 {
63- return new JSSynchronizationContext ( MainThread , Queue ) ;
66+ return new JSSynchronizationContext ( TargetThread , TargetThreadId , Queue ) ;
6467 }
6568
66- private void AwaitNewData ( )
69+ internal void AwaitNewData ( )
6770 {
71+ ObjectDisposedException . ThrowIf ( isDisposed , this ) ;
72+
6873 var vt = Queue . Reader . WaitToReadAsync ( ) ;
6974 if ( vt . IsCompleted )
7075 {
@@ -84,11 +89,13 @@ private unsafe void DataIsAvailable()
8489 {
8590 // While we COULD pump here, we don't want to. We want the pump to happen on the next event loop turn.
8691 // Otherwise we could get a chain where a pump generates a new work item and that makes us pump again, forever.
87- MainThreadScheduleBackgroundJob ( ( void * ) ( delegate * unmanaged[ Cdecl] < void > ) & BackgroundJobHandler ) ;
92+ TargetThreadScheduleBackgroundJob ( TargetThreadId , ( void * ) ( delegate * unmanaged[ Cdecl] < void > ) & BackgroundJobHandler ) ;
8893 }
8994
9095 public override void Post ( SendOrPostCallback d , object ? state )
9196 {
97+ ObjectDisposedException . ThrowIf ( isDisposed , this ) ;
98+
9299 var workItem = new WorkItem ( d , state , null ) ;
93100 if ( ! Queue . Writer . TryWrite ( workItem ) )
94101 throw new Exception ( "Internal error" ) ;
@@ -99,7 +106,9 @@ public override void Post(SendOrPostCallback d, object? state)
99106
100107 public override void Send ( SendOrPostCallback d , object ? state )
101108 {
102- if ( Thread . CurrentThread == MainThread )
109+ ObjectDisposedException . ThrowIf ( isDisposed , this ) ;
110+
111+ if ( Thread . CurrentThread == TargetThread )
103112 {
104113 d ( state ) ;
105114 return ;
@@ -115,27 +124,25 @@ public override void Send(SendOrPostCallback d, object? state)
115124 }
116125 }
117126
118- internal static void Install ( )
119- {
120- MainThreadSynchronizationContext ??= new JSSynchronizationContext ( ) ;
121- SynchronizationContext . SetSynchronizationContext ( MainThreadSynchronizationContext ) ;
122- MainThreadSynchronizationContext . AwaitNewData ( ) ;
123- }
124-
125127 [ MethodImplAttribute ( MethodImplOptions . InternalCall ) ]
126- internal static extern unsafe void MainThreadScheduleBackgroundJob ( void * callback ) ;
128+ internal static extern unsafe void TargetThreadScheduleBackgroundJob ( IntPtr targetThread , void * callback ) ;
127129
128130#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant
129131 [ UnmanagedCallersOnly ( CallConvs = new [ ] { typeof ( CallConvCdecl ) } ) ]
130132#pragma warning restore CS3016
131- // this callback will arrive on the bound thread, called from mono_background_exec
133+ // this callback will arrive on the target thread, called from mono_background_exec
132134 private static void BackgroundJobHandler ( )
133135 {
134- MainThreadSynchronizationContext ! . Pump ( ) ;
136+ CurrentJSSynchronizationContext ! . Pump ( ) ;
135137 }
136138
137139 private void Pump ( )
138140 {
141+ if ( isDisposed )
142+ {
143+ // FIXME: there could be abandoned work, but here we have no way how to propagate the failure
144+ return ;
145+ }
139146 try
140147 {
141148 while ( Queue . Reader . TryRead ( out var item ) )
@@ -160,7 +167,7 @@ private void Pump()
160167 finally
161168 {
162169 // If an item throws, we want to ensure that the next pump gets scheduled appropriately regardless.
163- AwaitNewData ( ) ;
170+ if ( ! isDisposed ) AwaitNewData ( ) ;
164171 }
165172 }
166173 }
0 commit comments