Skip to content

Commit bba1d17

Browse files
Add BridgeProcessingLogger and use !RuntimeFeature.IsCoreClrRuntime
- Add BridgeProcessingLogger class for detailed GC bridge logging - Migrate logging from C++ bridge-processing.cc (same tags and formatting) - Enable logging based on Logger.LogGC and Logger.LogGlobalRef flags - Replace s_isNativeAOT field with !RuntimeFeature.IsCoreClrRuntime checks - Log: missing method errors, weak/gref operations, GC summary Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
1 parent 584b7ce commit bba1d17

File tree

1 file changed

+143
-11
lines changed

1 file changed

+143
-11
lines changed

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

Lines changed: 143 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Runtime.CompilerServices;
55
using System.Runtime.InteropServices;
66
using System.Runtime.InteropServices.Java;
7+
using System.Threading;
78
using Android.Runtime;
89
using Java.Interop;
910

@@ -33,18 +34,16 @@ unsafe class BridgeProcessing
3334
static JniObjectReference s_RuntimeInstance;
3435
static JniMethodInfo? s_Runtime_gc;
3536

36-
// Is NativeAOT mode (uses GCUserPeerable instead of IGCUserPeer)
37-
static readonly bool s_isNativeAOT;
37+
// Logger instance (null if logging is disabled)
38+
static readonly BridgeProcessingLogger? s_logger;
3839

3940
static BridgeProcessing ()
4041
{
41-
s_isNativeAOT = !RuntimeFeature.IsMonoRuntime;
42-
4342
// Initialize GCUserPeer class for creating temporary peers
4443
s_GCUserPeerClass = new JniType ("mono/android/GCUserPeer");
4544
s_GCUserPeerCtor = s_GCUserPeerClass.GetConstructor ("()V");
4645

47-
if (s_isNativeAOT) {
46+
if (!RuntimeFeature.IsCoreClrRuntime) {
4847
// For NativeAOT, we also use GCUserPeerable interface for optimized method calls
4948
s_GCUserPeerableClass = new JniType ("net/dot/jni/GCUserPeerable");
5049
s_GCUserPeerable_jiAddManagedReference = s_GCUserPeerableClass.GetInstanceMethod ("jiAddManagedReference", "(Ljava/lang/Object;)V");
@@ -58,6 +57,11 @@ static BridgeProcessing ()
5857
var runtimeLocal = JniEnvironment.StaticMethods.CallStaticObjectMethod (runtimeClass.PeerReference, getRuntimeMethod, null);
5958
s_RuntimeInstance = runtimeLocal.NewGlobalRef ();
6059
JniObjectReference.Dispose (ref runtimeLocal);
60+
61+
// Initialize logger if logging is enabled
62+
if (Logger.LogGC || Logger.LogGlobalRef) {
63+
s_logger = new BridgeProcessingLogger ();
64+
}
6165
}
6266

6367
public BridgeProcessing (MarkCrossReferencesArgs* args)
@@ -85,6 +89,7 @@ public void Process ()
8589
PrepareForJavaCollection ();
8690
TriggerJavaGC ();
8791
CleanupAfterJavaCollection ();
92+
s_logger?.LogGcSummary (crossRefs);
8893
}
8994

9095
/// <summary>
@@ -206,8 +211,10 @@ bool AddReference (JniObjectReference from, JniObjectReference to)
206211
}
207212

