Skip to content

Commit 286d6fe

Browse files
[tests] Add regression test for global ref leak in LayoutInflater.Inflate (#11112)
Fixes: #11101 Fixes: #10989 The fix is in Java.Interop (ConstructPeer + Dispose for Invalid refs), included via the submodule bump. This adds an on-device regression test that inflates 100 custom views and asserts the JNI global reference count does not grow significantly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 522c1a6 commit 286d6fe

2 files changed

Lines changed: 44 additions & 2 deletions

File tree

tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Android.App;
1+
using System;
2+
using Android.App;
23
using Android.Content;
34
using Android.Util;
45
using Android.Views;
@@ -44,6 +45,47 @@ public void UpperAndLowerCaseCustomWidget_FromLibrary_ShouldNotThrowInflateExcep
4445
inflater.Inflate (Resource.Layout.upper_lower_custom, null);
4546
}, "Regression test for widgets with uppercase and lowercase namespace (bug #23880) failed.");
4647
}
48+
49+
// https://github.com/dotnet/android/issues/11101
50+
[Test]
51+
public void InflateCustomView_ShouldNotLeakGlobalRefs ()
52+
{
53+
var inflater = (LayoutInflater) Application.Context.GetSystemService (Context.LayoutInflaterService);
54+
Assert.IsNotNull (inflater);
55+
56+
// Warm up: inflate once to populate caches and type mappings,
57+
// and let any background thread activity from previous tests settle.
58+
inflater.Inflate (Resource.Layout.lowercase_custom, null);
59+
CollectGarbage (times: 3);
60+
61+
int grefBefore = Java.Interop.Runtime.GlobalReferenceCount;
62+
63+
// Use a large number of inflations so that a real leak (3+ global refs
64+
// per inflate) produces a delta far above any background noise from
65+
// Android system services, GC bridge processing, or finalizer threads.
66+
const int inflateCount = 100;
67+
for (int i = 0; i < inflateCount; i++) {
68+
inflater.Inflate (Resource.Layout.lowercase_custom, null);
69+
}
70+
71+
CollectGarbage (times: 3);
72+
73+
int grefAfter = Java.Interop.Runtime.GlobalReferenceCount;
74+
int delta = grefAfter - grefBefore;
75+
76+
// A real leak would produce delta >= 300 (3 leaked refs per inflate).
77+
// Use a generous threshold to tolerate background noise on real devices.
78+
Assert.IsTrue (delta <= 100,
79+
$"Global reference leak detected: {delta} extra global refs after inflating/GC'ing {inflateCount} custom views. Before={grefBefore}, After={grefAfter}");
80+
81+
static void CollectGarbage (int times)
82+
{
83+
for (int i = 0; i < times; i++) {
84+
GC.Collect ();
85+
GC.WaitForPendingFinalizers ();
86+
}
87+
}
88+
}
4789
}
4890

4991
public class CustomButton : Button

0 commit comments

Comments
 (0)