Skip to content

Commit 32495f3

Browse files
authored
[native, Mono.Android] GC bridge logging improvements (#9491)
Context: https://github.com/dotnet/android/issues/9039 While trying to further diagnose #9039, @jonpryor started fearing that a GC bridge issue might be in play, but the current GC bridge logging could use some improvements. Update `OSBridge::take_weak_global_ref_jni()` to do three things: 1. Set the `handle` and `handle_type` fields *before* deleting the original GREF. This is for a hypothetical thread-safety issue wherein the `handle` field and the `Handle` property could return a *deleted* GREF before being updated to the "new" Weak GREF. We have not seen this as being an issue in practice, but it's something we noticed [^1]. 2. Update the `from` parameter text to `_monodroid_weak_gref_new()` to look more like a stack trace. Previously, the GREF log would contain entries such as: monodroid-gref: -g- grefc 441 gwrefc 1 handle 0x4fda/G from thread 'finalizer'(17269) monodroid-gref: take_weak_global_ref_jni making @jonpryor wonder "why is `take_weak_global_ref_jni` there?!" It's there because of the `from` parameter, to provide context. Update it to instead read: monodroid-gref: -g- grefc 441 gwrefc 1 handle 0x4fda/G from thread 'finalizer'(17269) monodroid-gref: at [[gc:take_weak_global_ref_jni]] which makes it clearer what's intended. 3. Explicitly log when an instance was collected by the Java GC when the `debug.mono.gc` system property is set: monodroid-gref: handle 0x80f/W; key_handle 0xde7924e; MCW type: `Javax.Net.Ssl.HttpsURLConnectionInvoker`: was collected by a Java GC Update `OSBridge::take_global_ref_jni()` in a similar manner. Update other `_monodroid*_gref_*()` call sites to update *their* `from` parameters accordingly. Update `AndroidRuntime.FinalizePeer()` so that the messages it logs are actually useful. Currently they are: Finalizing handle 0x0/G which leaves a lot to be desired. Update it to instead read: Finalizing Instance.Type=Example.Whatever PeerReference=0x0/G IdentityHashCode=0x123456 Instance=0xdeadbeef Note that the `Instance` value is `RuntimeHelpers.GetHashCode()`, which may not be immediately useful. "Promote" `LOG_GC` to work in Release builds as well. This allows: adb shell setprop debug.mono.gc 1 to work with `libmono-android.release.so`, i.e. Release builds of apps. Update `OSBridge::gc_cross_references()` log the `handle` and `key_handle` fields of bridged instances when `debug.mono.gc` is set. All together, if you set: adb shell setprop debug.mono.log gref adb shell setprop debug.mono.gc 1 then `adb logcat` may have output similar to: /* 1 */ I monodroid-gref: +g+ grefc 38 gwrefc 0 obj-handle 0x7213951025/I -> new-handle 0x431a/G from thread '(null)'(1) /* 2 */ D monodroid-gref: at … /* 3 */ D monodroid-gref: handle 0x431a; key_handle 0xde7924e: Java Type: `com/android/okhttp/internal/huc/HttpsURLConnectionImpl`; MCW type: `Javax.Net.Ssl.HttpsURLConnectionInvoker` /* 4 */ … /* 5 */ I monodroid-gc: cross references callback invoked with 36 sccs and 0 xrefs. /* 6 */ I monodroid-gc: group 4 with 1 objects /* 7 */ I monodroid-gc: obj 0x6f4ecd4a60 [Javax.Net.Ssl::HttpsURLConnectionInvoker] handle 0x431a key_handle 0xde7924e /* 8 */ … /* 9 */ I monodroid-gref: +w+ grefc 67 gwrefc 5 obj-handle 0x431a/G -> new-handle 0x80f/W from thread 'finalizer'(6441) /* 10 */ I monodroid-gref: at [[gc:take_weak_global_ref_jni]] /* 11 */ I monodroid-gref: -g- grefc 66 gwrefc 5 handle 0x431a/G from thread 'finalizer'(6441) /* 12 */ I monodroid-gref: at [[gc:take_weak_global_ref_jni]] /* 13 */ D monodroid-gref: handle 0x80f/W; key_handle 0xde7924e; MCW type: `Javax.Net.Ssl.HttpsURLConnectionInvoker`: was collected by a Java GC /* 14 */ I monodroid-gref: -w- grefc 35 gwrefc 31 handle 0x80f/W from thread 'finalizer'(6441) /* 15 */ I monodroid-gref: at [[gc:take_global_ref_jni]] /* 16 */ … /* 17 */ D monodroid-gref: Finalizing Instance.Type=Javax.Net.Ssl.HttpsURLConnectionInvoker PeerReference=0x0/G IdentityHashCode=0xde7924e Instance=0xf7d6f98c with: * Lines 1-3: Creation of a `Javax.Net.Ssl.HttpsURLConnectionInvoker` instance with `key_handle` 0xde7924e. The omitted stack trace on "line 2" can be used to determine where the instance was created. * Lines 5-15+: GC bridge runs containing above `HttpsURLConnectionInvoker` instance. * Line 7: provides `key_handle` for correlation purposes. (`obj 0x6f4ecd4a60` is the raw pointer value, for MonoVM developer purposes.) * Line 13: Explicit call-out that the `HttpsURLConnectionInvoker` was collected by a Java-side GC. * Line 17: Finalization message. `IdentityHashCode` value is the `key_handle` value used elsewhere. [^1]: If it did happen, the app would crash and a JNI error message would be present in `adb logcat`, a'la: JNI DETECTED ERROR IN APPLICATION: use of deleted global reference 0x3d86
1 parent 8d2d53e commit 32495f3

File tree

6 files changed

+62
-29
lines changed

6 files changed

+62
-29
lines changed

src/Mono.Android/Android.Runtime/AndroidRuntime.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -846,7 +846,13 @@ public override void FinalizePeer (IJavaPeerable value)
846846
throw new ArgumentNullException (nameof (value));
847847

848848
if (Logger.LogGlobalRef) {
849-
RuntimeNativeMethods._monodroid_gref_log ($"Finalizing handle {value.PeerReference}\n");
849+
RuntimeNativeMethods._monodroid_gref_log (
850+
string.Format (CultureInfo.InvariantCulture,
851+
"Finalizing Instance.Type={0} PeerReference={1} IdentityHashCode=0x{2:x} Instance=0x{3:x}",
852+
value.GetType ().ToString (),
853+
value.PeerReference.ToString (),
854+
value.JniIdentityHashCode,
855+
RuntimeHelpers.GetHashCode (value)));
850856
}
851857

852858
// FIXME: need hash cleanup mechanism.

src/native/monodroid/monodroid-glue.cc

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -779,15 +779,19 @@ MonodroidRuntime::lookup_bridge_info (MonoClass *klass, const OSBridge::MonoJava
779779
info->handle = mono_class_get_field_from_name (info->klass, const_cast<char*> ("handle"));
780780
info->handle_type = mono_class_get_field_from_name (info->klass, const_cast<char*> ("handle_type"));
781781
info->refs_added = mono_class_get_field_from_name (info->klass, const_cast<char*> ("refs_added"));
782+
info->key_handle = mono_class_get_field_from_name (info->klass, const_cast<char*> ("key_handle"));
783+
784+
// key_handle is optional, as Java.Interop.JavaObject doesn't currently have it
782785
if (info->klass == nullptr || info->handle == nullptr || info->handle_type == nullptr || info->refs_added == nullptr) {
783786
Helpers::abort_application (
784787
Util::monodroid_strdup_printf (
785-
"The type `%s.%s` is missing required instance fields! handle=%p handle_type=%p refs_added=%p",
788+
"The type `%s.%s` is missing required instance fields! handle=%p handle_type=%p refs_added=%p key_handle=%p",
786789
type->_namespace,
787790
type->_typename,
788791
info->handle,
789792
info->handle_type,
790-
info->refs_added
793+
info->refs_added,
794+
info->key_handle
791795
)
792796
);
793797
}
@@ -965,7 +969,6 @@ MonodroidRuntime::propagate_uncaught_exception (JNIEnv *env, jobject javaThread,
965969
mono_runtime_invoke (method, nullptr, args, nullptr);
966970
}
967971

968-
#if DEBUG
969972
static void
970973
setup_gc_logging (void)
971974
{
@@ -974,7 +977,6 @@ setup_gc_logging (void)
974977
log_categories |= LOG_GC;
975978
}
976979
}
977-
#endif
978980

979981
inline void
980982
MonodroidRuntime::set_environment_variable_for_directory (const char *name, jstring_wrapper &value, bool createDirectory, mode_t mode)
@@ -1431,8 +1433,9 @@ MonodroidRuntime::Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass kl
14311433

14321434
Logger::init_reference_logging (AndroidSystem::get_primary_override_dir ());
14331435

1434-
#if DEBUG
14351436
setup_gc_logging ();
1437+
1438+
#if DEBUG
14361439
set_debug_env_vars ();
14371440
#endif
14381441

src/native/monodroid/osbridge.cc

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ OSBridge::MonoJavaGCBridgeInfo OSBridge::empty_bridge_info = {
4747
nullptr,
4848
nullptr,
4949
nullptr,
50+
nullptr,
5051
nullptr
5152
};
5253

@@ -461,18 +462,31 @@ OSBridge::take_global_ref_jni (JNIEnv *env, MonoObject *obj)
461462
_monodroid_gref_log_new (weak, get_object_ref_type (env, weak),
462463
handle, get_object_ref_type (env, handle),
463464
"finalizer", gettid (),
464-
"take_global_ref_jni", 0);
465-
}
466-
_monodroid_weak_gref_delete (weak, get_object_ref_type (env, weak),
467-
"finalizer", gettid (), "take_global_ref_jni", 0);
468-
env->DeleteWeakGlobalRef (weak);
469-
if (!handle) {
470-
void *old_handle = nullptr;
465+
" at [[gc:take_global_ref_jni]]", 0);
466+
} else if (Logger::gc_spew_enabled ()) [[unlikely]] {
467+
void *key_handle = nullptr;
468+
if (bridge_info->key_handle) {
469+
mono_field_get_value (obj, bridge_info->key_handle, &key_handle);
470+
}
471471

472-
mono_field_get_value (obj, bridge_info->handle, &old_handle);
472+
MonoClass *klass = mono_object_get_class (obj);
473+
char *message = Util::monodroid_strdup_printf (
474+
"handle %p/W; key_handle %p; MCW type: `%s.%s`: was collected by a Java GC",
475+
weak,
476+
key_handle,
477+
mono_class_get_namespace (klass),
478+
mono_class_get_name (klass));
479+
_monodroid_gref_log (message);
480+
free (message);
473481
}
482+
474483
mono_field_set_value (obj, bridge_info->handle, &handle);
475484
mono_field_set_value (obj, bridge_info->handle_type, &type);
485+
486+
_monodroid_weak_gref_delete (weak, get_object_ref_type (env, weak),
487+
"finalizer", gettid (), " at [[gc:take_global_ref_jni]]", 0);
488+
env->DeleteWeakGlobalRef (weak);
489+
476490
return handle != nullptr;
477491
}
478492