208213
// Try the optimized path for GCUserPeerable (NativeAOT)
209-
if (s_isNativeAOT && TryCallGCUserPeerableAddManagedReference (from, to)) {
210-
return true;
214+
if (!RuntimeFeature.IsCoreClrRuntime) {
215+
if (TryCallGCUserPeerableAddManagedReference (from, to)) {
216+
return true;
217+
}
211218
}
212219

213220
// Fall back to reflection-based approach
@@ -218,7 +225,7 @@ bool AddReference (JniObjectReference from, JniObjectReference to)
218225
try {
219226
addMethod = fromClass.GetInstanceMethod ("monodroidAddReference", "(Ljava/lang/Object;)V");
220227
} catch (Java.Lang.NoSuchMethodError) {
221-
Logger.Log (LogLevel.Error, "monodroid-gc", "Failed to find monodroidAddReference method");
228+
s_logger?.LogMissingAddReferencesMethod (fromClass);
222229
return false;
223230
}
224231

@@ -266,12 +273,16 @@ void TakeWeakGlobalRef (HandleContext* context)
266273
Debug.Assert (context->ControlBlock->HandleType == (int)JniObjectReferenceType.Global, "Expected global reference type for handle");
267274

268275
var handle = new JniObjectReference (context->ControlBlock->Handle, JniObjectReferenceType.Global);
276+
s_logger?.LogTakeWeakGlobalRef (handle);
277+
269278
var weak = handle.NewWeakGlobalRef ();
279+
s_logger?.LogWeakGrefNew (handle, weak);
270280

271281
context->ControlBlock->Handle = weak.Handle;
272282
context->ControlBlock->HandleType = (int)JniObjectReferenceType.WeakGlobal;
273283

274284
// Delete the old global ref
285+
s_logger?.LogGrefDelete (handle);
275286
JniObjectReference.Dispose (ref handle);
276287
}
277288

@@ -300,12 +311,18 @@ void TakeGlobalRef (HandleContext* context)
300311

301312
var weak = new JniObjectReference (context->ControlBlock->Handle, JniObjectReferenceType.WeakGlobal);
302313
var handle = weak.NewGlobalRef ();
314+
s_logger?.LogWeakToGref (weak, handle);
303315

304316
// The weak reference might have been collected
317+
if (handle.Handle == IntPtr.Zero) {
318+
s_logger?.LogWeakRefCollected (weak);
319+
}
320+
305321
context->ControlBlock->Handle = handle.Handle; // This may be null if collected
306322
context->ControlBlock->HandleType = (int)JniObjectReferenceType.Global;
307323

308324
// Delete the old weak ref
325+
s_logger?.LogWeakRefDelete (weak);
309326
JniObjectReference.Dispose (ref weak);
310327
}
311328

@@ -335,8 +352,10 @@ void ClearReferences (JniObjectReference handle)
335352
}
336353

