Skip to content

Commit d767fe8

Browse files
Use finalizable handles and vm:deeply-immutable pragma instead of NativeFinalizers for JReference
1 parent 7d99ceb commit d767fe8

File tree

7 files changed

+176
-31
lines changed

7 files changed

+176
-31
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.github.dart_lang.jni;
2+
3+
public class JniUtils {
4+
public static native Object fromReferenceAddress(long id);
5+
}

pkgs/jni/lib/src/jni.dart

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,17 @@ abstract final class Jni {
6262
_dylibDir = dylibDir;
6363
}
6464

65+
static bool _initialized = false;
66+
6567
/// Initializes DartApiDL used for Continuations and interface implementation.
6668
static void initDLApi() {
67-
assert(NativeApi.majorVersion == 2);
68-
assert(NativeApi.minorVersion >= 3);
69-
final result = _bindings.InitDartApiDL(NativeApi.initializeApiDLData);
70-
assert(result == 0);
69+
if (!_initialized) {
70+
assert(NativeApi.majorVersion == 2);
71+
assert(NativeApi.minorVersion >= 3);
72+
final result = _bindings.InitDartApiDL(NativeApi.initializeApiDLData);
73+
_initialized = result == 0;
74+
assert(_initialized);
75+
}
7176
}
7277

7378
/// Spawn an instance of JVM using JNI. This method should be called at the
@@ -267,6 +272,21 @@ extension ProtectedJniExtensions on Jni {
267272
Pointer<CallbackResult> result, JObjectPtr object) async {
268273
Jni._bindings.resultFor(result, object);
269274
}
275+
276+
static Dart_FinalizableHandle newFinalizableHandle(
277+
Object object,
278+
Pointer<Void> reference,
279+
int refType,
280+
) {
281+
Jni.initDLApi();
282+
return Jni._bindings.newFinalizableHandle(object, reference, refType);
283+
}
284+
285+
static void deleteFinalizableHandle(
286+
Dart_FinalizableHandle finalizableHandle, Object object) {
287+
Jni.initDLApi();
288+
Jni._bindings.deleteFinalizableHandle(finalizableHandle, object);
289+
}
270290
}
271291