@@ -495,13 +509,14 @@ OSBridge::take_weak_global_ref_jni (JNIEnv *env, MonoObject *obj)
495509
weak = env->NewWeakGlobalRef (handle);
496510
_monodroid_weak_gref_new (handle, get_object_ref_type (env, handle),
497511
weak, get_object_ref_type (env, weak),
498-
"finalizer", gettid (), "take_weak_global_ref_jni", 0);
512+
"finalizer", gettid (), " at [[gc:take_weak_global_ref_jni]]", 0);
499513

500-
_monodroid_gref_log_delete (handle, get_object_ref_type (env, handle),
501-
"finalizer", gettid (), "take_weak_global_ref_jni", 0);
502-
env->DeleteGlobalRef (handle);
503514
mono_field_set_value (obj, bridge_info->handle, &weak);
504515
mono_field_set_value (obj, bridge_info->handle_type, &type);
516+
517+
_monodroid_gref_log_delete (handle, get_object_ref_type (env, handle),
518+
"finalizer", gettid (), " at [[gc:take_weak_global_ref_jni]]", 0);
519+
env->DeleteGlobalRef (handle);
505520
return 1;
506521
}
507522

@@ -903,7 +918,6 @@ OSBridge::gc_cross_references (int num_sccs, MonoGCBridgeSCC **sccs, int num_xre
903918
if (gc_disabled)
904919
return;
905920

906-
#if DEBUG
907921
if (Logger::gc_spew_enabled ()) {
908922
int i, j;
909923
log_info (LOG_GC, "cross references callback invoked with %d sccs and %d xrefs.", num_sccs, num_xrefs);
@@ -912,11 +926,23 @@ OSBridge::gc_cross_references (int num_sccs, MonoGCBridgeSCC **sccs, int num_xre
912926
log_info (LOG_GC, "group %d with %d objects", i, sccs [i]->num_objs);
913927
for (j = 0; j < sccs [i]->num_objs; ++j) {
914928
MonoObject *obj = sccs [i]->objs [j];
929+
930+
MonoJavaGCBridgeInfo *bridge_info = get_gc_bridge_info_for_object (obj);
931+
jobject handle = 0;
932+
void *key_handle = nullptr;
933+
if (bridge_info != nullptr) {
934+
mono_field_get_value (obj, bridge_info->handle, &handle);
935+
if (bridge_info->key_handle != nullptr) {
936+
mono_field_get_value (obj, bridge_info->key_handle, &key_handle);
937+
}
938+
}
915939
MonoClass *klass = mono_object_get_class (obj);
916-
log_info (LOG_GC, "\tobj %p [%s::%s]",
940+
log_info (LOG_GC, "\tobj %p [%s::%s] handle %p key_handle %p",
917941
obj,
918942
mono_class_get_namespace (klass),
919-
mono_class_get_name (klass));
943+
mono_class_get_name (klass),
944+
handle,
945+
key_handle);
920946
}
921947
}
922948

@@ -925,7 +951,6 @@ OSBridge::gc_cross_references (int num_sccs, MonoGCBridgeSCC **sccs, int num_xre
925951
log_info_nocheck (LOG_GC, "xref [%d] %d -> %d", i, xrefs [i].src_scc_index, xrefs [i].dst_scc_index);
926952
}
927953
}
928-
#endif
929954

930955
env = ensure_jnienv ();
931956

src/native/monodroid/osbridge.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ namespace xamarin::android::internal
4040
MonoClassField *handle;
4141
MonoClassField *handle_type;
4242
MonoClassField *refs_added;
43+
MonoClassField *key_handle;
4344
};
4445

