Skip to content

Commit a76a115

Browse files
committed
Replace lock(RegisteredInstances) and SemaphoreSlim with ReaderWriterLockSlim
- Remove bridgeProcessingSemaphore (SemaphoreSlim) entirely - Replace lock(RegisteredInstances) with ReaderWriterLockSlim for AddPeer/RemovePeer/PeekPeer/GetSurfacedPeers - Bridge callback (OnBridgeProcessing) is completely lock-free since it runs during GC when all managed threads are suspended - Defer context cleanup to ConcurrentQueue<IntPtr> CollectedContexts, drained by CollectPeers() on managed threads with proper write lock - Add InitializeGCBridge() called from JNIEnvInit after AndroidRuntime is created, pre-initializing BridgeProcessing statics via RunClassConstructor before any GC can trigger the callback - HandleContext uses plain Dictionary since all access is protected by rwLock (managed threads) or exclusive GC access (bridge callback)
1 parent 4d30da0 commit a76a115

File tree

2 files changed

+117
-64
lines changed

2 files changed

+117
-64
lines changed

src/Mono.Android/Android.Runtime/JNIEnvInit.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
152152
args->jniAddNativeMethodRegistrationAttributePresent != 0
153153
);
154154

155+
// Initialize GC bridge after the runtime is created (JNI is now available).
156+
// This must happen before any GC can trigger the bridge callback.
157+
if (valueManager is ManagedValueManager managedValueManager) {
158+
managedValueManager.InitializeGCBridge ();
159+
}
160+
155161
grefIGCUserPeer_class = args->grefIGCUserPeer;
156162
grefGCUserPeerable_class = args->grefGCUserPeerable;
157163

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

Lines changed: 111 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)