Skip to content

Commit 8f7d339

Browse files
committed
[Java.Interop, Java.Interop.Dynamic] Dispose() JniPeerMembers.
Running Java.Interop.Dynamic-Tests would result in lots of "Deleting JNI local reference handle <HANDLE> from wrong thread" messages written to the JNI local reference log. These were coming from all the various JniType instances involved ~everywhere: * JniPeerMembers.JniPeerType * JavaMethodInvokeInfo.ReturnType * JavaMethodInvokeInfo.Arguments * JavaMethodInvokeInfo.Method Plus associated Jni*FieldID and Jni*MethodID values (though those won't cause the above "Deleting..." messages.) There are two plausible fixes: 1. Stop using JNI Local References for everything! #6 JNI Handles would still need to be used, but by making them *Global* references the GC can freely clean them up. 2. Add an explicit cleanup mechanism. We're opting for (2) for now. :-) ((1) is later.) That said, (2) is usually done by implementing IDisposable, which we're doing for DynamicJavaClass. Why not have JniPeerMembers and company implement IDisposable? Because I don't want some asshole coming through and going: var o = new JavaObject(); o.JniPeerMembers.Dispose(); ...which would promptly blow up fucking everything. Instead, we're adding a *static* JniPeerMembers.Dispose() method, which won't show up in normal JniPeerMembers code completion, and will prevent using JniPeerMembers instances in `using` blocks. Once JniPeerMembers has a cleanup method, extend DynamicJavaClass to implement IDispoable. This allows our tests to cleanup after themselves, reducing spam to the LREF log.
1 parent 775b34f commit 8f7d339

File tree

7 files changed

+177
-23
lines changed

7 files changed

+177
-23
lines changed

src/Java.Interop.Dynamic/Java.Interop.Dynamic/DynamicJavaClass.cs

