Skip to content

Commit cde3a28

Browse files
[jni] Fix interface implementation deadlock on the main thread (#2032)
1 parent f086776 commit cde3a28

File tree

3 files changed

+41
-4
lines changed

3 files changed

+41
-4
lines changed

pkgs/jni/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
and bootstrap jars [#2003](https://github.com/dart-lang/native/issues/2003)
55
- Added `JObject.isInstanceOf` which checks whether a `JObject` is an instance
66
of a java class.
7+
- Fixed a [bug](https://github.com/dart-lang/native/issues/1908) where
8+
Java interfaces implemented in on the main thread in Dart could deadlock when
9+
invoked from the main thread outside the context of a Dart isolate.
710

811
## 0.14.0
912

pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortProxyBuilder.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,26 @@ private static final class DartImplementation {
3838

3939
private boolean built = false;
4040
private final long isolateId;
41+
private final boolean constructedOnMainThread;
4142
private final HashMap<String, DartImplementation> implementations = new HashMap<>();
4243
private final HashSet<String> asyncMethods = new HashSet<>();
4344

45+
private static boolean isOnMainThread() {
46+
try {
47+
Class<?> looper = Class.forName("android.os.Looper");
48+
Method getMainLooper = looper.getMethod("getMainLooper");
49+
Method getThread = looper.getMethod("getThread");
50+
Thread mainThread = (Thread) getThread.invoke(getMainLooper.invoke(null));
51+
return mainThread == Thread.currentThread();
52+
} catch (Exception e) {
53+
// Not on Android, so there is no concept of a "main" thread.
54+
return false;
55+
}
56+
}
57+
4458
public PortProxyBuilder(long isolateId) {
4559
this.isolateId = isolateId;
60+
this.constructedOnMainThread = isOnMainThread();
4661
}
4762

4863
private static String getDescriptor(Method method) {
@@ -121,7 +136,8 @@ private static native Object[] _invoke(
121136
Object proxy,
122137
String methodDescriptor,
123138
Object[] args,
124-
boolean isBlocking);
139+
boolean isBlocking,
140+
boolean mayEnterIsolate);
125141

126142
private static native void _cleanUp(long resultPtr);
127143

@@ -139,6 +155,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
139155
DartImplementation implementation = implementations.get(method.getDeclaringClass().getName());
140156
String descriptor = getDescriptor(method);
141157
boolean isBlocking = !asyncMethods.contains(descriptor);
158+
boolean mayEnterIsolate = isOnMainThread() && constructedOnMainThread;
142159
Object[] result =
143160
_invoke(
144161
implementation.port,
@@ -147,7 +164,8 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
147164
proxy,
148165
descriptor,
149166
args,
150-
isBlocking);
167+
isBlocking,
168+
mayEnterIsolate);
151169
if (!isBlocking) {
152170
return null;
153171
}

pkgs/jni/src/dartjni.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,12 +419,14 @@ Java_com_github_dart_1lang_jni_PortProxyBuilder__1invoke(
419419
jobject proxy,
420420
jstring methodDescriptor,
421421
jobjectArray args,
422-
jboolean isBlocking) {
422+
jboolean isBlocking,
423+
jboolean mayEnterIsolate) {
423424
CallbackResult* result = NULL;
424425
if (isBlocking) {
425426
result = (CallbackResult*)malloc(sizeof(CallbackResult));
426427
}
427-
if (isolateId != (jlong)Dart_CurrentIsolate_DL() || !isBlocking) {
428+
if ((isolateId != (jlong)Dart_CurrentIsolate_DL() && !mayEnterIsolate) ||
429+
!isBlocking) {
428430
if (isBlocking) {
429431
init_lock(&result->lock);
430432
init_cond(&result->cond);
@@ -464,9 +466,23 @@ Java_com_github_dart_1lang_jni_PortProxyBuilder__1invoke(
464466
destroy_cond(&result->cond);
465467
}
466468
} else {
469+
// Flutter-specific: `mayEnterIsolate` is `true` when the proxy was
470+
// constructed on the main thread and is being invoked on the main thread.
471+
//
472+
// When the current isolate is `null`, enter the main isolate that is pinned
473+
// to the main thread first before invoking the `functionPtr`.
474+
assert(Dart_CurrentIsolate_DL() == NULL ||
475+
Dart_CurrentIsolate_DL() == (Dart_Isolate)isolateId);
476+
bool mustEnterIsolate = Dart_CurrentIsolate_DL() == NULL && mayEnterIsolate;
477+
if (mustEnterIsolate) {
478+
Dart_EnterIsolate_DL((Dart_Isolate)isolateId);
479+
}
467480
result->object = ((jobject(*)(uint64_t, jobject, jobject))functionPtr)(
468481
port, (*env)->NewGlobalRef(env, methodDescriptor),
469482
(*env)->NewGlobalRef(env, args));
483+
if (mustEnterIsolate) {
484+
Dart_ExitIsolate_DL();
485+
}
470486
}
471487
if (!isBlocking) {
472488
// No result is created in this case, there is nothing to clean up either.

0 commit comments

Comments
 (0)