Skip to content

Commit c8d3e08

Browse files
committed
[Java.Interop] JavaObject: GREFs by default, not LREFs.
Fixes: #6 Context: https://trello.com/c/EXxOMZOD/397-add-java-lang-object-unregisterfromvm After presenting parts of the Java.Interop design to various other Xamarin employees at the 2014 Xummit, I got lots of pushback about the idea of not registering JavaObject instances by default and using JNI Local References by default. The problem/fear: it would be a usability nightmare. Single-threaded use kinda/sorta works...if you never leave the method. If another thread tries to access your JavaObject instance, you blow up. If you call Java code which calls back into managed code tries to grab that instance, you may blow up, as the value wasn't registered. It's not good. Then there's the future Android side of things: Android only permits 512 JNI local references; allocate more, and you abort. That's *really* not good. Partially revert commit 1caf077: don't use JNI Local References, use JNI Global References, *always*. (This fixes Android.) Additionally, register JavaObject and JavaException instances by default. Allow them to be *unregistered* via the new IJavaPeerable.UnregisterWithRuntime() method, *or* the new JniObjectReferenceOptions.DoNotRegisterWithRuntime enum value. The former will remove an existing registration; the latter will prevent it from being registered AT ALL [0]. [0]: This is a lie: we'll register the instance for the duration of the constructor, the unregister it automatically [1]. [1]: I'm not sure if this is a good idea or not. On the one time, we *need* the registration within the constructor to support Java-side invocation of virtual members: the instance MUST be registered so that we lookup the appropriate instance for virtual method dispatch at a later point in time. However, it also means that it *is* being registered, if only for a short period, and thus when sharing an instance from multiple threads, each trying to register/unregister, things may fail badly; see e.g. [2, 3] This idea requires further consideration; yet another reason JavaObject isn't ready for Xamarin.Android integration. [2]: https://bugzilla.xamarin.com/show_bug.cgi?id=14999 [3]: https://github.com/xamarin/monodroid/commit/813864948e7316cdbbf0bf439380322bd727f8f8
1 parent 0953980 commit c8d3e08

File tree

17 files changed

+48
-81
lines changed

17 files changed

+48
-81
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ public void Constructor ()
2626
public void DisposeWithJavaObjectDisposesObject ([Values (true, false)] bool register)
2727
{
2828
var native = new JavaObject ();
29-
if (register) {
30-
native.RegisterWithVM ();
29+
if (!register) {
30+
native.UnregisterFromRuntime ();
3131
}
3232

3333
var instance = new DynamicJavaInstance (native);

src/Java.Interop.Export/Tests/Java.Interop/ExportedMemberBuilderTest.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ public void AddExportMethods ()
3939
Assert.IsTrue (ExportTest.StaticActionInt32StringCalled);
4040

4141
using (var o = CreateExportTest (t)) {
42-
o.RegisterWithVM ();
4342
t.GetInstanceMethod ("testMethods", "()V").InvokeVirtualVoidMethod (o.PeerReference);
4443
Assert.IsTrue (o.HelloCalled);
4544
o.Dispose ();

src/Java.Interop/Java.Interop/IJavaPeerable.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public interface IJavaPeerable : IDisposable
77
JniObjectReference PeerReference {get;}
88
JniPeerMembers JniPeerMembers {get;}
99

10-
void RegisterWithVM ();
10+
void UnregisterFromRuntime ();
1111
void DisposeUnlessRegistered ();
1212
}
1313
}

src/Java.Interop/Java.Interop/JavaException.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,11 @@ protected SetSafeHandleCompletion SetPeerReference (ref JniObjectReference handl
130130
a => new SetSafeHandleCompletion (a));
131131
}
132132

133-
public void RegisterWithVM ()
133+
public void UnregisterFromRuntime ()
134134
{
135-
JniEnvironment.Runtime.RegisterObject (this);
135+
if (!PeerReference.IsValid)
136+
throw new ObjectDisposedException (GetType ().FullName);
137+
JniEnvironment.Runtime.UnRegisterObject (this);
136138
}
137139

138140
public void Dispose ()

src/Java.Interop/Java.Interop/JavaObject.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,11 @@ protected SetPeerReferenceCompletion SetPeerReference (ref JniObjectReference ha
7171
a => new SetPeerReferenceCompletion (a));
7272
}
7373

74-
public void RegisterWithVM ()
74+
public void UnregisterFromRuntime ()
7575
{
76-
JniEnvironment.Runtime.RegisterObject (this);
76+
if (!PeerReference.IsValid)
77+
throw new ObjectDisposedException (GetType ().FullName);
78+
JniEnvironment.Runtime.UnRegisterObject (this);
7779
}
7880

7981
public void Dispose ()

