Skip to content

Commit eeabf88

Browse files
committed
Replace lock(RegisteredInstances) and SemaphoreSlim with ReaderWriterLockSlim
Use ReaderWriterLockSlim for proper synchronization between bridge processing and peer access operations: - Write lock: OnBridgeProcessing, CollectPeers, RemovePeer - Read lock: PeekPeer, GetSurfacedPeers (concurrent readers allowed) - Upgradeable read lock: AddPeer (reads concurrently, upgrades to write only when modifying) - Drop lock(referenceTrackingHandles) since write lock already provides exclusive access - WaitForGCBridgeProcessing is now a no-op since protection is built into each method
1 parent 4d30da0 commit eeabf88

File tree

1 file changed

+131
-103
lines changed

1 file changed

+131
-103
lines changed

src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs

Lines changed: 131 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Originally from: https://github.com/dotnet/java-interop/blob/9b1d8781e8e322849d05efac32119c913b21c192/src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs
22
using System;
3-
using System.Collections.Concurrent;
43
using System.Collections.Generic;
54
using System.Collections.ObjectModel;
65
using 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

Comments
 (0)