4546
// add_reference can work with objects which are either MonoObjects with java peers, or raw jobjects

src/native/runtime-base/logger.cc

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,6 @@ Logger::init_logging_categories (char*& mono_log_mask, char*& mono_log_level) no
239239
#endif
240240
}
241241

242-
#if DEBUG
243242
if ((log_categories & LOG_GC) != 0)
244-
_gc_spew_enabled = 1;
245-
#endif /* DEBUG */
243+
_gc_spew_enabled = true;
246244
}

src/native/runtime-base/logger.hh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,17 @@ namespace xamarin::android {
3030
{
3131
return _debugger_log_level;
3232
}
33+
#endif // def DEBUG
3334

34-
static void set_gc_spew_enabled (int yesno) noexcept
35+
static void set_gc_spew_enabled (bool yesno) noexcept
3536
{
3637
_gc_spew_enabled = yesno;
3738
}
3839

39-
static int gc_spew_enabled () noexcept
40+
static bool gc_spew_enabled () noexcept
4041
{
4142
return _gc_spew_enabled;
4243
}
43-
#endif // def DEBUG
4444

4545
private:
4646
static bool set_category (std::string_view const& name, internal::string_segment& arg, unsigned int entry, bool arg_starts_with_name = false) noexcept;
@@ -50,8 +50,8 @@ namespace xamarin::android {
5050
#if defined(DEBUG)
5151
static inline bool _got_debugger_log_level = false;
5252
static inline int _debugger_log_level = 0;
53-
static inline int _gc_spew_enabled = 0;
5453
#endif // def DEBUG
54+
static inline bool _gc_spew_enabled = false;
5555
};
5656
}
5757
#endif

0 commit comments

Comments
 (0)