Skip to content

Commit 68062a7

Browse files
jonpryorjpobst
authored andcommitted
[logcat-parse] Various semantic bugfixes (#500)
Context: dotnet/android#3706 A memory leak in an app is reported. What Do We Do™? We enable GREF logging and use `logcat-parse`! GREF logging is enabled via the `debug.mono.log` Android system property, by having it include `gref`: adb shell setprop debug.mono.log gref,timing, When GREF logging is enabled, the app will write a `grefs.txt` file into the `.__override__` directory. Launch the app, use it for awhile, then extract the `grefs.txt` file: adb shell run-as ActivityRecreateTest.ActivityRecreateTest cat files/.__override__/grefs.txt > grefs.txt Once you have `grefs.txt`, use `logcat-parse.exe` to read it: $ mono /Library/Frameworks/Xamarin.Android.framework/Versions/Current/lib/xamarin.android/xbuild/Xamarin/Android/logcat-parse.exe grefs.txt # A different path is used on Windows, but it's still `logcat-parse.exe` // `adb logcat` GREF parsing utility // // Use `Grefs.Parse(stream)` to parse a file containing `adb logcat` output. // Grefs.AllocatedPeers contains all exposed Java.Lang.Object instances. // Grefs.AlivePeers contains those still alive by the end of parsing. var grefs = Grefs.Parse("grefs.txt"); That's when things get...weird: csharp> grefs.GrefCount; 245 csharp> grefs.AlivePeers.Count(); 18 There's a GREF count of 245, which matches what we can see within `grefs.txt`, which has the following line toward the end: +g+ grefc 245 gwrefc 0 obj-handle 0x79/I -> new-handle 0x2aa6/G from thread '(null)'(1) ...but there's only *18* peers? For 245 GREFs? That does *not* make sense. (It *is* possible to "alias" GREFs, but that's not very common in a Xamarin.Android app, and the above would require ~14 GREFs per peer, which is *very* unlikely.) Turns Out™, `logcat-parse` has bugs! ![Bugs Everywhere][0] ~~ Initial Handle Reuse ~~ One of the problems was that the handle used when an instance entered managed code could be *reused*: +g+ grefc 11 gwrefc 0 obj-handle 0x71/L -> new-handle 0x266a/G from thread '(null)'(1) ... +g+ grefc 15 gwrefc 0 obj-handle 0x71/I -> new-handle 0x26aa/G from thread '(null)'(1) ... +g+ grefc 21 gwrefc 0 obj-handle 0x71/I -> new-handle 0x2786/G from thread '(null)'(1) ... +g+ grefc 25 gwrefc 0 obj-handle 0x71/I -> new-handle 0x280a/G from thread '(null)'(1) ... +g+ grefc 26 gwrefc 0 obj-handle 0x71/I -> new-handle 0x2816/G from thread '(null)'(1) Note that `obj-handle 0x71` is present in *all* of these messages, but they *all* refer to *different object instances*. However, because of how `Grefs.GetPeerInfo()` and `PeerInfo.Handles` worked, they were all associated with the same `PeerInfo` instance, because the *first* `obj-handle 0x71` became the "owner" of all the *other* `obj-handle 0x71` instances. To address this, make two thanges to `Grefs` and `PeerInfo`: 1. Split up `PeerInfo.Handles` into `PeerInfo.Handles`, which contains all *current* handles, and `PeerInfo.RemovedHandles`, which contains all *previously seen but no longer valid* JNI handles. 2. When creating a new `PeerInfo` instance, DO NOT use the `obj-handle` value found in the `+g+` message. That is *immediately* considered a `RemovedHandle` value. These two changes prevent "accidental aliasing," in which `logcat-parse` believes that two entirely unrelated JNI handles actually belong to the same instance. ~~ Alive Peer Validation ~~ To help find related bugs, `GrefParseOptions` was overhauled so that information about "Alive Peers" could be validated. The semantics of `GrefParseOptions.CheckCounts` was altered so that it's now a unique state; *just* specifying it won't do anything, it needs to be combined with `GrefParseOptions.LogWarningOnMismatch` or `GrefParseOptions.ThrowExceptionOnMismatch`, or continue using the previously existing `GrefParseOptions.WarnOnCountMismatch` or `GrefParseOptions.ThrowOnCountMismatch` options. To these, we add: partial enum GrefParseOptions { CheckAlivePeers = 1 << 3, WarnOnAlivePeerMismatch = CheckAlivePeers | LogWarningOnMismatch, ThrowOnAlivePeerMismatch = CheckAlivePeers | ThrowExceptionOnMismatch, } You can enable Alive Peer validation by using the `Grefs.Parse()` overload within `logcat-parse.exe`: csharp> var grefs = Grefs.Parse ("grefs.txt", null, GrefParseOptions.ThrowOnAlivePeerMismatch); which will cause an exception to be thrown when an "alive peer" mismatch is encountered. This will happen when: * A `Disposing handle` message is encountered, but no `PeerInfo` can be found owning the specified handle. * A `Finalizing.*handle` message is encountered, but no `PeerInfo` can be found owning the specified handle. * A ``handle HANDLE; key_handle HANDLE; Java Type: `...`; MCW Type: `...``` message is encountered, and (1) no `PeerInfo` can be found which owns the specified handle, or (2) `PeerInfo.KeyHandle` has already been set on the instance, and thus `logcat-parse` is erroneously trying to *alter* an already-existing `PeerInfo` instance. * A `-g-` message is encountered, and no `PeerInfo` can be found which owns the specified GREF. ~~ Assorted Extras ~~ `JniHandleInfo` was updated so that a "full" handle can be used in comparisons, e.g.: grefs.AlivePeers.Count(p => p.Handles.Contains ("0x1234/G")); Previously this wouldn't match, because the `0x1234/G` would be compared against `0x1234` (no `/G`) in `JniHandleInfo.Handle`. `PeerInfo.Handles` is now an `IList<JniHandleInfo>`, instead of an `ISet<JniHandleInfo>`, so that we can now reason about *time*: earlier entries will be created earlier in time, and the order of `PeerInfo.Handles` and `PeerInfo.RemovedHandles` is the order encountered within the log file. ~~ Result ~~ With the above changes, processing `grefs.txt` makes more sense: $ mono /Library/Frameworks/Xamarin.Android.framework/Versions/Current/lib/xamarin.android/xbuild/Xamarin/Android/logcat-parse.exe grefs.txt ... csharp> grefs.GrefCount; 245 csharp> grefs.AlivePeers.Count(); 245 This in turn allows us to get appropriate type counts: csharp> grefs.GetAliveJniTypeCounts(); {{ "", 14 }, { "com/android/internal/os/RuntimeInit$KillApplicationHandler", 1 }, { "android/runtime/UncaughtExceptionHandler", 1 }, { "md5327d1d288bcaf8c0cc06d09d4b2f8e3a/MainActivity", 45 }, { "android/support/v7/widget/Toolbar", 45 }, { "android/support/design/widget/FloatingActionButton", 45 }, { "android/support/v7/view/menu/MenuBuilder", 45 }, { "android/support/v7/view/SupportMenuInflater", 45 }, { "mono/android/view/View_OnClickListenerImplementor", 3 }, { "android/os/Bundle", 1 }} For this run, there are 45 `MainActivity` instances, along with instances which `MainActivity` references, such as `ToolBar`, `FloatingActionButton`, `MenuBuilder`, etc. [0]: https://media.makeameme.org/created/bugs-bugs-everywhere-kyn0a3.jpg
1 parent 7a309c4 commit 68062a7

File tree

4 files changed

+142
-64
lines changed

4 files changed

+142
-64
lines changed

tools/logcat-parse/GrefParseOptions.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,16 @@ namespace Xamarin.Android.Tools.LogcatParse {
55
[Flags]
66
public enum GrefParseOptions {
77
None,
8-
CheckCounts = 1,
9-
WarnOnCountMismatch = 1,
10-
ThrowOnCountMismatch = 2,
8+
9+
LogWarningOnMismatch = 1 << 0,
10+
ThrowExceptionOnMismatch = 1 << 1,
11+
12+
CheckCounts = 1 << 2,
13+
WarnOnCountMismatch = CheckCounts | LogWarningOnMismatch,
14+
ThrowOnCountMismatch = CheckCounts | ThrowExceptionOnMismatch,
15+
16+
CheckAlivePeers = 1 << 3,
17+
WarnOnAlivePeerMismatch = CheckAlivePeers | LogWarningOnMismatch,
18+
ThrowOnAlivePeerMismatch = CheckAlivePeers | ThrowExceptionOnMismatch,
1119
}
1220
}

tools/logcat-parse/Grefs.cs

Lines changed: 68 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ Dictionary<Regex, Action<Match, int>> CreateHandlers (int? pid)
107107
return new Dictionary<Regex, Action<Match, int>> {
108108
{ new Regex (addGm), (m, l) => {
109109
var h = GetHandle (m, "ghandle");
110-
var p = GetPeerInfo (m);
110+
var p = GetLastOrCreatePeerInfo (m, h);
111111
p.AppendStackTraceForHandle (h, m.Groups ["stack"].Value);
112112
GlobalRefs.Add (h);
113113
CheckCounts (m, l);
@@ -117,7 +117,7 @@ Dictionary<Regex, Action<Match, int>> CreateHandlers (int? pid)
117117
} },
118118
{ new Regex (addWm), (m, l) => {
119119
var h = GetHandle (m, "whandle");
120-
var p = GetPeerInfo (m);
120+
var p = GetLastOrCreatePeerInfo (m, h);
121121
p.AppendStackTraceForHandle (h, m.Groups ["stack"].Value);
122122
WeakRefs.Add (h);
123123
CheckCounts (m, l);
@@ -126,24 +126,47 @@ Dictionary<Regex, Action<Match, int>> CreateHandlers (int? pid)
126126
curHandle = h;
127127
} },
128128
{ new Regex (dm), (m, l) => {
129-
var p = GetPeerInfo (m);
129+
var p = GetAlivePeerInfo (m);
130+
if (p == null) {
131+
LogWarning (GrefParseOptions.CheckAlivePeers, $"at line {l}: could not find PeerInfo for disposed handle {GetHandle (m, "handle")}");
132+
return;
133+
}
130134
p.Disposed = true;
131135
} },
132136
{ new Regex (fm), (m, l) => {
133-
var p = GetPeerInfo (m);
137+
var p = GetAlivePeerInfo (m);
138+
if (p == null) {
139+
LogWarning (GrefParseOptions.CheckAlivePeers, $"at line {l}: could not find PeerInfo for finalized handle {GetHandle (m, "handle")}");
140+
return;
141+
}
134142
p.Finalized = true;
135143
} },
136144
{ new Regex (hm), (m, l) => {
137-
var p = GetPeerInfo (m);
145+
var p = GetAlivePeerInfo (m);
146+
if (p == null) {
147+
LogWarning (GrefParseOptions.CheckAlivePeers, $"at line {l}: could not find PeerInfo for handle {GetHandle (m, "handle")}");
148+
return;
149+
}
150+
if (!string.IsNullOrEmpty (p.KeyHandle)) {
151+
LogWarning (GrefParseOptions.CheckAlivePeers, $"at line {l}: Attempting to re-set p.KeyHandle {p.KeyHandle} for `{p.JniType}` to {m.Groups ["key_handle"].Value} for {m.Groups ["jtype"].Value}");
152+
return;
153+
}
138154
p.KeyHandle = m.Groups ["key_handle"].Value;
139155
p.JniType = m.Groups ["jtype"].Value;
140156
p.McwType = m.Groups ["mtype"].Value;
141157
} },
142158
{ new Regex (remGm), (m, l) => {
143159
var h = GetHandle (m, "handle");
144-
var p = GetPeerInfo (m);
160+
var p = GetAlivePeerInfo (m);
161+
if (p == null) {
162+
LogWarning (GrefParseOptions.CheckAlivePeers, $"at line {l}: could not find PeerInfo to remove gref for handle {h}");
163+
}
145164
GlobalRefs.Remove (h);
146-
if (!WeakRefs.Any (w => p.Handles.Contains (w))) {
165+
if (p != null) {
166+
p.Handles.Remove (h);
167+
p.RemovedHandles.Add (h);
168+
}
169+
if (p != null && !WeakRefs.Any (w => p.Handles.Contains (w))) {
147170
p.Collected = true;
148171
p.DestroyedOnThread = m.Groups ["thread"].Value;
149172
alive.Remove (p);
@@ -155,12 +178,18 @@ Dictionary<Regex, Action<Match, int>> CreateHandlers (int? pid)
155178
} },
156179
{ new Regex (remWm), (m, l) => {
157180
var h = GetHandle (m, "handle");
158-
var p = GetPeerInfo (m);
181+
var p = GetAlivePeerInfo (m);
159182
WeakRefs.Remove (h);
160-
if (!GlobalRefs.Any (g => p.Handles.Contains (g))) {
161-
p.Collected = true;
162-
p.DestroyedOnThread = m.Groups ["thread"].Value;
163-
alive.Remove (p);
183+
if (p == null) {
184+
// Means that the instance has been collected; this is fine.
185+
} else {
186+
p.Handles.Remove (h);
187+
p.RemovedHandles.Add (h);
188+
if (!GlobalRefs.Any (g => p.Handles.Contains (g))) {
189+
p.Collected = true;
190+
p.DestroyedOnThread = m.Groups ["thread"].Value;
191+
alive.Remove (p);
192+
}
164193
}
165194
CheckCounts (m, l);
166195

@@ -188,27 +217,42 @@ static JniHandleInfo GetHandle (Match m, string handleGroup)
188217

189218
void CheckCounts (Match m, int lineNumber)
190219
{
191-
if ((ParseOptions & GrefParseOptions.CheckCounts) == 0 &&
192-
(ParseOptions & GrefParseOptions.ThrowOnCountMismatch) == 0)
220+
if ((ParseOptions & GrefParseOptions.CheckCounts) == 0)
193221
return;
194222
string message = null;
195223
int gc;
196224
if (int.TryParse (m.Groups ["gcount"].Value, out gc) && gc != GrefCount) {
197225
message = string.Format (" grefc mismatch at line {0}: expected {1,5}, actual {2,5}; line: {3}",
198-
lineNumber, GrefCount, gc, m.Groups [0]);
226+
lineNumber, GrefCount, gc, m.Groups[0]);
199227
}
200228
if (int.TryParse (m.Groups ["wgcount"].Value, out gc) && gc != WeakGrefCount) {
201229
message = string.Format ("wgrefc mismatch at line {0}; expected {1,5}, actual {2,5}; line: {3}",
202-
lineNumber, WeakGrefCount, gc, m.Groups [0]);
230+
lineNumber, WeakGrefCount, gc, m.Groups[0]);
203231
}
204232
if (message != null) {
205-
Console.WriteLine ("Warning: {0}", message);
206-
if ((ParseOptions & GrefParseOptions.ThrowOnCountMismatch) != 0)
207-
throw new InvalidOperationException (message);
233+
LogWarning (GrefParseOptions.CheckCounts, message);
208234
}
209235
}
210236

211-
PeerInfo GetPeerInfo (Match m)
237+
void LogWarning (GrefParseOptions type, string message)
238+
{
239+
Console.WriteLine ("Warning: {0}", message);
240+
241+
GrefParseOptions shouldThrow = type | GrefParseOptions.ThrowExceptionOnMismatch;
242+
if ((ParseOptions & shouldThrow) == shouldThrow)
243+
throw new InvalidOperationException (message);
244+
}
245+
246+
PeerInfo GetAlivePeerInfo (Match m)
247+
{
248+
var h = GetHandle (m, "handle");
249+
var i = alive.FindLastIndex (p => p.Handles.Contains(h));
250+
if (i >= 0)
251+
return alive [i];
252+
return null;
253+
}
254+
255+
PeerInfo GetLastOrCreatePeerInfo (Match m, JniHandleInfo newHandle)
212256
{
213257
var h = GetHandle (m, "handle");
214258
var i = alive.FindLastIndex (p => p.Handles.Contains (h));
@@ -218,6 +262,10 @@ PeerInfo GetPeerInfo (Match m)
218262
var peer = new PeerInfo (pid) {
219263
CreatedOnThread = m.Groups ["thread"].Value,
220264
Handles = {
265+
newHandle,
266+
},
267+
// Not *technically* removed, but `h` can't appear in Handles either, because of possible handle "reuse"
268+
RemovedHandles = {
221269
h,
222270
},
223271
};

tools/logcat-parse/PeerInfo.cs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,22 +97,30 @@ public static implicit operator JniHandleInfo (string handle)
9797

9898
public static bool operator==(JniHandleInfo lhs, string rhs)
9999
{
100-
return lhs.Handle == rhs;
100+
if (string.IsNullOrEmpty (rhs))
101+
return lhs.Handle == rhs;
102+
return lhs == new JniHandleInfo (rhs);
101103
}
102104

103105
public static bool operator!=(JniHandleInfo lhs, string rhs)
104106
{
105-
return lhs.Handle != rhs;
107+
if (string.IsNullOrEmpty (rhs))
108+
return lhs.Handle != rhs;
109+
return lhs != new JniHandleInfo (rhs);
106110
}
107111

108112
public static bool operator==(string lhs, JniHandleInfo rhs)
109113
{
110-
return lhs == rhs.Handle;
114+
if (string.IsNullOrEmpty (lhs))
115+
return lhs == rhs.Handle;
116+
return new JniHandleInfo (lhs) == rhs;
111117
}
112118

113119
public static bool operator!=(string lhs, JniHandleInfo rhs)
114120
{
115-
return lhs != rhs.Handle;
121+
if (string.IsNullOrEmpty (lhs))
122+
return lhs != rhs.Handle;
123+
return new JniHandleInfo (lhs) != rhs;
116124
}
117125
}
118126

@@ -154,7 +162,9 @@ public bool Alive {
154162
public string McwType {get; internal set;}
155163

156164
public string KeyHandle {get; internal set;}
157-
public ISet<JniHandleInfo> Handles {get; private set;}
165+
public IList<JniHandleInfo> Handles {get; private set;}
166+
167+
public IList<JniHandleInfo> RemovedHandles {get; private set;}
158168

159169
public string CreatedOnThread { get; internal set; }
160170
public string DestroyedOnThread { get; internal set; }
@@ -167,8 +177,9 @@ public IEnumerable<string> StackTraces {
167177

168178
public PeerInfo (string pid)
169179
{
170-
Handles = new HashSet<JniHandleInfo> ();
180+
Handles = new List<JniHandleInfo> ();
171181
KeyHandle = JniType = McwType = "";
182+
RemovedHandles = new List<JniHandleInfo> ();
172183
int p;
173184
if (int.TryParse (pid, NumberStyles.Integer, CultureInfo.InvariantCulture, out p))
174185
Pid = p;

tools/logcat-parse/Tests/GrefsTest.cs

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ public void Instances_GrefToWgrefToCollected ()
2727
Assert.AreEqual ("android/widget/ProgressBar", peer.JniType);
2828
Assert.AreEqual ("Android.Widget.ProgressBar", peer.McwType);
2929
Assert.AreEqual ("0x41f008f8", peer.KeyHandle);
30-
Assert.AreEqual (3, peer.Handles.Count);
31-
Assert.IsTrue (peer.Handles.Contains ("0x1d20046a"));
32-
Assert.IsTrue (peer.Handles.Contains ("0x1d200003"));
33-
Assert.IsTrue (peer.Handles.Contains ("0x41f008f8"));
30+
Assert.AreEqual (0, peer.Handles.Count);
31+
Assert.AreEqual (3, peer.RemovedHandles.Count);
32+
Assert.IsTrue (peer.RemovedHandles [0] == "0x41f008f8/L");
33+
Assert.IsTrue (peer.RemovedHandles [1] == "0x1d20046a/G");
34+
Assert.IsTrue (peer.RemovedHandles [2] == "0x1d200003/W");
35+
3436
Assert.AreEqual (
3537
" at Android.Runtime.JNIEnv.NewGlobalRef(IntPtr jobject)\n" +
3638
" at Java.Lang.Object.RegisterInstance(IJavaObject instance, IntPtr value, JniHandleOwnership transfer)\n" +
@@ -60,9 +62,10 @@ public void Instances_GrefToDisposed ()
6062
Assert.AreEqual ("java/lang/String", peer.JniType);
6163
Assert.AreEqual ("Java.Lang.String", peer.McwType);
6264
Assert.AreEqual ("0x41e29778", peer.KeyHandle);
63-
Assert.AreEqual (2, peer.Handles.Count);
64-
Assert.IsTrue (peer.Handles.Contains ("0x1d200282"));
65-
Assert.IsTrue (peer.Handles.Contains ("0x41e29778"));
65+
Assert.AreEqual (0, peer.Handles.Count);
66+
Assert.AreEqual (2, peer.RemovedHandles.Count);
67+
Assert.IsTrue (peer.RemovedHandles [0] == "0x41e29778/L");
68+
Assert.IsTrue (peer.RemovedHandles [1] == "0x1d200282/G");
6669
Assert.AreEqual (
6770
" at Android.Runtime.JNIEnv.NewGlobalRef(IntPtr jobject)\n" +
6871
" at Java.Lang.Object.RegisterInstance(IJavaObject instance, IntPtr value, JniHandleOwnership transfer)\n" +
@@ -95,11 +98,12 @@ public void Instances_Resurrection ()
9598
Assert.AreEqual ("android/widget/LinearLayout", peer.JniType);
9699
Assert.AreEqual ("Android.Widget.LinearLayout", peer.McwType);
97100
Assert.AreEqual ("0x41ff8758", peer.KeyHandle);
98-
Assert.AreEqual (4, peer.Handles.Count);
99-
Assert.IsTrue (peer.Handles.Contains ("0x1d200f1e"));
100-
Assert.IsTrue (peer.Handles.Contains ("0x1d9000cb"));
101-
Assert.IsTrue (peer.Handles.Contains ("0x1d300eea"));
102-
Assert.IsTrue (peer.Handles.Contains ("0x41ff8758"));
101+
Assert.AreEqual (1, peer.Handles.Count);
102+
Assert.IsTrue (peer.Handles [0] == "0x1d300eea");
103+
Assert.AreEqual (3, peer.RemovedHandles.Count);
104+
Assert.IsTrue (peer.RemovedHandles [0] == "0x41ff8758/L");
105+
Assert.IsTrue (peer.RemovedHandles [1] == "0x1d200f1e/G");
106+
Assert.IsTrue (peer.RemovedHandles [2] == "0x1d9000cb/W");
103107
Assert.AreEqual (
104108
" at Android.Runtime.JNIEnv.NewGlobalRef(IntPtr jobject)\n" +
105109
" at Java.Lang.Object.RegisterInstance(IJavaObject instance, IntPtr value, JniHandleOwnership transfer)\n" +
@@ -130,9 +134,10 @@ void Instances_CreateAndDestroy (string resource)
130134
Assert.IsFalse (peer.Disposed);
131135
Assert.AreEqual ("Java.Lang.Thread+RunnableImplementor.class", peer.JniType);
132136
Assert.AreEqual ("typeof(Java.Lang.Thread+RunnableImplementor)", peer.McwType);
133-
Assert.AreEqual (2, peer.Handles.Count);
134-
Assert.IsTrue (peer.Handles.Contains ("0x1d200276"));
135-
Assert.IsTrue (peer.Handles.Contains ("0x41e29370"));
137+
Assert.AreEqual (0, peer.Handles.Count);
138+
Assert.AreEqual (2, peer.RemovedHandles.Count);
139+
Assert.IsTrue (peer.RemovedHandles [0] == "0x41e29370/L");
140+
Assert.IsTrue (peer.RemovedHandles [1] == "0x1d200276/G");
136141
Assert.AreEqual (
137142
" at Android.Runtime.JNIEnv.NewGlobalRef(IntPtr jobject)\n" +
138143
" at Android.Runtime.JNIEnv.FindClass(System.String classname)\n" +
@@ -185,10 +190,11 @@ public void Instances_Alias ()
185190
Assert.IsFalse (peer.Disposed);
186191
Assert.AreEqual ("android/widget/NewButton", peer.JniType);
187192
Assert.AreEqual ("Android.Widget.NewButton", peer.McwType);
188-
Assert.AreEqual (3, peer.Handles.Count);
189-
Assert.IsTrue (peer.Handles.Contains ("0xbecdf114"));
193+
Assert.AreEqual (2, peer.Handles.Count);
190194
Assert.IsTrue (peer.Handles.Contains ("0x100456"));
191195
Assert.IsTrue (peer.Handles.Contains ("0x100472"));
196+
Assert.AreEqual (1, peer.RemovedHandles.Count);
197+
Assert.IsTrue (peer.RemovedHandles [0] == "0xbecdf114/L");
192198
Assert.AreEqual (
193199
" at Android.Runtime.JNIEnv.NewGlobalRef(IntPtr jobject)\n" +
194200
" at Java.Lang.Object.RegisterInstance(IJavaObject instance, IntPtr value, JniHandleOwnership transfer, IntPtr ByRef handle)\n" +
@@ -220,9 +226,10 @@ public void GetClassRef ()
220226
Assert.IsFalse (peer.Disposed);
221227
Assert.AreEqual ("Android.Widget.Button.class", peer.JniType);
222228
Assert.AreEqual ("typeof(Android.Widget.Button)", peer.McwType);
223-
Assert.AreEqual (2, peer.Handles.Count);
224-
Assert.IsTrue (peer.Handles.Contains ("0x7830001d"));
225-
Assert.IsTrue (peer.Handles.Contains ("0x10046a"));
229+
Assert.AreEqual (1, peer.Handles.Count);
230+
Assert.IsTrue (peer.Handles [0] == "0x10046a");
231+
Assert.AreEqual (1, peer.RemovedHandles.Count);
232+
Assert.IsTrue (peer.RemovedHandles [0] == "0x7830001d/L");
226233

227234
Assert.AreEqual (
228235
" at Android.Runtime.JNIEnv.NewGlobalRef(IntPtr jobject)\n" +
@@ -250,9 +257,10 @@ public void InvokerJavaClassRef ()
250257
Assert.IsFalse (peer.Disposed);
251258
Assert.AreEqual ("Android.Views.View+IOnClickListenerInvoker.class", peer.JniType);
252259
Assert.AreEqual ("typeof(Android.Views.View+IOnClickListenerInvoker)", peer.McwType);
253-
Assert.AreEqual (2, peer.Handles.Count);
254-
Assert.IsTrue (peer.Handles.Contains ("0x78b0001d"));
255-
Assert.IsTrue (peer.Handles.Contains ("0x100476"));
260+
Assert.AreEqual (1, peer.Handles.Count);
261+
Assert.IsTrue (peer.Handles [0] == "0x100476");
262+
Assert.AreEqual (1, peer.RemovedHandles.Count);
263+
Assert.IsTrue (peer.RemovedHandles [0] == "0x78b0001d/L");
256264

257265

258266
Assert.AreEqual (
@@ -289,9 +297,9 @@ public void JavaListClassRef ()
289297
Assert.IsFalse (peer.Finalized);
290298
Assert.AreEqual ("Android.Runtime.JavaList.class", peer.JniType);
291299
Assert.AreEqual ("typeof(Android.Runtime.JavaList)", peer.McwType);
292-
Assert.AreEqual (2, peer.Handles.Count);
293-
Assert.IsTrue (peer.Handles.Contains ("0x4a00009"));
294300
Assert.IsTrue (peer.Handles.Contains ("0x19004aa"));
301+
Assert.AreEqual (1, peer.RemovedHandles.Count);
302+
Assert.IsTrue (peer.RemovedHandles [0] == "0x4a00009/L");
295303

296304

297305
Assert.AreEqual (
@@ -356,10 +364,11 @@ public void TrackThreadInformation ()
356364
Assert.AreEqual ("'finalizer'(20660)", peer.DestroyedOnThread);
357365

358366
Assert.AreEqual ("0x39bcfcb7", peer.KeyHandle);
359-
Assert.AreEqual (3, peer.Handles.Count);
360-
Assert.IsTrue (peer.Handles.Contains ("0x9500001/L"));
361-
Assert.IsTrue (peer.Handles.Contains ("0x100492/G"));
362-
Assert.IsTrue (peer.Handles.Contains ("0x700003/W"));
367+
Assert.AreEqual (0, peer.Handles.Count);
368+
Assert.AreEqual (3, peer.RemovedHandles.Count);
369+
Assert.IsTrue (peer.RemovedHandles [0] == "0x9500001/L");
370+
Assert.IsTrue (peer.RemovedHandles [1] == "0x100492/G");
371+
Assert.IsTrue (peer.RemovedHandles [2] == "0x700003/W");
363372

364373
Assert.AreEqual (
365374
" at Android.Runtime.JNIEnv.NewGlobalRef(IntPtr jobject)\n" +
@@ -420,9 +429,10 @@ public void RepeatedThreadHandles ()
420429
Assert.AreEqual ("'(null)'(3)", peer.DestroyedOnThread);
421430

422431
Assert.AreEqual ("0x41e29778", peer.KeyHandle);
423-
Assert.AreEqual (2, peer.Handles.Count);
424-
Assert.IsTrue (peer.Handles.Contains ("0x4a00009/L"));
425-
Assert.IsTrue (peer.Handles.Contains ("0x19004aa/G"));
432+
Assert.AreEqual (0, peer.Handles.Count);
433+
Assert.AreEqual (2, peer.RemovedHandles.Count);
434+
Assert.IsTrue (peer.RemovedHandles [0] == "0x4a00009/L");
435+
Assert.IsTrue (peer.RemovedHandles [1] == "0x19004aa/G");
426436

427437
Assert.AreEqual (
428438
" at Doesn't Matter",
@@ -439,9 +449,10 @@ public void RepeatedThreadHandles ()
439449
Assert.AreEqual (null, peer.DestroyedOnThread);
440450

441451
Assert.AreEqual ("0x41e29778", peer.KeyHandle);
442-
Assert.AreEqual (2, peer.Handles.Count);
443-
Assert.IsTrue (peer.Handles.Contains ("0x4a00009/L"));
444-
Assert.IsTrue (peer.Handles.Contains ("0x19004aa/G"));
452+
Assert.AreEqual (1, peer.Handles.Count);
453+
Assert.IsTrue (peer.Handles [0] == "0x19004aa/G");
454+
Assert.AreEqual (1, peer.RemovedHandles.Count);
455+
Assert.IsTrue (peer.RemovedHandles [0] == "0x4a00009/L");
445456

446457
Assert.AreEqual (
447458
" at Doesn't Matter",

0 commit comments

Comments
 (0)