Skip to content

Commit

Permalink
[jnigen] Finalize interfaces in Java (dart-archive/jnigen#369)
Browse files Browse the repository at this point in the history
  • Loading branch information
HosseinYousefi authored Aug 24, 2023
1 parent 4f85ced commit 95c8375
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 87 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

package com.github.dart_lang.jni;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

/// A registry of Java objects with associated Dart resources that cleans up the
/// resources after they get unreachable and collected by the garbage collector.
///
/// A simple alternative to [java.lang.ref.Cleaner] which is only available in
/// Android API level 33+.
class PortCleaner {
static {
System.loadLibrary("dartjni");
}

private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
private final PortPhantom list = new PortPhantom();

private class PortPhantom extends PhantomReference<Object> {
final long port;

/// Form a linked list.
PortPhantom prev = this, next = this;

PortPhantom(Object referent, long port) {
super(referent, queue);
this.port = port;
insert();
}

/// Only used for the head of the list.
PortPhantom() {
super(null, null);
this.port = 0;
}

void insert() {
synchronized (list) {
prev = list;
next = list.next;
next.prev = this;
list.next = this;
}
}

private void remove() {
synchronized (list) {
next.prev = prev;
prev.next = next;
prev = this;
next = this;
}
}
}

PortCleaner() {
// Only a single PortCleaner and therefore thread will be created.
Thread thread =
new Thread(
() -> {
while (true) {
try {
PortPhantom portPhantom = (PortPhantom) queue.remove();
portPhantom.remove();
if (portPhantom.port != 0) {
clean(portPhantom.port);
}
} catch (Throwable e) {
// Ignore.
}
}
},
"PortCleaner");
thread.setDaemon(true);
thread.start();
}

/// Registers [obj] to be cleaned up later by sending a signal through [port].
void register(Object obj, long port) {
new PortPhantom(obj, port);
}

private static native void clean(long port);
}
38 changes: 22 additions & 16 deletions pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class PortProxy implements InvocationHandler {
System.loadLibrary("dartjni");
}

private static final PortCleaner cleaner = new PortCleaner();
private final long port;
private final long isolateId;
private final long functionPtr;
Expand All @@ -23,48 +24,53 @@ private PortProxy(long port, long isolateId, long functionPtr) {

private static String getDescriptor(Method method) {
StringBuilder descriptor = new StringBuilder();
descriptor.append(method.getName()).append("(");
descriptor.append(method.getName()).append('(');
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> paramType : parameterTypes) {
appendType(descriptor, paramType);
}
descriptor.append(")");
descriptor.append(')');
appendType(descriptor, method.getReturnType());
return descriptor.toString();
}

private static void appendType(StringBuilder descriptor, Class<?> type) {
if (type == void.class) {
descriptor.append("V");
descriptor.append('V');
} else if (type == boolean.class) {
descriptor.append("Z");
descriptor.append('Z');
} else if (type == byte.class) {
descriptor.append("B");
descriptor.append('B');
} else if (type == char.class) {
descriptor.append("C");
descriptor.append('C');
} else if (type == short.class) {
descriptor.append("S");
descriptor.append('S');
} else if (type == int.class) {
descriptor.append("I");
descriptor.append('I');
} else if (type == long.class) {
descriptor.append("J");
descriptor.append('J');
} else if (type == float.class) {
descriptor.append("F");
descriptor.append('F');
} else if (type == double.class) {
descriptor.append("D");
descriptor.append('D');
} else if (type.isArray()) {
descriptor.append('[');
appendType(descriptor, type.getComponentType());
} else {
descriptor.append("L").append(type.getName().replace('.', '/')).append(";");
descriptor.append('L').append(type.getName().replace('.', '/')).append(';');
}
}

public static Object newInstance(String binaryName, long port, long isolateId, long functionPtr)
throws ClassNotFoundException {
Class<?> clazz = Class.forName(binaryName);
return Proxy.newProxyInstance(
clazz.getClassLoader(), new Class[] {clazz}, new PortProxy(port, isolateId, functionPtr));
Object obj =
Proxy.newProxyInstance(
clazz.getClassLoader(),
new Class[] {clazz},
new PortProxy(port, isolateId, functionPtr));
cleaner.register(obj, port);
return obj;
}

@Override
Expand All @@ -77,13 +83,13 @@ public Object invoke(Object proxy, Method method, Object[] args) {
/// Returns an array with two objects:
/// [0]: The address of the result pointer used for the clean-up.
/// [1]: The result of the invocation.
private native Object[] _invoke(
private static native Object[] _invoke(
long port,
long isolateId,
long functionPtr,
Object proxy,
String methodDescriptor,
Object[] args);

private native void _cleanUp(long resultPtr);
private static native void _cleanUp(long resultPtr);
}
15 changes: 12 additions & 3 deletions pkgs/jni/src/dartjni.c
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data) {

JNIEXPORT void JNICALL
Java_com_github_dart_1lang_jni_PortContinuation__1resumeWith(JNIEnv* env,
jobject thiz,
jclass clazz,
jlong port,
jobject result) {
attach_thread();
Expand Down Expand Up @@ -643,7 +643,7 @@ jmethodID _m_Long_init = NULL;

JNIEXPORT jobjectArray JNICALL
Java_com_github_dart_1lang_jni_PortProxy__1invoke(JNIEnv* env,
jobject thiz,
jclass clazz,
jlong port,
jlong isolateId,
jlong functionPtr,
Expand Down Expand Up @@ -709,9 +709,18 @@ Java_com_github_dart_1lang_jni_PortProxy__1invoke(JNIEnv* env,

JNIEXPORT void JNICALL
Java_com_github_dart_1lang_jni_PortProxy__1cleanUp(JNIEnv* env,
jobject thiz,
jclass clazz,
jlong resultPtr) {
CallbackResult* result = (CallbackResult*)resultPtr;
(*env)->DeleteGlobalRef(env, result->object);
free(result);
}

JNIEXPORT void JNICALL
Java_com_github_dart_1lang_jni_PortCleaner_clean(JNIEnv* env,
jclass clazz,
jlong port) {
Dart_CObject close_signal;
close_signal.type = Dart_CObject_kNull;
Dart_PostCObject_DL(port, &close_signal);
}
59 changes: 28 additions & 31 deletions pkgs/jnigen/lib/src/bindings/dart_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -410,30 +410,18 @@ class $name$typeParamsDef extends $superName {
s.write('''
/// Maps a specific port to the implemented interface.
static final Map<int, $implClassName> _\$impls = {};
''');
s.write(r'''
ReceivePort? _$p;
ReceivePort? _\$p;
static final Finalizer<ReceivePort> _\$finalizer = Finalizer((\$p) {
_\$impls.remove(\$p.sendPort.nativePort);
\$p.close();
});
@override
void delete() {
_\$impls.remove(_\$p?.sendPort.nativePort);
_\$p?.close();
_\$finalizer.detach(this);
super.delete();
}
static jni.JObjectPtr _\$invoke(
static jni.JObjectPtr _$invoke(
int port,
jni.JObjectPtr descriptor,
jni.JObjectPtr args,
) {
return _\$invokeMethod(
return _$invokeMethod(
port,
\$MethodInvocation.fromAddresses(
$MethodInvocation.fromAddresses(
0,
descriptor.address,
args.address,
Expand All @@ -445,14 +433,14 @@ class $name$typeParamsDef extends $superName {
ffi.NativeFunction<
jni.JObjectPtr Function(
ffi.Uint64, jni.JObjectPtr, jni.JObjectPtr)>>
_\$invokePointer = ffi.Pointer.fromFunction(_\$invoke);
_$invokePointer = ffi.Pointer.fromFunction(_$invoke);
static ffi.Pointer<ffi.Void> _\$invokeMethod(
int \$p,
\$MethodInvocation \$i,
static ffi.Pointer<ffi.Void> _$invokeMethod(
int $p,
$MethodInvocation $i,
) {
final \$d = \$i.methodDescriptor.toDartString(deleteOriginal: true);
final \$a = \$i.args;
final $d = $i.methodDescriptor.toDartString(deleteOriginal: true);
final $a = $i.args;
''');
final proxyMethodIf = _InterfaceMethodIf(resolver, s);
for (final method in node.methods) {
Expand Down Expand Up @@ -482,18 +470,27 @@ class $name$typeParamsDef extends $superName {
final \$a = \$p.sendPort.nativePort;
_\$impls[\$a] = \$impl;
''');
s.write('''
_\$finalizer.attach(\$x, \$p, detach: \$x);
\$p.listen((\$m) {
final \$i = \$MethodInvocation.fromMessage(\$m);
final \$r = _\$invokeMethod(\$p.sendPort.nativePort, \$i);
ProtectedJniExtensions.returnResult(\$i.result, \$r);
s.write(r'''
$p.listen(($m) {
if ($m == null) {
_$impls.remove($p.sendPort.nativePort);
$p.close();
return;
}
final $i = $MethodInvocation.fromMessage($m);
final $r = _$invokeMethod($p.sendPort.nativePort, $i);
ProtectedJniExtensions.returnResult($i.result, $r);
});
return \$x;
return $x;
}
''');
}

// Writing any custom code provided for this class.
if (config.customClassBody?.containsKey(node.binaryName) ?? false) {
s.writeln(config.customClassBody![node.binaryName]);
}

// End of Class definition.
s.writeln('}');

Expand Down
7 changes: 7 additions & 0 deletions pkgs/jnigen/lib/src/config/config_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ class Config {
this.sourcePath,
this.classPath,
this.preamble,
this.customClassBody,
this.androidSdkConfig,
this.mavenDownloads,
this.summarizerOptions,
Expand Down Expand Up @@ -386,6 +387,12 @@ class Config {
/// Call [importClasses] before using this.
late final Map<String, ClassDecl> importedClasses;

/// Custom code that is added to the end of the class body with the specified
/// binary name.
///
/// Used for testing package:jnigen.
final Map<String, String>? customClassBody;

Future<void> importClasses() async {
importedClasses = {};
for (final import in [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3344,22 +3344,8 @@ class MyInterface<$T extends jni.JObject> extends jni.JObject {

/// Maps a specific port to the implemented interface.
static final Map<int, $MyInterfaceImpl> _$impls = {};

ReceivePort? _$p;

static final Finalizer<ReceivePort> _$finalizer = Finalizer(($p) {
_$impls.remove($p.sendPort.nativePort);
$p.close();
});

@override
void delete() {
_$impls.remove(_$p?.sendPort.nativePort);
_$p?.close();
_$finalizer.detach(this);
super.delete();
}

static jni.JObjectPtr _$invoke(
int port,
jni.JObjectPtr descriptor,
Expand Down Expand Up @@ -3439,14 +3425,19 @@ class MyInterface<$T extends jni.JObject> extends jni.JObject {
).._$p = $p;
final $a = $p.sendPort.nativePort;
_$impls[$a] = $impl;
_$finalizer.attach($x, $p, detach: $x);
$p.listen(($m) {
if ($m == null) {
_$impls.remove($p.sendPort.nativePort);
$p.close();
return;
}
final $i = $MethodInvocation.fromMessage($m);
final $r = _$invokeMethod($p.sendPort.nativePort, $i);
ProtectedJniExtensions.returnResult($i.result, $r);
});
return $x;
}
static Map<int, $MyInterfaceImpl> get $impls => _$impls;
}

abstract class $MyInterfaceImpl<$T extends jni.JObject> {
Expand Down
Loading

0 comments on commit 95c8375

Please sign in to comment.