337354
// Try the optimized path for GCUserPeerable (NativeAOT)
338-
if (s_isNativeAOT && TryCallGCUserPeerableClearManagedReferences (handle)) {
339-
return;
355+
if (!RuntimeFeature.IsCoreClrRuntime) {
356+
if (TryCallGCUserPeerableClearManagedReferences (handle)) {
357+
return;
358+
}
340359
}
341360

342361
// Fall back to reflection-based approach
@@ -347,7 +366,7 @@ void ClearReferences (JniObjectReference handle)
347366
try {
348367
clearMethod = javaClass.GetInstanceMethod ("monodroidClearReferences", "()V");
349368
} catch (Java.Lang.NoSuchMethodError) {
350-
Logger.Log (LogLevel.Error, "monodroid-gc", "Failed to find monodroidClearReferences method");
369+
s_logger?.LogMissingClearReferencesMethod (javaClass);
351370
return;
352371
}
353372

@@ -418,3 +437,116 @@ internal struct HandleContext
418437
public JniObjectReferenceControlBlock* ControlBlock;
419438
}
420439
}
440+
441+
/// <summary>
442+
/// Logger for GC bridge processing operations.
443+
/// Mirrors the logging from the original C++ bridge-processing.cc.
444+
/// </summary>
445+
unsafe class BridgeProcessingLogger
446+
{
447+
const string LogTag = "monodroid-gc";
448+
449+
public void LogMissingAddReferencesMethod (JniType javaClass)
450+
{
451+
Logger.Log (LogLevel.Error, LogTag, "Failed to find monodroidAddReference method");
452+
if (Logger.LogGC) {
453+
var className = GetClassName (javaClass);
454+
Logger.Log (LogLevel.Error, LogTag, $"Missing monodroidAddReference method for object of class {className}");
455+
}
456+
}
457+
458+
public void LogMissingClearReferencesMethod (JniType javaClass)
459+
{
460+
Logger.Log (LogLevel.Error, LogTag, "Failed to find monodroidClearReferences method");
461+
if (Logger.LogGC) {
462+
var className = GetClassName (javaClass);
463+
Logger.Log (LogLevel.Error, LogTag, $"Missing monodroidClearReferences method for object of class {className}");
464+
}
465+
}
466+
467+
public void LogWeakToGref (JniObjectReference weak, JniObjectReference handle)
468+
{
469+
if (!Logger.LogGlobalRef) {
470+
return;
471+
}
472+
Logger.Log (LogLevel.Info, LogTag, $"take_global_ref wref=0x{weak.Handle:x} -> handle=0x{handle.Handle:x}");
473+
}
474+
475+
public void LogWeakRefCollected (JniObjectReference weak)
476+
{
477+
if (!Logger.LogGC) {
478+
return;
479+
}
480+
Logger.Log (LogLevel.Info, LogTag, $"handle 0x{weak.Handle:x}/W; was collected by a Java GC");
481+
}
482+
483+
public void LogTakeWeakGlobalRef (JniObjectReference handle)
484+
{
485+
if (!Logger.LogGlobalRef) {
486+
return;
487+
}
488+
Logger.Log (LogLevel.Info, LogTag, $"take_weak_global_ref handle=0x{handle.Handle:x}");
489+
}
490+
491+
public void LogWeakGrefNew (JniObjectReference handle, JniObjectReference weak)
492+
{
493+
if (!Logger.LogGlobalRef) {
494+
return;
495+
}
496+
Logger.Log (LogLevel.Info, LogTag, $"weak_gref_new handle=0x{handle.Handle:x} -> weak=0x{weak.Handle:x}");
497+
}
498+
499+
public void LogGrefDelete (JniObjectReference handle)
500+
{
501+
if (!Logger.LogGlobalRef) {
502+
return;
503+
}
504+
Logger.Log (LogLevel.Info, LogTag, $"gref_delete handle=0x{handle.Handle:x} at [[clr-gc:take_weak_global_ref]]");
505+
}
506+
507+
public void LogWeakRefDelete (JniObjectReference weak)
508+
{
509+
if (!Logger.LogGlobalRef) {
510+
return;
511+
}
512+
Logger.Log (LogLevel.Info, LogTag, $"weak_ref_delete weak=0x{weak.Handle:x} at [[clr-gc:take_global_ref]]");
513+
}
514+
515+
public void LogGcSummary (MarkCrossReferencesArgs* crossRefs)
516+
{
517+
if (!Logger.LogGC) {
518+
return;
519+
}
520+
521+
nuint total = 0;
522+
nuint alive = 0;
523+
524+
for (nuint i = 0; i < crossRefs->ComponentCount; i++) {
525+
ref StronglyConnectedComponent scc = ref crossRefs->Components[i];
526+
527+
for (nuint j = 0; j < scc.Count; j++) {
528+
BridgeProcessing.HandleContext* context = (BridgeProcessing.HandleContext*)scc.Contexts[j];
529+
if (context == null) {
530+
continue;
531+
}
532+
533+
total++;
534+
if (context->ControlBlock != null && context->ControlBlock->Handle != IntPtr.Zero) {
535+
alive++;
536+
}
537+
}
538+
}
539+
540+
Logger.Log (LogLevel.Info, LogTag, $"GC cleanup summary: {total} objects tested - resurrecting {alive}.");
541+
}
542+
543+
static string GetClassName (JniType javaClass)
544+
{
545+
try {
546+
// Get class name via Java reflection
547+
return javaClass.PeerReference.IsValid ? javaClass.Name : "(unknown)";
548+
} catch {
549+
return "(unknown)";
550+
}
551+
}
552+
}

0 commit comments

Comments
 (0)