@@ -27,39 +27,47 @@ class ManagedValueManager : JniRuntime.JniValueManager
2727
2828 bool disposed ;
2929
30- static readonly SemaphoreSlim bridgeProcessingSemaphore = new ( 1 , 1 ) ;
30+ static readonly ReaderWriterLockSlim rwLock = new ( ) ;
3131
3232 static Lazy < ManagedValueManager > s_instance = new ( ( ) => new ManagedValueManager ( ) ) ;
3333 public static ManagedValueManager GetOrCreateInstance ( ) => s_instance . Value ;
3434
3535 /// <summary>
36- /// Callback invoked by native background thread to process bridge args.
37- /// This runs on the native bridge processing thread, not a managed thread pool thread.
36+ /// Callback invoked by the native GC bridge thread during garbage collection.
37+ /// All managed threads are suspended at this point, so no managed locks are needed —
38+ /// we have exclusive access to all managed data structures. Acquiring a managed lock
39+ /// here would deadlock if a suspended thread holds it.
3840 /// </summary>
3941 [ UnmanagedCallersOnly ]
4042 static unsafe void OnBridgeProcessing ( void * argsPtr )
4143 {
4244 MarkCrossReferencesArgs * args = ( MarkCrossReferencesArgs * ) argsPtr ;
4345
44- bridgeProcessingSemaphore . Wait ( ) ;
45- try {
46- // Validate contexts
47- HandleContext . EnsureAllContextsAreOurs ( args ) ;
46+ HandleContext . EnsureAllContextsAreOurs ( args ) ;
4847
49- // Do the actual bridge processing in managed code
50- var processing = new BridgeProcessing ( args ) ;
51- processing . Process ( ) ;
48+ var processing = new BridgeProcessing ( args ) ;
49+ processing . Process ( ) ;
5250
53- // Process collected contexts and finish
54- ReadOnlySpan < GCHandle > handlesToFree = ProcessCollectedContexts ( args ) ;
55- JavaMarshal . FinishCrossReferenceProcessing ( args , handlesToFree ) ;
56- } finally {
57- bridgeProcessingSemaphore . Release ( ) ;
58- }
51+ ReadOnlySpan < GCHandle > handlesToFree = ProcessCollectedContexts ( args ) ;
52+ JavaMarshal . FinishCrossReferenceProcessing ( args , handlesToFree ) ;
5953 }
6054
6155 unsafe ManagedValueManager ( )
6256 {
57+ }
58+
59+ /// <summary>
60+ /// Must be called after the JNI runtime is created (AndroidRuntime ctor), but before any GC
61+ /// can trigger the bridge callback.
62+ /// </summary>
63+ internal unsafe void InitializeGCBridge ( )
64+ {
65+ // Force static initialization of bridge processing types NOW, while JNI calls work normally.
66+ // These static constructors do JNI FindClass/GetMethod calls that would fail or deadlock
67+ // if run lazily inside the bridge processing callback (GC bridge restricts JNI operations).
68+ System . Runtime . CompilerServices . RuntimeHelpers . RunClassConstructor ( typeof ( BridgeProcessing ) . TypeHandle ) ;
69+ System . Runtime . CompilerServices . RuntimeHelpers . RunClassConstructor ( typeof ( BridgeProcessingJniHelper ) . TypeHandle ) ;
70+
6371 // Initialize GC bridge with our callback that will be invoked from the native background thread
6472 delegate * unmanaged< void * , void > callback = & OnBridgeProcessing ;
6573 var mark_cross_references_ftn = RuntimeNativeMethods . clr_initialize_gc_bridge ( callback ) ;
@@ -80,8 +88,6 @@ void ThrowIfDisposed ()
8088
8189 public override void WaitForGCBridgeProcessing ( )
8290 {
83- bridgeProcessingSemaphore . Wait ( ) ;
84- bridgeProcessingSemaphore . Release ( ) ;
8591 }
8692
8793 public unsafe override void CollectPeers ( )
@@ -91,9 +97,12 @@ public unsafe override void CollectPeers ()
9197 while ( CollectedContexts . TryDequeue ( out IntPtr contextPtr ) ) {
9298 Debug . Assert ( contextPtr != IntPtr . Zero , "CollectedContexts should not contain null pointers." ) ;
9399 HandleContext * context = ( HandleContext * ) contextPtr ;
94-
95- lock ( RegisteredInstances ) {
100+
101+ rwLock . EnterWriteLock ( ) ;
102+ try {
96103 Remove ( context ) ;
104+ } finally {
105+ rwLock . ExitWriteLock ( ) ;
97106 }
98107
99108 HandleContext . Free ( ref context ) ;
@@ -122,9 +131,6 @@ public override void AddPeer (IJavaPeerable value)
122131 {
123132 ThrowIfDisposed ( ) ;
124133
125- // Remove any collected contexts before adding a new peer.
126- CollectPeers ( ) ;
127-
128134 var r = value . PeerReference ;
129135 if ( ! r . IsValid )
130136 throw new ObjectDisposedException ( value . GetType ( ) . FullName ) ;
@@ -134,11 +140,11 @@ public override void AddPeer (IJavaPeerable value)
134140 JniObjectReference . Dispose ( ref r , JniObjectReferenceOptions . CopyAndDispose ) ;
135141 }
136142 int key = value . JniIdentityHashCode ;
137- lock ( RegisteredInstances ) {
143+ rwLock . EnterUpgradeableReadLock ( ) ;
144+ try {
138145 List < ReferenceTrackingHandle > ? peers ;
139146 if ( ! RegisteredInstances . TryGetValue ( key , out peers ) ) {
140- peers = [ new ReferenceTrackingHandle ( value ) ] ;
141- RegisteredInstances . Add ( key , peers ) ;
147+ AddNewPeer ( key , value ) ;
142148 return ;
143149 }
144150
@@ -149,16 +155,49 @@ public override void AddPeer (IJavaPeerable value)
149155 if ( ! JniEnvironment . Types . IsSameObject ( target . PeerReference , value . PeerReference ) )
150156 continue ;
151157 if ( target . JniManagedPeerState . HasFlag ( JniManagedPeerStates . Replaceable ) ) {
152- peer . Dispose ( ) ;
153- peers [ i ] = new ReferenceTrackingHandle ( value ) ;
158+ ReplacePeer ( peers , i , peer , value ) ;
154159 } else {
155160 WarnNotReplacing ( key , value , target ) ;
156161 }
157162 GC . KeepAlive ( target ) ;
158163 return ;
159164 }
160165
161- peers . Add ( new ReferenceTrackingHandle ( value ) ) ;
166+ AppendPeer ( peers , value ) ;
167+ } finally {
168+ rwLock . ExitUpgradeableReadLock ( ) ;
169+ }
170+
171+ void AddNewPeer ( int key , IJavaPeerable value )
172+ {
173+ var peers = new List < ReferenceTrackingHandle > { new ReferenceTrackingHandle ( value ) } ;
174+ rwLock . EnterWriteLock ( ) ;
175+ try {
176+ RegisteredInstances . Add ( key , peers ) ;
177+ } finally {
178+ rwLock . ExitWriteLock ( ) ;
179+ }
180+ }
181+
182+ void ReplacePeer ( List < ReferenceTrackingHandle > peers , int index , ReferenceTrackingHandle oldPeer , IJavaPeerable value )
183+ {
184+ rwLock . EnterWriteLock ( ) ;
185+ try {
186+ oldPeer . Dispose ( ) ;
187+ peers [ index ] = new ReferenceTrackingHandle ( value ) ;
188+ } finally {
189+ rwLock . ExitWriteLock ( ) ;
190+ }
191+ }
192+
193+ void AppendPeer ( List < ReferenceTrackingHandle > peers , IJavaPeerable value )
194+ {
195+ rwLock . EnterWriteLock ( ) ;
196+ try {
197+ peers . Add ( new ReferenceTrackingHandle ( value ) ) ;
198+ } finally {
199+ rwLock . ExitWriteLock ( ) ;
200+ }
162201 }
163202 }
164203
@@ -187,7 +226,15 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal
187226
188227 int key = GetJniIdentityHashCode ( reference ) ;
189228
190- lock ( RegisteredInstances ) {
229+ rwLock . EnterReadLock ( ) ;
230+ try {
231+ return FindPeer ( key , reference ) ;
232+ } finally {
233+ rwLock . ExitReadLock ( ) ;
234+ }
235+
236+ IJavaPeerable ? FindPeer ( int key , JniObjectReference reference )
237+ {
191238 if ( ! RegisteredInstances . TryGetValue ( key , out List < ReferenceTrackingHandle > ? peers ) )
192239 return null ;
193240
@@ -199,23 +246,26 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal
199246 }
200247 }
201248
202- if ( peers . Count == 0 )
203- RegisteredInstances . Remove ( key ) ;
249+ return null ;
204250 }
205- return null ;
206251 }
207252
208253 public override void RemovePeer ( IJavaPeerable value )
209254 {
210255 ThrowIfDisposed ( ) ;
211256
212- // Remove any collected contexts before modifying RegisteredInstances
213- CollectPeers ( ) ;
214-
215257 if ( value == null )
216258 throw new ArgumentNullException ( nameof ( value ) ) ;
217259
218- lock ( RegisteredInstances ) {
260+ rwLock . EnterWriteLock ( ) ;
261+ try {
262+ RemovePeerCore ( value ) ;
263+ } finally {
264+ rwLock . ExitWriteLock ( ) ;
265+ }
266+
267+ void RemovePeerCore ( IJavaPeerable value )
268+ {
219269 int key = value . JniIdentityHashCode ;
220270 if ( ! RegisteredInstances . TryGetValue ( key , out List < ReferenceTrackingHandle > ? peers ) )
221271 return ;
@@ -308,20 +358,25 @@ public override List<JniSurfacedPeerInfo> GetSurfacedPeers ()
308358 {
309359 ThrowIfDisposed ( ) ;
310360
311- // Remove any collected contexts before iterating over all the registered instances
312- CollectPeers ( ) ;
361+ rwLock . EnterReadLock ( ) ;
362+ try {
363+ return GetSurfacedPeersCore ( ) ;
364+ } finally {
365+ rwLock . ExitReadLock ( ) ;
366+ }
367+ }
313368
314- lock ( RegisteredInstances ) {
315- var peers = new List < JniSurfacedPeerInfo > ( RegisteredInstances . Count ) ;
316- foreach ( var ( identityHashCode , referenceTrackingHandles ) in RegisteredInstances ) {
317- foreach ( var peer in referenceTrackingHandles ) {
318- if ( peer . Target is IJavaPeerable target ) {
319- peers . Add ( new JniSurfacedPeerInfo ( identityHashCode , new WeakReference < IJavaPeerable > ( target ) ) ) ;
320- }
369+ List < JniSurfacedPeerInfo > GetSurfacedPeersCore ( )
370+ {
371+ var peers = new List < JniSurfacedPeerInfo > ( RegisteredInstances . Count ) ;
372+ foreach ( var ( identityHashCode , referenceTrackingHandles ) in RegisteredInstances ) {
373+ foreach ( var peer in referenceTrackingHandles ) {
374+ if ( peer . Target is IJavaPeerable target ) {
375+ peers . Add ( new JniSurfacedPeerInfo ( identityHashCode , new WeakReference < IJavaPeerable > ( target ) ) ) ;
321376 }
322377 }
323- return peers ;
324378 }
379+ return peers ;
325380 }
326381
327382 unsafe struct ReferenceTrackingHandle : IDisposable
@@ -392,22 +447,18 @@ private struct JniObjectReferenceControlBlock
392447
393448 public static GCHandle GetAssociatedGCHandle ( HandleContext * context )
394449 {
395- lock ( referenceTrackingHandles ) {
396- if ( ! referenceTrackingHandles . TryGetValue ( ( IntPtr ) context , out GCHandle handle ) ) {
397- throw new InvalidOperationException ( "Unknown reference tracking handle." ) ;
398- }
399-
400- return handle ;
450+ if ( ! referenceTrackingHandles . TryGetValue ( ( IntPtr ) context , out GCHandle handle ) ) {
451+ throw new InvalidOperationException ( "Unknown reference tracking handle." ) ;
401452 }
453+
454+ return handle ;
402455 }
403456
404457 public static unsafe void EnsureAllContextsAreOurs ( MarkCrossReferencesArgs * mcr )
405458 {
406- lock ( referenceTrackingHandles ) {
407- for ( nuint i = 0 ; i < mcr ->ComponentCount ; i ++ ) {
408- StronglyConnectedComponent component = mcr ->Components [ i ] ;
409- EnsureAllContextsInComponentAreOurs ( component ) ;
410- }
459+ for ( nuint i = 0 ; i < mcr ->ComponentCount ; i ++ ) {
460+ StronglyConnectedComponent component = mcr ->Components [ i ] ;
461+ EnsureAllContextsInComponentAreOurs ( component ) ;
411462 }
412463
413464 static void EnsureAllContextsInComponentAreOurs ( StronglyConnectedComponent component )
@@ -436,9 +487,7 @@ static void EnsureContextIsOurs (IntPtr context)
436487 context ->controlBlock = peer . JniObjectReferenceControlBlock ;
437488
438489 GCHandle handle = JavaMarshal . CreateReferenceTrackingHandle ( peer , context ) ;
439- lock ( referenceTrackingHandles ) {
440- referenceTrackingHandles [ ( IntPtr ) context ] = handle ;
441- }
490+ referenceTrackingHandles [ ( IntPtr ) context ] = handle ;
442491
443492 return context ;
444493 }
@@ -449,9 +498,7 @@ public static void Free (ref HandleContext* context)
449498 return ;
450499 }
451500
452- lock ( referenceTrackingHandles ) {
453- referenceTrackingHandles . Remove ( ( IntPtr ) context ) ;
454- }
501+ referenceTrackingHandles . Remove ( ( IntPtr ) context ) ;
455502
456503 NativeMemory . Free ( context ) ;
457504 context = null ;
0 commit comments