272292
extension AdditionalEnvMethods on GlobalJniEnv {

pkgs/jni/lib/src/jreference.dart

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@ import 'jni.dart';
1111

1212
extension ProtectedJReference on JReference {
1313
void setAsReleased() {
14-
if (_released) {
15-
throw DoubleReleaseError();
16-
}
17-
_released = true;
18-
JGlobalReference._finalizer.detach(this);
14+
ProtectedJniExtensions.deleteFinalizableHandle(
15+
_finalizableHandle, _finalizable);
16+
// FIXME: No [DoubleReleaseError] will be thrown.
1917
}
2018

2119
void ensureNotNull() {
@@ -29,15 +27,25 @@ extension ProtectedJReference on JReference {
2927
/// Detaches the finalizer so the underlying pointer will not be deleted.
3028
JObjectPtr toPointer() {
3129
setAsReleased();
32-
return _pointer;
30+
return _finalizable._pointer;
3331
}
3432
}
3533

34+
@pragma('vm:deeply-immutable')
35+
final class _JFinalizable implements Finalizable {
36+
final Pointer<Void> _pointer;
37+
38+
_JFinalizable(this._pointer);
39+
}
40+
41+
@pragma('vm:deeply-immutable')
3642
abstract final class JReference {
37-
final JObjectPtr _pointer;
38-
bool _released = false;
43+
final _JFinalizable _finalizable;
44+
final Dart_FinalizableHandle _finalizableHandle;
3945

40-
JReference(this._pointer);
46+
JReference(this._finalizable, int kind)
47+
: _finalizableHandle = ProtectedJniExtensions.newFinalizableHandle(
48+
_finalizable, _finalizable._pointer, kind);
4149

4250
/// The underlying JNI reference.
4351
///
@@ -46,12 +54,13 @@ abstract final class JReference {
4654
/// Be careful when storing this in a variable since it might have gotten
4755
/// released upon use.
4856
JObjectPtr get pointer {
49-
if (_released) throw UseAfterReleaseError();
50-
return _pointer;
57+
// FIXME: No [UseAfterReleaseError] will be thrown.
58+
return _finalizable._pointer;
5159
}
5260

5361
/// Whether the underlying JNI reference is deleted or not.
54-
bool get isReleased => _released;
62+
// FIXME: releasing does not work.
63+
bool get isReleased => false;
5564

5665
/// Whether the underlying JNI reference is `null` or not.
5766
bool get isNull;
@@ -72,27 +81,26 @@ abstract final class JReference {
7281
/// A managed JNI global reference.
7382
///
7483
/// Uses a [NativeFinalizer] to delete the JNI global reference when finalized.
75-
final class JGlobalReference extends JReference implements Finalizable {
76-
static final _finalizer =
77-
NativeFinalizer(Jni.env.ptr.ref.DeleteGlobalRef.cast());
78-
79-
JGlobalReference(super._reference) {
80-
_finalizer.attach(this, _pointer, detach: this);
81-
}
84+
@pragma('vm:deeply-immutable')
85+
final class JGlobalReference extends JReference {
86+
JGlobalReference(Pointer<Void> pointer)
87+
: super(_JFinalizable(pointer), JObjectRefType.JNIGlobalRefType);
8288

8389
@override
8490
bool get isNull => pointer == nullptr;
8591

8692
@override
8793
void _deleteReference() {
88-
Jni.env.DeleteGlobalRef(_pointer);
94+
Jni.env.DeleteGlobalRef(_finalizable._pointer);
8995
}
9096
}
9197

92-
final jNullReference = _JNullReference();
98+
final JReference jNullReference = _JNullReference();
9399

100+
@pragma('vm:deeply-immutable')
94101
final class _JNullReference extends JReference {
95-
_JNullReference() : super(nullptr);
102+
_JNullReference()
103+
: super(_JFinalizable(nullptr), JObjectRefType.JNIInvalidRefType);
96104

97105
@override
98106
void _deleteReference() {

pkgs/jni/lib/src/third_party/jni_bindings_generated.dart

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,42 @@ class JniBindings {
242242
late final _resultFor = _resultForPtr
243243
.asFunction<void Function(ffi.Pointer<CallbackResult>, JObjectPtr)>();
244244

245+
Dart_FinalizableHandle newFinalizableHandle(
246+
Object object,
247+
JObjectPtr reference,
248+
int refType,
249+
) {
250+
return _newFinalizableHandle(
251+
object,
252+
reference,
253+
refType,
254+
);
255+
}
256+
257+
late final _newFinalizableHandlePtr = _lookup<
258+
ffi.NativeFunction<
259+
Dart_FinalizableHandle Function(
260+
ffi.Handle, JObjectPtr, ffi.Int32)>>('newFinalizableHandle');
261+
late final _newFinalizableHandle = _newFinalizableHandlePtr
262+
.asFunction<Dart_FinalizableHandle Function(Object, JObjectPtr, int)>();
263+
264+
void deleteFinalizableHandle(
265+
Dart_FinalizableHandle finalizableHandle,
266+
Object object,
267+
) {
268+
return _deleteFinalizableHandle(
269+
finalizableHandle,
270+
object,
271+
);
272+
}
273+
274+
late final _deleteFinalizableHandlePtr = _lookup<
275+
ffi.NativeFunction<
276+
ffi.Void Function(
277+
Dart_FinalizableHandle, ffi.Handle)>>('deleteFinalizableHandle');
278+
late final _deleteFinalizableHandle = _deleteFinalizableHandlePtr
279+
.asFunction<void Function(Dart_FinalizableHandle, Object)>();
280+
245281
ffi.Pointer<GlobalJniEnvStruct> GetGlobalEnv() {
246282
return _GetGlobalEnv();
247283
}
@@ -2021,6 +2057,10 @@ final class _opaque_pthread_cond_t extends ffi.Struct {
20212057
external ffi.Array<ffi.Char> __opaque;
20222058
}
20232059

2060+
typedef Dart_FinalizableHandle = ffi.Pointer<_Dart_FinalizableHandle>;
2061+
2062+
final class _Dart_FinalizableHandle extends ffi.Opaque {}
2063+
20242064
final class GlobalJniEnvStruct extends ffi.Struct {
20252065
external ffi.Pointer<ffi.Void> reserved0;
20262066

pkgs/jni/src/dartjni.c

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88

99
#include "dartjni.h"
1010

11-
#include "include/dart_api_dl.h"
12-
1311
void initAllLocks(JniLocks* locks) {
1412
init_lock(&locks->classLoadingLock);
1513
init_lock(&locks->fieldLoadingLock);
@@ -659,6 +657,46 @@ void resultFor(CallbackResult* result, jobject object) {
659657
release_lock(&result->lock);
660658
}
661659

660+
void doNotFinalize(void* isolate_callback_data, void* peer) {}
661+
662+
void finalizeLocal(void* isolate_callback_data, void* peer) {
663+
attach_thread();
664+
(*jniEnv)->DeleteLocalRef(jniEnv, peer);
665+
}
666+
667+
void finalizeGlobal(void* isolate_callback_data, void* peer) {
668+
attach_thread();
669+
(*jniEnv)->DeleteGlobalRef(jniEnv, peer);
670+
}
671+
672+
void finalizeWeakGlobal(void* isolate_callback_data, void* peer) {
673+
attach_thread();
674+
(*jniEnv)->DeleteWeakGlobalRef(jniEnv, peer);
675+
}
676+
677+
FFI_PLUGIN_EXPORT
678+
Dart_FinalizableHandle newFinalizableHandle(Dart_Handle object,
679+
jobject reference,
680+
jobjectRefType refType) {
681+
switch (refType) {
682+
case JNIInvalidRefType:
683+
return Dart_NewFinalizableHandle_DL(object, reference, 0, doNotFinalize);
684+
case JNILocalRefType:
685+
return Dart_NewFinalizableHandle_DL(object, reference, 0, finalizeLocal);
686+
case JNIGlobalRefType:
687+
return Dart_NewFinalizableHandle_DL(object, reference, 0, finalizeGlobal);
688+
case JNIWeakGlobalRefType:
689+
return Dart_NewFinalizableHandle_DL(object, reference, 0,
690+
finalizeWeakGlobal);
691+
}
692+
}
693+
694+
FFI_PLUGIN_EXPORT
695+
void deleteFinalizableHandle(Dart_FinalizableHandle finalizableHandle,
696+
Dart_Handle object) {
697+
return Dart_DeleteFinalizableHandle_DL(finalizableHandle, object);
698+
}
699+
662700
jclass _c_Object = NULL;
663701
jclass _c_Long = NULL;
664702

@@ -747,3 +785,11 @@ Java_com_github_dart_1lang_jni_PortCleaner_clean(JNIEnv* env,
747785
close_signal.type = Dart_CObject_kNull;
748786
Dart_PostCObject_DL(port, &close_signal);
749787
}
788+
789+
JNIEXPORT jobject JNICALL
790+
Java_com_github_dart_1lang_jni_JniUtils_fromReferenceAddress(JNIEnv* env,
791+
jclass clazz,
792+
jlong id) {
793+
attach_thread();
794+
return (jobject)(id);
795+
}

pkgs/jni/src/dartjni.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#pragma once
66

77
// Note: include appropriate system jni.h as found by CMake, not third_party/jni.h.
8+
#include "include/dart_api_dl.h"
9+
810
#include <jni.h>
911
#include <stdint.h>
1012
#include <stdio.h>
@@ -422,3 +424,12 @@ JniResult PortProxy__newInstance(jobject binaryName,
422424
int64_t functionPtr);
423425

424426
FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object);
427+
428+
FFI_PLUGIN_EXPORT
429+
Dart_FinalizableHandle newFinalizableHandle(Dart_Handle object,
430+
jobject reference,
431+
jobjectRefType refType);
432+
433+
FFI_PLUGIN_EXPORT
434+
void deleteFinalizableHandle(Dart_FinalizableHandle finalizableHandle,
435+
Dart_Handle object);

pkgs/jni/test/load_test.dart

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ void run({required TestRunnerCallback testRunner}) {
6767
// The actual expect here does not matter. I am just being paranoid
6868
// against assigning to `_` because compiler may optimize it. (It has
6969
// side effect of calling FFI but still.)
70-
expect(random.reference, isNot(nullptr));
70+
expect(random.reference.pointer, isNot(nullptr));
7171
});
7272
}
7373
});
@@ -76,7 +76,7 @@ void run({required TestRunnerCallback testRunner}) {
7676
() {
7777
for (int i = 0; i < k256; i++) {
7878
final random = newRandom();
79-
expect(random.reference, isNot(nullptr));
79+
expect(random.reference.pointer, isNot(nullptr));
8080
random.release();
8181
}
8282
});
@@ -86,7 +86,7 @@ void run({required TestRunnerCallback testRunner}) {
8686
using((arena) {
8787
for (int i = 0; i < 256; i++) {
8888
final r = newRandom()..releasedBy(arena);
89-
expect(r.reference, isNot(nullptr));
89+
expect(r.reference.pointer, isNot(nullptr));
9090
}
9191
});
9292
}
@@ -103,6 +103,20 @@ void run({required TestRunnerCallback testRunner}) {
103103
}
104104
});
105105

106+
void testFinalizer() {
107+
testRunner('Finalizer correctly removes the references', () {
108+
// We are checking if we can run this for large number of times.
109+
// More than the number of available global references.
110+
for (var i = 0; i < k256; ++i) {
111+
final random = newRandom();
112+
expect(random.reference.pointer, isNot(nullptr));
113+
if (i % k4 == 0) {
114+
doGC();
115+
}
116+
}
117+
});
118+
}
119+
106120
void testRefValidityAfterGC(int delayInSeconds) {
107121
testRunner('Validate reference after GC & ${delayInSeconds}s sleep', () {
108122
final random = newRandom();
@@ -122,6 +136,7 @@ void run({required TestRunnerCallback testRunner}) {
122136

123137
// Dart_ExecuteInternalCommand doesn't exist in Android.
124138
if (!Platform.isAndroid) {
139+
testFinalizer();
125140
testRefValidityAfterGC(1);
126141
testRefValidityAfterGC(10);
127142
}

0 commit comments

Comments
 (0)