diff --git a/pkgs/jni/CHANGELOG.md b/pkgs/jni/CHANGELOG.md index b49ed8c78b..44a88a3f23 100644 --- a/pkgs/jni/CHANGELOG.md +++ b/pkgs/jni/CHANGELOG.md @@ -4,6 +4,9 @@ and bootstrap jars [#2003](https://github.com/dart-lang/native/issues/2003) - Added `JObject.isInstanceOf` which checks whether a `JObject` is an instance of a java class. +- Fixed a [bug](https://github.com/dart-lang/native/issues/1908) where + Java interfaces implemented in on the main thread in Dart could deadlock when + invoked from the main thread outside the context of a Dart isolate. ## 0.14.0 diff --git a/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortProxyBuilder.java b/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortProxyBuilder.java index 178d25115b..3622a5c489 100644 --- a/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortProxyBuilder.java +++ b/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortProxyBuilder.java @@ -38,11 +38,26 @@ private static final class DartImplementation { private boolean built = false; private final long isolateId; + private final boolean constructedOnMainThread; private final HashMap implementations = new HashMap<>(); private final HashSet asyncMethods = new HashSet<>(); + private static boolean isOnMainThread() { + try { + Class looper = Class.forName("android.os.Looper"); + Method getMainLooper = looper.getMethod("getMainLooper"); + Method getThread = looper.getMethod("getThread"); + Thread mainThread = (Thread) getThread.invoke(getMainLooper.invoke(null)); + return mainThread == Thread.currentThread(); + } catch (Exception e) { + // Not on Android, so there is no concept of a "main" thread. + return false; + } + } + public PortProxyBuilder(long isolateId) { this.isolateId = isolateId; + this.constructedOnMainThread = isOnMainThread(); } private static String getDescriptor(Method method) { @@ -121,7 +136,8 @@ private static native Object[] _invoke( Object proxy, String methodDescriptor, Object[] args, - boolean isBlocking); + boolean isBlocking, + boolean mayEnterIsolate); private static native void _cleanUp(long resultPtr); @@ -139,6 +155,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl DartImplementation implementation = implementations.get(method.getDeclaringClass().getName()); String descriptor = getDescriptor(method); boolean isBlocking = !asyncMethods.contains(descriptor); + boolean mayEnterIsolate = isOnMainThread() && constructedOnMainThread; Object[] result = _invoke( implementation.port, @@ -147,7 +164,8 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl proxy, descriptor, args, - isBlocking); + isBlocking, + mayEnterIsolate); if (!isBlocking) { return null; } diff --git a/pkgs/jni/src/dartjni.c b/pkgs/jni/src/dartjni.c index b23dcba8a8..28bfabc97c 100644 --- a/pkgs/jni/src/dartjni.c +++ b/pkgs/jni/src/dartjni.c @@ -419,12 +419,14 @@ Java_com_github_dart_1lang_jni_PortProxyBuilder__1invoke( jobject proxy, jstring methodDescriptor, jobjectArray args, - jboolean isBlocking) { + jboolean isBlocking, + jboolean mayEnterIsolate) { CallbackResult* result = NULL; if (isBlocking) { result = (CallbackResult*)malloc(sizeof(CallbackResult)); } - if (isolateId != (jlong)Dart_CurrentIsolate_DL() || !isBlocking) { + if ((isolateId != (jlong)Dart_CurrentIsolate_DL() && !mayEnterIsolate) || + !isBlocking) { if (isBlocking) { init_lock(&result->lock); init_cond(&result->cond); @@ -464,9 +466,23 @@ Java_com_github_dart_1lang_jni_PortProxyBuilder__1invoke( destroy_cond(&result->cond); } } else { + // Flutter-specific: `mayEnterIsolate` is `true` when the proxy was + // constructed on the main thread and is being invoked on the main thread. + // + // When the current isolate is `null`, enter the main isolate that is pinned + // to the main thread first before invoking the `functionPtr`. + assert(Dart_CurrentIsolate_DL() == NULL || + Dart_CurrentIsolate_DL() == (Dart_Isolate)isolateId); + bool mustEnterIsolate = Dart_CurrentIsolate_DL() == NULL && mayEnterIsolate; + if (mustEnterIsolate) { + Dart_EnterIsolate_DL((Dart_Isolate)isolateId); + } result->object = ((jobject(*)(uint64_t, jobject, jobject))functionPtr)( port, (*env)->NewGlobalRef(env, methodDescriptor), (*env)->NewGlobalRef(env, args)); + if (mustEnterIsolate) { + Dart_ExitIsolate_DL(); + } } if (!isBlocking) { // No result is created in this case, there is nothing to clean up either.