11// Originally from: https://github.com/dotnet/java-interop/blob/9b1d8781e8e322849d05efac32119c913b21c192/src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs
22using System ;
3- using System . Collections . Concurrent ;
43using System . Collections . Generic ;
54using System . Collections . ObjectModel ;
65using System . Diagnostics ;
@@ -23,11 +22,10 @@ class ManagedValueManager : JniRuntime.JniValueManager
2322 const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes . PublicConstructors | DynamicallyAccessedMemberTypes . NonPublicConstructors ;
2423
2524 readonly Dictionary < int , List < ReferenceTrackingHandle > > RegisteredInstances = new ( ) ;
26- readonly ConcurrentQueue < IntPtr > CollectedContexts = new ( ) ;
2725
2826 bool disposed ;
2927
30- static readonly SemaphoreSlim bridgeProcessingSemaphore = new ( 1 , 1 ) ;
28+ static readonly ReaderWriterLockSlim rwLock = new ( ) ;
3129
3230 static Lazy < ManagedValueManager > s_instance = new ( ( ) => new ManagedValueManager ( ) ) ;
3331 public static ManagedValueManager GetOrCreateInstance ( ) => s_instance . Value ;
@@ -41,7 +39,7 @@ static unsafe void OnBridgeProcessing (void* argsPtr)
4139 {
4240 MarkCrossReferencesArgs * args = ( MarkCrossReferencesArgs * ) argsPtr ;
4341
44- bridgeProcessingSemaphore . Wait ( ) ;
42+ rwLock . EnterWriteLock ( ) ;
4543 try {
4644 // Validate contexts
4745 HandleContext . EnsureAllContextsAreOurs ( args ) ;
@@ -51,10 +49,16 @@ static unsafe void OnBridgeProcessing (void* argsPtr)
5149 processing . Process ( ) ;
5250
5351 // Process collected contexts and finish
54- ReadOnlySpan < GCHandle > handlesToFree = ProcessCollectedContexts ( args ) ;
55- JavaMarshal . FinishCrossReferenceProcessing ( args , handlesToFree ) ;
52+ var ( handlesToFree , contextsToFree ) = ProcessCollectedContexts ( args ) ;
53+ JavaMarshal . FinishCrossReferenceProcessing ( args , CollectionsMarshal . AsSpan ( handlesToFree ) ) ;
54+ GC . KeepAlive ( handlesToFree ) ;
55+
56+ foreach ( IntPtr contextPtr in contextsToFree ) {
57+ var ctx = ( HandleContext * ) contextPtr ;
58+ HandleContext . Free ( ref ctx ) ;
59+ }
5660 } finally {
57- bridgeProcessingSemaphore . Release ( ) ;
61+ rwLock . ExitWriteLock ( ) ;
5862 }
5963 }
6064
@@ -80,51 +84,17 @@ void ThrowIfDisposed ()
8084
8185 public override void WaitForGCBridgeProcessing ( )
8286 {
83- bridgeProcessingSemaphore . Wait ( ) ;
84- bridgeProcessingSemaphore . Release ( ) ;
8587 }
8688
8789 public unsafe override void CollectPeers ( )
8890 {
8991 ThrowIfDisposed ( ) ;
90-
91- while ( CollectedContexts . TryDequeue ( out IntPtr contextPtr ) ) {
92- Debug . Assert ( contextPtr != IntPtr . Zero , "CollectedContexts should not contain null pointers." ) ;
93- HandleContext * context = ( HandleContext * ) contextPtr ;
94-
95- lock ( RegisteredInstances ) {
96- Remove ( context ) ;
97- }
98-
99- HandleContext . Free ( ref context ) ;
100- }
101-
102- void Remove ( HandleContext * context )
103- {
104- int key = context ->PeerIdentityHashCode ;
105- if ( ! RegisteredInstances . TryGetValue ( key , out List < ReferenceTrackingHandle > ? peers ) )
106- return ;
107-
108- for ( int i = peers . Count - 1 ; i >= 0 ; i -- ) {
109- var peer = peers [ i ] ;
110- if ( peer . BelongsToContext ( context ) ) {
111- peers . RemoveAt ( i ) ;
112- }
113- }
114-
115- if ( peers . Count == 0 ) {
116- RegisteredInstances . Remove ( key ) ;
117- }
118- }
11992 }
12093
12194 public override void AddPeer ( IJavaPeerable value )
12295 {
12396 ThrowIfDisposed ( ) ;
12497
125- // Remove any collected contexts before adding a new peer.
126- CollectPeers ( ) ;
127-
12898 var r = value . PeerReference ;
12999 if ( ! r . IsValid )
130100 throw new ObjectDisposedException ( value . GetType ( ) . FullName ) ;
@@ -134,11 +104,11 @@ public override void AddPeer (IJavaPeerable value)
134104 JniObjectReference . Dispose ( ref r , JniObjectReferenceOptions . CopyAndDispose ) ;
135105 }
136106 int key = value . JniIdentityHashCode ;
137- lock ( RegisteredInstances ) {
107+ rwLock . EnterUpgradeableReadLock ( ) ;
108+ try {
138109 List < ReferenceTrackingHandle > ? peers ;
139110 if ( ! RegisteredInstances . TryGetValue ( key , out peers ) ) {
140- peers = [ new ReferenceTrackingHandle ( value ) ] ;
141- RegisteredInstances . Add ( key , peers ) ;
111+ AddNewPeer ( key , value ) ;
142112 return ;
143113 }
144114
@@ -149,16 +119,49 @@ public override void AddPeer (IJavaPeerable value)
149119 if ( ! JniEnvironment . Types . IsSameObject ( target . PeerReference , value . PeerReference ) )
150120 continue ;
151121 if ( target . JniManagedPeerState . HasFlag ( JniManagedPeerStates . Replaceable ) ) {
152- peer . Dispose ( ) ;
153- peers [ i ] = new ReferenceTrackingHandle ( value ) ;
122+ ReplacePeer ( peers , i , peer , value ) ;
154123 } else {
155124 WarnNotReplacing ( key , value , target ) ;
156125 }
157126 GC . KeepAlive ( target ) ;
158127 return ;
159128 }
160129
161- peers . Add ( new ReferenceTrackingHandle ( value ) ) ;
130+ AppendPeer ( peers , value ) ;
131+ } finally {
132+ rwLock . ExitUpgradeableReadLock ( ) ;
133+ }
134+
135+ void AddNewPeer ( int key , IJavaPeerable value )
136+ {
137+ rwLock . EnterWriteLock ( ) ;
138+ try {
139+ var peers = new List < ReferenceTrackingHandle > { new ReferenceTrackingHandle ( value ) } ;
140+ RegisteredInstances . Add ( key , peers ) ;
141+ } finally {
142+ rwLock . ExitWriteLock ( ) ;
143+ }
144+ }
145+
146+ void ReplacePeer ( List < ReferenceTrackingHandle > peers , int index , ReferenceTrackingHandle oldPeer , IJavaPeerable value )
147+ {
148+ rwLock . EnterWriteLock ( ) ;
149+ try {
150+ oldPeer . Dispose ( ) ;
151+ peers [ index ] = new ReferenceTrackingHandle ( value ) ;
152+ } finally {
153+ rwLock . ExitWriteLock ( ) ;
154+ }
155+ }
156+
157+ void AppendPeer ( List < ReferenceTrackingHandle > peers , IJavaPeerable value )
158+ {
159+ rwLock . EnterWriteLock ( ) ;
160+ try {
161+ peers . Add ( new ReferenceTrackingHandle ( value ) ) ;
162+ } finally {
163+ rwLock . ExitWriteLock ( ) ;
164+ }
162165 }
163166 }
164167
@@ -187,7 +190,15 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal
187190
188191 int key = GetJniIdentityHashCode ( reference ) ;
189192
190- lock ( RegisteredInstances ) {
193+ rwLock . EnterReadLock ( ) ;
194+ try {
195+ return FindPeer ( key , reference ) ;
196+ } finally {
197+ rwLock . ExitReadLock ( ) ;
198+ }
199+
200+ IJavaPeerable ? FindPeer ( int key , JniObjectReference reference )
201+ {
191202 if ( ! RegisteredInstances . TryGetValue ( key , out List < ReferenceTrackingHandle > ? peers ) )
192203 return null ;
193204
@@ -199,39 +210,42 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal
199210 }
200211 }
201212
202- if ( peers . Count == 0 )
203- RegisteredInstances . Remove ( key ) ;
213+ return null ;
204214 }
205- return null ;
206215 }
207216
208217 public override void RemovePeer ( IJavaPeerable value )
209218 {
210219 ThrowIfDisposed ( ) ;
211220
212- // Remove any collected contexts before modifying RegisteredInstances
213- CollectPeers ( ) ;
214-
215221 if ( value == null )
216222 throw new ArgumentNullException ( nameof ( value ) ) ;
217223
218- lock ( RegisteredInstances ) {
219- int key = value . JniIdentityHashCode ;
220- if ( ! RegisteredInstances . TryGetValue ( key , out List < ReferenceTrackingHandle > ? peers ) )
221- return ;
224+ rwLock . EnterWriteLock ( ) ;
225+ try {
226+ RemovePeerCore ( value ) ;
227+ } finally {
228+ rwLock . ExitWriteLock ( ) ;
229+ }
230+ }
222231
223- for ( int i = peers . Count - 1 ; i >= 0 ; i -- ) {
224- ReferenceTrackingHandle peer = peers [ i ] ;
225- IJavaPeerable target = peer . Target ;
226- if ( ReferenceEquals ( value , target ) ) {
227- peers . RemoveAt ( i ) ;
228- peer . Dispose ( ) ;
229- }
230- GC . KeepAlive ( target ) ;
232+ void RemovePeerCore ( IJavaPeerable value )
233+ {
234+ int key = value . JniIdentityHashCode ;
235+ if ( ! RegisteredInstances . TryGetValue ( key , out List < ReferenceTrackingHandle > ? peers ) )
236+ return ;
237+
238+ for ( int i = peers . Count - 1 ; i >= 0 ; i -- ) {
239+ ReferenceTrackingHandle peer = peers [ i ] ;
240+ IJavaPeerable target = peer . Target ;
241+ if ( ReferenceEquals ( value , target ) ) {
242+ peers . RemoveAt ( i ) ;
243+ peer . Dispose ( ) ;
231244 }
232- if ( peers . Count == 0 )
233- RegisteredInstances . Remove ( key ) ;
245+ GC . KeepAlive ( target ) ;
234246 }
247+ if ( peers . Count == 0 )
248+ RegisteredInstances . Remove ( key ) ;
235249 }
236250
237251 public override void FinalizePeer ( IJavaPeerable value )
@@ -308,20 +322,25 @@ public override List<JniSurfacedPeerInfo> GetSurfacedPeers ()
308322 {
309323 ThrowIfDisposed ( ) ;
310324
311- // Remove any collected contexts before iterating over all the registered instances
312- CollectPeers ( ) ;
325+ rwLock . EnterReadLock ( ) ;
326+ try {
327+ return GetSurfacedPeersCore ( ) ;
328+ } finally {
329+ rwLock . ExitReadLock ( ) ;
330+ }
331+ }
313332
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- }
333+ List < JniSurfacedPeerInfo > GetSurfacedPeersCore ( )
334+ {
335+ var peers = new List < JniSurfacedPeerInfo > ( RegisteredInstances . Count ) ;
336+ foreach ( var ( identityHashCode , referenceTrackingHandles ) in RegisteredInstances ) {
337+ foreach ( var peer in referenceTrackingHandles ) {
338+ if ( peer . Target is IJavaPeerable target ) {
339+ peers . Add ( new JniSurfacedPeerInfo ( identityHashCode , new WeakReference < IJavaPeerable > ( target ) ) ) ;
321340 }
322341 }
323- return peers ;
324342 }
343+ return peers ;
325344 }
326345
327346 unsafe struct ReferenceTrackingHandle : IDisposable
@@ -392,22 +411,18 @@ private struct JniObjectReferenceControlBlock
392411
393412 public static GCHandle GetAssociatedGCHandle ( HandleContext * context )
394413 {
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 ;
414+ if ( ! referenceTrackingHandles . TryGetValue ( ( IntPtr ) context , out GCHandle handle ) ) {
415+ throw new InvalidOperationException ( "Unknown reference tracking handle." ) ;
401416 }
417+
418+ return handle ;
402419 }
403420
404421 public static unsafe void EnsureAllContextsAreOurs ( MarkCrossReferencesArgs * mcr )
405422 {
406- lock ( referenceTrackingHandles ) {
407- for ( nuint i = 0 ; i < mcr ->ComponentCount ; i ++ ) {
408- StronglyConnectedComponent component = mcr ->Components [ i ] ;
409- EnsureAllContextsInComponentAreOurs ( component ) ;
410- }
423+ for ( nuint i = 0 ; i < mcr ->ComponentCount ; i ++ ) {
424+ StronglyConnectedComponent component = mcr ->Components [ i ] ;
425+ EnsureAllContextsInComponentAreOurs ( component ) ;
411426 }
412427
413428 static void EnsureAllContextsInComponentAreOurs ( StronglyConnectedComponent component )
@@ -436,9 +451,7 @@ static void EnsureContextIsOurs (IntPtr context)
436451 context ->controlBlock = peer . JniObjectReferenceControlBlock ;
437452
438453 GCHandle handle = JavaMarshal . CreateReferenceTrackingHandle ( peer , context ) ;
439- lock ( referenceTrackingHandles ) {
440- referenceTrackingHandles [ ( IntPtr ) context ] = handle ;
441- }
454+ referenceTrackingHandles [ ( IntPtr ) context ] = handle ;
442455
443456 return context ;
444457 }
@@ -449,18 +462,17 @@ public static void Free (ref HandleContext* context)
449462 return ;
450463 }
451464
452- lock ( referenceTrackingHandles ) {
453- referenceTrackingHandles . Remove ( ( IntPtr ) context ) ;
454- }
465+ referenceTrackingHandles . Remove ( ( IntPtr ) context ) ;
455466
456467 NativeMemory . Free ( context ) ;
457468 context = null ;
458469 }
459470 }
460471
461- static unsafe ReadOnlySpan < GCHandle > ProcessCollectedContexts ( MarkCrossReferencesArgs * mcr )
472+ static unsafe ( List < GCHandle > handlesToFree , List < IntPtr > contextsToFree ) ProcessCollectedContexts ( MarkCrossReferencesArgs * mcr )
462473 {
463474 List < GCHandle > handlesToFree = [ ] ;
475+ List < IntPtr > contextsToFree = [ ] ;
464476 ManagedValueManager instance = GetOrCreateInstance ( ) ;
465477
466478 for ( int i = 0 ; ( nuint ) i < mcr ->ComponentCount ; i ++ ) {
@@ -482,17 +494,33 @@ void ProcessContext (HandleContext* context)
482494 }
483495
484496 GCHandle handle = HandleContext . GetAssociatedGCHandle ( context ) ;
497+ handlesToFree . Add ( handle ) ;
485498
486- // Note: modifying the RegisteredInstances dictionary while processing the collected contexts
487- // is tricky and can lead to deadlocks, so we remember which contexts were collected and we will free
488- // them later outside of the bridge processing loop.
489- instance . CollectedContexts . Enqueue ( ( IntPtr ) context ) ;
499+ // Remove from RegisteredInstances immediately (we hold the write lock)
500+ RemoveFromRegisteredInstances ( instance , context ) ;
490501
491- // important: we must not free the handle before passing it to JavaMarshal.FinishCrossReferenceProcessing
492- handlesToFree . Add ( handle ) ;
502+ // Defer HandleContext.Free until after JavaMarshal.FinishCrossReferenceProcessing
503+ contextsToFree . Add ( ( IntPtr ) context ) ;
493504 }
494505
495- return CollectionsMarshal . AsSpan ( handlesToFree ) ;
506+ return ( handlesToFree , contextsToFree ) ;
507+ }
508+
509+ static unsafe void RemoveFromRegisteredInstances ( ManagedValueManager instance , HandleContext * context )
510+ {
511+ int key = context ->PeerIdentityHashCode ;
512+ if ( ! instance . RegisteredInstances . TryGetValue ( key , out List < ReferenceTrackingHandle > ? peers ) )
513+ return ;
514+
515+ for ( int i = peers . Count - 1 ; i >= 0 ; i -- ) {
516+ if ( peers [ i ] . BelongsToContext ( context ) ) {
517+ peers . RemoveAt ( i ) ;
518+ }
519+ }
520+
521+ if ( peers . Count == 0 ) {
522+ instance . RegisteredInstances . Remove ( key ) ;
523+ }
496524 }
497525
498526 const BindingFlags ActivationConstructorBindingFlags = BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . Instance ;
0 commit comments