src/Java.Interop/Java.Interop/JavaProxyObject.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ public static JavaProxyObject GetProxy (object value)
6262
if (CachedValues.TryGetValue (value, out proxy))
6363
return proxy;
6464
proxy = new JavaProxyObject (value);
65-
proxy.RegisterWithVM ();
6665
CachedValues.Add (value, proxy);
6766
return proxy;
6867
}

src/Java.Interop/Java.Interop/JniEnvironment.Errors.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ public static void Throw (Exception e)
1212
var je = e as JavaException;
1313
if (je == null) {
1414
je = new JavaProxyThrowable (e);
15-
// because `je` may cross thread boundaries;
16-
// We'll need to rely on the GC to cleanup
17-
je.RegisterWithVM ();
1815
}
1916
Throw (je.PeerReference);
2017
}

src/Java.Interop/Java.Interop/JniEnvironment.References.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ public static void Dispose (ref JniObjectReference reference, JniObjectReference
3838
}
3939
reference.Invalidate ();
4040
break;
41-
default:
42-
throw new NotImplementedException ("Do not know how to transfer: " + transfer);
4341
}
4442
}
4543

src/Java.Interop/Java.Interop/JniObjectReferenceOptions.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ public enum JniObjectReferenceOptions
88
Invalid = 0,
99
CreateNewReference = 1 << 0, // DoNotTransfer
1010
DisposeSourceReference = 1 << 1, // Transfer
11-
12-
/*
13-
Unregistered = 4,
14-
*/
11+
DoNotRegisterWithRuntime = 1 << 2,
1512
}
1613
}
1714

src/Java.Interop/Java.Interop/JniRuntime.cs

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,8 @@ internal void RegisterObject<T> (T value)
327327
value.Registered = true;
328328
}
329329

330-
internal void UnRegisterObject (IJavaPeerableEx value)
330+
internal void UnRegisterObject<T> (T value)
331+
where T : IJavaPeerable, IJavaPeerableEx
331332
{
332333
int key = value.IdentityHashCode;
333334
lock (RegisteredInstances) {
@@ -341,27 +342,23 @@ internal void UnRegisterObject (IJavaPeerableEx value)
341342
}
342343
}
343344

344-
internal TCleanup SetObjectPeerReference<T, TCleanup> (T value, ref JniObjectReference reference, JniObjectReferenceOptions transfer, Func<Action, TCleanup> createCleanup)
345+
internal TCleanup SetObjectPeerReference<T, TCleanup> (T value, ref JniObjectReference reference, JniObjectReferenceOptions options, Func<Action, TCleanup> createCleanup)
345346
where T : IJavaPeerable, IJavaPeerableEx
346347
where TCleanup : IDisposable
347348
{
348349
if (!reference.IsValid)
349350
throw new ArgumentException ("handle is invalid.", nameof (reference));
350351

351-
bool register = reference.Flags == JniObjectReferenceFlags.Alloc;
352-
353-
value.SetPeerReference (reference.NewLocalRef ());
354-
JniEnvironment.References.Dispose (ref reference, transfer);
352+
var newRef = reference.NewGlobalRef ();
353+
value.SetPeerReference (newRef);
354+
JniEnvironment.References.Dispose (ref reference, options);
355355

356-
value.IdentityHashCode = JniSystem.IdentityHashCode (value.PeerReference);
356+
value.IdentityHashCode = JniSystem.IdentityHashCode (newRef);
357357

358-
if (register) {
359-
RegisterObject (value);
358+
RegisterObject (value);
359+
if ((options & JniObjectReferenceOptions.DoNotRegisterWithRuntime) == JniObjectReferenceOptions.DoNotRegisterWithRuntime) {
360360
Action unregister = () => {
361361
UnRegisterObject (value);
362-
var o = value.PeerReference;
363-
value.SetPeerReference (o.NewLocalRef ());
364-
JniEnvironment.References.Dispose (ref o);
365362
};
366363
return createCleanup (unregister);
367364
}
@@ -461,7 +458,7 @@ public IJavaPeerable GetObject (ref JniObjectReference reference, JniObjectRefer
461458
return null;
462459

463460
var existing = PeekObject (reference);
464-
if (existing != null && targetType != null && targetType.IsInstanceOfType (existing)) {
461+
if (existing != null && (targetType == null || targetType.IsInstanceOfType (existing))) {
465462
JniEnvironment.References.Dispose (ref reference, transfer);
466463
return existing;
467464
}
@@ -742,9 +739,6 @@ public virtual void RaisePendingException (Exception pendingException)
742739
var je = pendingException as JavaException;
743740
if (je == null) {
744741
je = new JavaProxyThrowable (pendingException);
745-
// because `je` may cross thread boundaries;
746-
// We'll need to rely on the GC to cleanup
747-
je.RegisterWithVM ();
748742
}
749743
JniEnvironment.Exceptions.Throw (je.PeerReference);
750744
#endif // !XA_INTEGRATION

0 commit comments

Comments
 (0)