+62-9
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
namespace Java.Interop.Dynamic {
1515

16-
public class DynamicJavaClass : IDynamicMetaObjectProvider
16+
public class DynamicJavaClass : IDynamicMetaObjectProvider, IDisposable
1717
{
1818
readonly static Func<string, JniPeerMembers> CreatePeerMembers;
1919

@@ -67,6 +67,7 @@ static DynamicJavaClass ()
6767
public string JniClassName {get; private set;}
6868

6969
JniPeerMembers members;
70+
bool disposed;
7071

7172
Dictionary<string, HashSet<string>> StaticFields;
7273
Dictionary<string, List<JavaMethodInvokeInfo>> StaticMethods;
@@ -80,9 +81,35 @@ public DynamicJavaClass (string jniClassName)
8081
members = CreatePeerMembers (jniClassName);
8182
}
8283

84+
public void Dispose ()
85+
{
86+
Dispose (disposing: true);
87+
GC.SuppressFinalize (this);
88+
}
89+
90+
protected virtual void Dispose (bool disposing)
91+
{
92+
if (!disposing)
93+
return;
94+
95+
if (disposed)
96+
return;
97+
98+
foreach (var name in StaticMethods.Keys.ToList ()) {
99+
foreach (var info in StaticMethods [name])
100+
info.Dispose ();
101+
StaticMethods [name] = null;
102+
}
103+
104+
JniPeerMembers.Dispose (members);
105+
members = null;
106+
107+
disposed = true;
108+
}
109+
83110
void LookupMethods ()
84111
{
85-
if (StaticMethods != null)
112+
if (StaticMethods != null || disposed)
86113
return;
87114

88115
StaticMethods = new Dictionary<string, List<JavaMethodInvokeInfo>> ();
@@ -110,7 +137,7 @@ void LookupMethods ()
110137

111138
void LookupFields ()
112139
{
113-
if (StaticFields != null)
140+
if (StaticFields != null || disposed)
114141
return;
115142

116143
StaticFields = new Dictionary<string, HashSet<string>> ();
@@ -141,8 +168,6 @@ void LookupFields ()
141168

142169
DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject (Expression parameter)
143170
{
144-
if (members == null)
145-
throw new ObjectDisposedException (nameof (DynamicJavaClass));
146171
return new MetaObject (parameter, this);
147172
}
148173

@@ -185,6 +210,9 @@ static List<JniType> GetJniTypes (DynamicMetaObject[] args)
185210
try {
186211
var at = new JniType (vm.GetJniTypeInfoForType (a.LimitType).JniTypeReference);
187212
r.Add (at);
213+
} catch (JavaException e) {
214+
e.Dispose ();
215+
r.Add (null);
188216
} catch {
189217
r.Add (null);
190218
}
@@ -234,30 +262,43 @@ public override DynamicMetaObject BindGetMember (GetMemberBinder binder)
234262
if (overloads == null)
235263
return binder.FallbackGetMember (this);
236264

265+
if (Value.disposed) {
266+
return new DynamicMetaObject (ThrowObjectDisposedException (typeof (object)), BindingRestrictions.GetInstanceRestriction (Expression, Value));
267+
}
268+
237269
Func<string, object> getValue = Value.members.StaticFields.GetValue;
238270

239271
var e = Expression.Call (Expression.Constant (Value.members.StaticFields), getValue.Method, Expression.Constant (overloads.First ()));
240272
Debug.WriteLine ("# MetaObject.BindGetMember: e={0}", e.ToCSharpCode ());
241273
return new DynamicMetaObject (e, BindingRestrictions.GetInstanceRestriction (Expression, Value));
242274
}
243275

276+
static Expression ThrowObjectDisposedException (Type type = null)
277+
{
278+
return Expression.Throw (Expression.Constant (new ObjectDisposedException (nameof (DynamicJavaClass))), type);
279+
}
280+
244281
HashSet<string> GetField (string name)
245282
{
246283
Value.LookupFields ();
247284

248285
HashSet<string> overloads;
249-
if (Value.StaticFields.TryGetValue (name, out overloads))
286+
if (Value.StaticFields != null && Value.StaticFields.TryGetValue (name, out overloads))
250287
return overloads;
251288
return null;
252289
}
253290

254291
public override DynamicMetaObject BindInvokeMember (InvokeMemberBinder binder, DynamicMetaObject[] args)
255292
{
256293
Value.LookupMethods ();
257-
List<JavaMethodInvokeInfo> overloads;
258-
if (!Value.StaticMethods.TryGetValue (binder.Name, out overloads))
294+
List<JavaMethodInvokeInfo> overloads = null;
295+
if (Value.StaticMethods != null && !Value.StaticMethods.TryGetValue (binder.Name, out overloads))
259296
return binder.FallbackInvokeMember (this, args);
260297

298+
if (Value.disposed) {
299+
return new DynamicMetaObject (ThrowObjectDisposedException (typeof (object)), BindingRestrictions.GetInstanceRestriction (Expression, Value));
300+
}
301+
261302
foreach (var m in overloads)
262303
m.LookupArguments ();
263304

@@ -284,6 +325,10 @@ public override DynamicMetaObject BindSetMember (SetMemberBinder binder, Dynamic
284325
if (overloads == null)
285326
return binder.FallbackSetMember (this, value);
286327

328+
if (Value.disposed) {
329+
return new DynamicMetaObject (ThrowObjectDisposedException (), BindingRestrictions.GetInstanceRestriction (Expression, Value));
330+
}
331+
287332
Action<string, object> setValue = Value.members.StaticFields.SetValue;
288333
var e = Expression.Block (
289334
Expression.Call (Expression.Constant (Value.members.StaticFields), setValue.Method,
@@ -333,8 +378,16 @@ public void Dispose ()
333378
Method.Dispose ();
334379
ReturnType.Dispose ();
335380
ReturnType = null;
336-
for (int i = 0; i < Arguments.Count; ++i)
381+
382+
if (Arguments == null)
383+
return;
384+
385+
for (int i = 0; i < Arguments.Count; ++i) {
386+
if (Arguments [i] == null)
387+
continue;
337388
Arguments [i].Dispose ();
389+
Arguments [i] = null;
390+
}
338391
Arguments = null;
339392
}
340393

src/Java.Interop.Dynamic/Tests/Java.Interop.Dynamic/DynamicJavaClassTests.cs

+21
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,27 @@ public void Constructor ()
4040
Assert.Throws<ArgumentNullException> (() => new DynamicJavaClass (null));
4141
}
4242

43+
[Test]
44+
public void DisposedInstanceThrowsObjectDisposedException ()
45+
{
46+
dynamic Integer = new DynamicJavaClass (Integer_class);
47+
Integer.Dispose ();
48+
Integer.Dispose (); // Dispose() is idempotent
49+
Assert.Catch<Exception> (() => Integer.bitCount (2));
50+
Assert.Catch<Exception> (() => {
51+
int max = Integer.MAX_INT;
52+
});
53+
Assert.Catch<Exception> (() => {
54+
Integer.MAX_INT = 42;
55+
});
56+
}
57+
4358
[Test]
4459
public void JniClassName ()
4560
{
4661
dynamic Arrays = new DynamicJavaClass (Arrays_class);
4762
Assert.AreEqual (Arrays_class, Arrays.JniClassName);
63+
Arrays.Dispose ();
4864
}
4965

5066
[Test]
@@ -55,6 +71,7 @@ public void CallStaticMethod ()
5571
int value = 3;
5672
int index = Arrays.binarySearch (array, value);
5773
Assert.AreEqual (2, index);
74+
Arrays.Dispose ();
5875
}
5976

6077
[Test]
@@ -63,6 +80,7 @@ public void ReadStaticMember ()
6380
dynamic Integer = new DynamicJavaClass (Integer_class);
6481
int max = Integer.MAX_VALUE;
6582
Assert.AreEqual (int.MaxValue, max);
83+
Integer.Dispose ();
6684
}
6785

6886
[Test]
@@ -77,6 +95,7 @@ public void WriteStaticMember ()
7795
Assert.AreEqual (42, max);
7896
Integer.MAX_VALUE = cur;
7997
Console.WriteLine ("# done!");
98+
Integer.Dispose ();
8099
}
81100

82101
[Test]
@@ -87,6 +106,7 @@ public void FallbackPropertySet ()
87106
d.MyProperty = 42;
88107
int v = d.MyProperty;
89108
Assert.AreEqual (42, v);
109+
d.Dispose ();
90110
}
91111

92112
[Test]
@@ -95,6 +115,7 @@ public void FallbackInvokeMember ()
95115
dynamic d = new MyDynamicObject ();
96116
int v = d.Method ("foo");
97117
Assert.AreEqual (3, v);
118+
d.Dispose ();
98119
}
99120
}
100121
}

src/Java.Interop/Java.Interop/JniPeerInstanceFields.cs

+12-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,18 @@ internal JniPeerInstanceFields (JniPeerMembers members)
1111
}
1212

1313
readonly JniPeerMembers Members;
14-
readonly Dictionary<string, JniInstanceFieldID> InstanceFields = new Dictionary<string, JniInstanceFieldID>();
14+
15+
Dictionary<string, JniInstanceFieldID> InstanceFields = new Dictionary<string, JniInstanceFieldID>();
16+
17+
internal void Dispose ()
18+
{
19+
if (InstanceFields == null)
20+
return;
21+
22+
foreach (var f in InstanceFields.Values)
23+
f.Dispose ();
24+
InstanceFields = null;
25+
}
1526

1627
public JniInstanceFieldID GetFieldID (string encodedMember)
1728
{

src/Java.Interop/Java.Interop/JniPeerInstanceMethods.cs

+21-3
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,27 @@ internal JniPeerInstanceMethods (JniPeerMembers members)
2626
}
2727

2828
readonly Type DeclaringType;
29-
readonly JniType JniPeerType;
30-
readonly Dictionary<string, JniInstanceMethodID> InstanceMethods = new Dictionary<string, JniInstanceMethodID>();
31-
readonly Dictionary<Type, JniPeerInstanceMethods> SubclassConstructors = new Dictionary<Type, JniPeerInstanceMethods> ();
29+
30+
JniType JniPeerType;
31+
Dictionary<string, JniInstanceMethodID> InstanceMethods = new Dictionary<string, JniInstanceMethodID>();
32+
Dictionary<Type, JniPeerInstanceMethods> SubclassConstructors = new Dictionary<Type, JniPeerInstanceMethods> ();
33+
34+
internal void Dispose ()
35+
{
36+
if (JniPeerType == null)
37+
return;
38+
39+
// Don't dispose JniPeerType; it's shared with others.
40+
foreach (var m in InstanceMethods.Values)
41+
m.Dispose ();
42+
InstanceMethods = null;
43+
44+
foreach (var p in SubclassConstructors.Values)
45+
p.Dispose ();
46+
SubclassConstructors = null;
47+
48+
JniPeerType = null;
49+
}
3250

3351
public JniInstanceMethodID GetConstructor (string signature)
3452
{

src/Java.Interop/Java.Interop/JniPeerMembers.cs

+37-8
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ static JniPeerMembers CreatePeerMembers (string jniPeerType)
4343

4444
JniType jniPeerType;
4545

46-
readonly JniPeerInstanceMethods instanceMethods;
47-
readonly JniPeerInstanceFields instanceFields;
48-
readonly JniPeerStaticMethods staticMethods;
49-
readonly JniPeerStaticFields staticFields;
46+
JniPeerInstanceMethods instanceMethods;
47+
JniPeerInstanceFields instanceFields;
48+
JniPeerStaticMethods staticMethods;
49+
JniPeerStaticFields staticFields;
5050

5151
public Type ManagedPeerType {get; private set;}
5252
public string JniPeerTypeName {get; private set;}
@@ -59,19 +59,48 @@ public JniType JniPeerType {
5959
}
6060

6161
public JniPeerInstanceMethods InstanceMethods {
62-
get {return instanceMethods;}
62+
get {return Assert (instanceMethods);}
6363
}
6464

6565
public JniPeerInstanceFields InstanceFields {
66-
get {return instanceFields;}
66+
get {return Assert (instanceFields);}
6767
}
6868

6969
public JniPeerStaticMethods StaticMethods {
70-
get {return staticMethods;}
70+
get {return Assert (staticMethods);}
7171
}
7272

7373
public JniPeerStaticFields StaticFields {
74-
get {return staticFields;}
74+
get {return Assert (staticFields);}
75+
}
76+
77+
static T Assert<T>(T value)
78+
where T : class
79+
{
80+
if (value == null)
81+
throw new ObjectDisposedException (nameof (JniPeerMembers));
82+
return value;
83+
}
84+
85+
public static void Dispose (JniPeerMembers members)
86+
{
87+
if (members.jniPeerType == null)
88+
return;
89+
90+
members.instanceMethods.Dispose ();
91+
members.instanceMethods = null;
92+
93+
members.instanceFields.Dispose ();
94+
members.instanceFields = null;
95+
96+
members.staticMethods.Dispose ();
97+
members.staticMethods = null;
98+
99+
members.staticFields.Dispose ();
100+
members.staticFields = null;
101+
102+
members.jniPeerType.Dispose ();
103+
members.jniPeerType = null;
75104
}
76105

77106
internal static void AssertSelf (IJavaObject self)

src/Java.Interop/Java.Interop/JniPeerStaticFields.cs

+12-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ internal JniPeerStaticFields (JniPeerMembers members)
1111
}
1212

1313
readonly JniPeerMembers Members;
14-
readonly Dictionary<string, JniStaticFieldID> StaticFields = new Dictionary<string, JniStaticFieldID>();
14+
15+
Dictionary<string, JniStaticFieldID> StaticFields = new Dictionary<string, JniStaticFieldID>();
1516

1617
public JniStaticFieldID GetFieldID (string encodedMember)
1718
{
@@ -27,6 +28,16 @@ public JniStaticFieldID GetFieldID (string encodedMember)
2728
}
2829
}
2930

31+
internal void Dispose ()
32+
{
33+
if (StaticFields == null)
34+
return;
35+
36+
foreach (var f in StaticFields.Values)
37+
f.Dispose ();
38+
StaticFields = null;
39+
}
40+
3041
public object GetValue (string encodedMember)
3142
{
3243
var n = JniPeerMembers.GetSignatureSeparatorIndex (encodedMember);

src/Java.Interop/Java.Interop/JniPeerStaticMethods.cs

+12-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,18 @@ internal JniPeerStaticMethods (JniPeerMembers members)
1111
}
1212

1313
readonly JniPeerMembers Members;
14-
readonly Dictionary<string, JniStaticMethodID> StaticMethods = new Dictionary<string, JniStaticMethodID>();
14+
15+
Dictionary<string, JniStaticMethodID> StaticMethods = new Dictionary<string, JniStaticMethodID>();
16+
17+
internal void Dispose ()
18+
{
19+
if (StaticMethods == null)
20+
return;
21+
22+
foreach (var m in StaticMethods.Values)
23+
m.Dispose ();
24+
StaticMethods = null;
25+
}
1526

1627
public JniStaticMethodID GetMethodID (string encodedMember)
1728
{

0 commit comments

Comments
 (0)