Skip to content

Arena use with Java references #766

@dcharkes

Description

@dcharkes

Problem

When using JNI, references to Java objects need to be deleted. Manual management leads to the following type of code.

  testWidgets("Long.intValue() using JniObject", (tester) async {
    final longClass = jni.findJniClass("java/lang/Long");
    final longCtor = longClass.getConstructorID("(J)V");
    final long = longClass.newObject(longCtor, [176]);

    final intValue = long.callIntMethodByName("intValue", "()I", []);
    expect(intValue, equals(176));

    long.delete();
    longClass.delete();
  });

This type of code smells like it should use the Arena from package:ffi.

https://github.com/dart-lang/ffi/blob/master/lib/src/arena.dart

The question is what API we should use.

API 1: Extension method on Arena

  testWidgets("Long.intValue() using JniObject", (tester) async {
    using((Arena arena) {
      final longClass = arena.manage(jni.findJniClass("java/lang/Long"));
      final longCtor = longClass.getConstructorID("(J)V");
      final long = arena.manage(longClass.newObject(longCtor, [176]));

      final intValue = long.callIntMethodByName("intValue", "()I", []);
      expect(intValue, equals(176));
    });
  });

Pro:

  • Conceptually makes it very clear that the object returned from manage is managed by the arena so that the API user doesn't have to worry about managing it.

Con:

  • It requires the API user to write arena.manage before the thing they are thinking about.

API 2: Method on objects with delete

  testWidgets("Long.intValue() using JniObject", (tester) async {
    using((Arena arena) {
      final longClass = jni.findJniClass("java/lang/Long")..deletedBy(arena);
      final longCtor = longClass.getConstructorID("(J)V");
      final long = longClass.newObject(longCtor, [176])..deletedBy(arena);

      final intValue = long.callIntMethodByName("intValue", "()I", []);
      expect(intValue, equals(176));
    });
  });

Pro:

  • The methods delete and deletedBy are clearly related.

Con:

  • .. feels like it is easily forgotten.
  • deletedBy doesn't feel like a natural method name. Should it be deleter? setDeleter?

Side note: should it be delete? It is DeleteGlobalRef in JNI, but if we want to talk about the abstraction of native resource management, we've used release as verb. Not free or delete.

API 3: Optional arena parameter

  testWidgets("Long.intValue() using JniObject", (tester) async {
    using((Arena arena) {
      final longClass = jni.findJniClass("java/lang/Long", arena: arena);
      final longCtor = longClass.getConstructorID("(J)V");
      final long = longClass.newObject(longCtor, [176], arena: arena);

      final intValue = long.callIntMethodByName("intValue", "()I", []);
      expect(intValue, equals(176));
    });
  });

Pro:

  • Most concise

Con:

  • Generated for a huge API surface

This approach would be similar to the optional allocator parameter in toNativeUtf8.

https://github.com/dart-lang/ffi/blob/master/lib/src/utf8.dart#L81

API 4: NativeFinalizers

We could attach a NativeFinalizer to all objects and have them be cleaned up automatically.

  testWidgets("Long.intValue() using JniObject", (tester) async {
    final longClass = jni.findJniClass("java/lang/Long");
    final longCtor = longClass.getConstructorID("(J)V");
    final long = longClass.newObject(longCtor, [176]);

    final intValue = long.callIntMethodByName("intValue", "()I", []);
    expect(intValue, equals(176));
  });
  // Magic, at some point the GC does cleanup...

However, even in that case we could keep delete and Arena support. Those would cancel the native finalizer.

@mahesh-hegde and I are slightly leaning towards API 2.
@liamappelbe @lrhn @mkustermann WDYT?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions