Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable sharing JObjects across isolates #1060

Merged
merged 10 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pkgs/jni/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
`fill` object and not its Java runtime type.
- `JObject`s now check the types using `instanceof` in debug mode when using
`castTo`.
- **Breaking Change**: `Jni.initDLApi()` is renamed to `Jni.ensureInitialized()`
and it is no longer needed to be called by users.
- Added the ability to share `JObject`s across isolates.
HosseinYousefi marked this conversation as resolved.
Show resolved Hide resolved

## 0.7.3

Expand Down
2 changes: 2 additions & 0 deletions pkgs/jni/ffigen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ output: 'lib/src/third_party/jni_bindings_generated.dart'
headers:
entry-points:
- 'src/dartjni.h' # Exports majority of JNI functions
- 'src/internal.h'
- 'src/third_party/global_jni_env.h' # Exports GlobalJniEnv type
- 'src/jni_constants.h'
include-directives:
- 'src/dartjni.h'
- 'src/internal.h'
- 'src/third_party/global_jni_env.h'
- 'third_party/jni.h' # jni.h from Android NDK
- 'src/jni_constants.h'
Expand Down
19 changes: 6 additions & 13 deletions pkgs/jni/lib/src/errors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,17 @@ import 'package:jni/src/third_party/generated_bindings.dart';
// TODO(#567): Add the fact that [JException] is now a [JObject] to the
// CHANGELOG.

final class UseAfterReleaseError extends Error {
@override
String toString() {
return 'Use after release error';
}
final class UseAfterReleaseError extends StateError {
UseAfterReleaseError() : super('Use after release error');
}

// TODO(#567): Use NullPointerError once it's available.
final class JNullError extends Error {
@override
String toString() => 'The reference was null';
final class JNullError extends StateError {
JNullError() : super('The reference was null');
}

final class DoubleReleaseError extends Error {
@override
String toString() {
return 'Double release error';
}
final class DoubleReleaseError extends StateError {
DoubleReleaseError() : super('Double release error');
}

/// Represents JNI errors that might be returned by methods like
Expand Down
41 changes: 36 additions & 5 deletions pkgs/jni/lib/src/jni.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,17 @@ abstract final class Jni {
_dylibDir = dylibDir;
}

static bool _initialized = false;
HosseinYousefi marked this conversation as resolved.
Show resolved Hide resolved

/// Initializes DartApiDL used for Continuations and interface implementation.
static void initDLApi() {
assert(NativeApi.majorVersion == 2);
assert(NativeApi.minorVersion >= 3);
final result = _bindings.InitDartApiDL(NativeApi.initializeApiDLData);
assert(result == 0);
static void _ensureInitialized() {
if (!_initialized) {
HosseinYousefi marked this conversation as resolved.
Show resolved Hide resolved
assert(NativeApi.majorVersion == 2);
assert(NativeApi.minorVersion >= 3);
final result = _bindings.InitDartApiDL(NativeApi.initializeApiDLData);
_initialized = result == 0;
assert(_initialized);
}
}

/// Spawn an instance of JVM using JNI. This method should be called at the
Expand Down Expand Up @@ -238,6 +243,7 @@ extension ProtectedJniExtensions on Jni {

/// Returns a new PortContinuation.
static JReference newPortContinuation(ReceivePort port) {
Jni._ensureInitialized();
return JGlobalReference(
Jni._bindings
.PortContinuation__ctor(port.sendPort.nativePort)
Expand All @@ -253,6 +259,7 @@ extension ProtectedJniExtensions on Jni {
NativeFunction<
Pointer<Void> Function(Uint64, Pointer<Void>, Pointer<Void>)>>
functionPtr) {
Jni._ensureInitialized();
return JGlobalReference(Jni._bindings
.PortProxy__newInstance(
Jni.env.toJStringPtr(binaryName),
Expand All @@ -267,6 +274,30 @@ extension ProtectedJniExtensions on Jni {
Pointer<CallbackResult> result, JObjectPtr object) async {
Jni._bindings.resultFor(result, object);
}

static Dart_FinalizableHandle newJObjectFinalizableHandle(
Object object,
Pointer<Void> reference,
int refType,
) {
Jni._ensureInitialized();
return Jni._bindings
.newJObjectFinalizableHandle(object, reference, refType);
}

static Dart_FinalizableHandle newBooleanFinalizableHandle(
Object object,
Pointer<Bool> reference,
) {
Jni._ensureInitialized();
return Jni._bindings.newBooleanFinalizableHandle(object, reference);
}

static void deleteFinalizableHandle(
Dart_FinalizableHandle finalizableHandle, Object object) {
Jni._ensureInitialized();
Jni._bindings.deleteFinalizableHandle(finalizableHandle, object);
}
}

extension AdditionalEnvMethods on GlobalJniEnv {
Expand Down
7 changes: 6 additions & 1 deletion pkgs/jni/lib/src/jobject.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
import 'dart:ffi';

import 'package:ffi/ffi.dart';
import 'package:jni/internal_helpers_for_jnigen.dart';

import 'accessors.dart';
import 'jni.dart';
import 'jreference.dart';
import 'lang/jstring.dart';
import 'types.dart';

Expand Down Expand Up @@ -66,6 +67,10 @@ class JObject {

bool get isNull => reference.isNull;

/// Releases the underlying [reference].
///
/// Releasing in one isolate while using or releasing in another isolate might
/// crash in the JNI layer.
void release() {
reference.release();
}
Expand Down
84 changes: 60 additions & 24 deletions pkgs/jni/lib/src/jreference.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,15 @@

import 'dart:ffi';

import 'package:ffi/ffi.dart';
import 'package:jni/src/third_party/generated_bindings.dart';

import 'errors.dart';
import 'jni.dart';

extension ProtectedJReference on JReference {
void setAsReleased() {
if (_released) {
throw DoubleReleaseError();
}
_released = true;
JGlobalReference._finalizer.detach(this);
_setAsReleased();
}

void ensureNotNull() {
Expand All @@ -28,16 +25,24 @@ extension ProtectedJReference on JReference {
///
/// Detaches the finalizer so the underlying pointer will not be deleted.
JObjectPtr toPointer() {
setAsReleased();
return _pointer;
_setAsReleased();
return _finalizable.pointer;
}
}

/// A thin wrapper around a pointer that makes it [Finalizable].
@pragma('vm:deeply-immutable')
final class _JFinalizable implements Finalizable {
final Pointer<Void> pointer;

_JFinalizable(this.pointer);
}

@pragma('vm:deeply-immutable')
abstract final class JReference {
final JObjectPtr _pointer;
bool _released = false;
final _JFinalizable _finalizable;

JReference(this._pointer);
JReference(this._finalizable);

/// The underlying JNI reference.
///
Expand All @@ -46,12 +51,12 @@ abstract final class JReference {
/// Be careful when storing this in a variable since it might have gotten
/// released upon use.
JObjectPtr get pointer {
if (_released) throw UseAfterReleaseError();
return _pointer;
if (isReleased) throw UseAfterReleaseError();
return _finalizable.pointer;
}

/// Whether the underlying JNI reference is deleted or not.
bool get isReleased => _released;
bool get isReleased;

/// Whether the underlying JNI reference is `null` or not.
bool get isNull;
Expand All @@ -62,45 +67,76 @@ abstract final class JReference {
///
/// Further uses of this object will throw [UseAfterReleaseError].
HosseinYousefi marked this conversation as resolved.
Show resolved Hide resolved
void release() {
setAsReleased();
_setAsReleased();
_deleteReference();
}

void _deleteReference();

void _setAsReleased();
}

/// A managed JNI global reference.
///
/// Uses a [NativeFinalizer] to delete the JNI global reference when finalized.
final class JGlobalReference extends JReference implements Finalizable {
static final _finalizer =
NativeFinalizer(Jni.env.ptr.ref.DeleteGlobalRef.cast());

JGlobalReference(super._reference) {
_finalizer.attach(this, _pointer, detach: this);
@pragma('vm:deeply-immutable')
final class JGlobalReference extends JReference {
/// The finalizable handle that deletes [_JFinalizable.pointer].
final Dart_FinalizableHandle _jobjectFinalizableHandle;
final Pointer<Bool> _isReleased;

JGlobalReference._(
super._finalizable, this._jobjectFinalizableHandle, this._isReleased);

factory JGlobalReference(Pointer<Void> pointer) {
final finalizable = _JFinalizable(pointer);
final isReleased = calloc<Bool>();
final jobjectFinalizableHandle =
ProtectedJniExtensions.newJObjectFinalizableHandle(
finalizable, finalizable.pointer, JObjectRefType.JNIGlobalRefType);
ProtectedJniExtensions.newBooleanFinalizableHandle(finalizable, isReleased);
return JGlobalReference._(
finalizable, jobjectFinalizableHandle, isReleased);
}

@override
bool get isNull => pointer == nullptr;

@override
void _setAsReleased() {
if (isReleased) {
throw DoubleReleaseError();
HosseinYousefi marked this conversation as resolved.
Show resolved Hide resolved
}
_isReleased.value = true;
HosseinYousefi marked this conversation as resolved.
Show resolved Hide resolved
ProtectedJniExtensions.deleteFinalizableHandle(
_jobjectFinalizableHandle, _finalizable);
}

@override
void _deleteReference() {
Jni.env.DeleteGlobalRef(_pointer);
Jni.env.DeleteGlobalRef(_finalizable.pointer);
}

@override
bool get isReleased => _isReleased.value;
}

final jNullReference = _JNullReference();
final JReference jNullReference = _JNullReference();

@pragma('vm:deeply-immutable')
final class _JNullReference extends JReference {
_JNullReference() : super(nullptr);
_JNullReference() : super(_JFinalizable(nullptr));

@override
bool get isReleased => false;

@override
void _deleteReference() {
// No need to delete `null`.
}

@override
void release() {
void _setAsReleased() {
// No need to release `null`.
}

Expand Down
58 changes: 58 additions & 0 deletions pkgs/jni/lib/src/third_party/jni_bindings_generated.dart
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,60 @@ class JniBindings {
late final _resultFor = _resultForPtr
.asFunction<void Function(ffi.Pointer<CallbackResult>, JObjectPtr)>();

Dart_FinalizableHandle newJObjectFinalizableHandle(
Object object,
JObjectPtr reference,
int refType,
) {
return _newJObjectFinalizableHandle(
object,
reference,
refType,
);
}

late final _newJObjectFinalizableHandlePtr = _lookup<
ffi.NativeFunction<
Dart_FinalizableHandle Function(ffi.Handle, JObjectPtr,
ffi.Int32)>>('newJObjectFinalizableHandle');
late final _newJObjectFinalizableHandle = _newJObjectFinalizableHandlePtr
.asFunction<Dart_FinalizableHandle Function(Object, JObjectPtr, int)>();

Dart_FinalizableHandle newBooleanFinalizableHandle(
Object object,
ffi.Pointer<ffi.Bool> reference,
) {
return _newBooleanFinalizableHandle(
object,
reference,
);
}

late final _newBooleanFinalizableHandlePtr = _lookup<
ffi.NativeFunction<
Dart_FinalizableHandle Function(ffi.Handle,
ffi.Pointer<ffi.Bool>)>>('newBooleanFinalizableHandle');
late final _newBooleanFinalizableHandle =
_newBooleanFinalizableHandlePtr.asFunction<
Dart_FinalizableHandle Function(Object, ffi.Pointer<ffi.Bool>)>();

void deleteFinalizableHandle(
Dart_FinalizableHandle finalizableHandle,
Object object,
) {
return _deleteFinalizableHandle(
finalizableHandle,
object,
);
}

late final _deleteFinalizableHandlePtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(
Dart_FinalizableHandle, ffi.Handle)>>('deleteFinalizableHandle');
late final _deleteFinalizableHandle = _deleteFinalizableHandlePtr
.asFunction<void Function(Dart_FinalizableHandle, Object)>();

ffi.Pointer<GlobalJniEnvStruct> GetGlobalEnv() {
return _GetGlobalEnv();
}
Expand Down Expand Up @@ -2003,6 +2057,10 @@ final class _opaque_pthread_cond_t extends ffi.Struct {
external ffi.Array<ffi.Char> __opaque;
}

typedef Dart_FinalizableHandle = ffi.Pointer<_Dart_FinalizableHandle>;

final class _Dart_FinalizableHandle extends ffi.Opaque {}

final class GlobalJniEnvStruct extends ffi.Struct {
external ffi.Pointer<ffi.Void> reserved0;

Expand Down
1 change: 1 addition & 0 deletions pkgs/jni/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ project(jni_library VERSION 0.0.1 LANGUAGES C)

add_library(jni SHARED
"dartjni.c"
"internal.c"
"third_party/global_jni_env.c"
"include/dart_api_dl.c"
)
Expand Down
Loading
Loading