diff --git a/test-app/app/src/main/assets/app/tests/byte-buffer-test.js b/test-app/app/src/main/assets/app/tests/byte-buffer-test.js index 8ed0e592f..675559f56 100644 --- a/test-app/app/src/main/assets/app/tests/byte-buffer-test.js +++ b/test-app/app/src/main/assets/app/tests/byte-buffer-test.js @@ -80,4 +80,31 @@ describe("Tests mapped ByteBuffer conversion", function () { } expect(exceptionCaught).toBe(true); }); -}); \ No newline at end of file +}); + +describe("Tests JNI references of ArrayBuffers passed to Java", function () { + // ART aborts the process once its JNI global reference table holds 51200 + // entries. Each ArrayBuffer or typed array crossing to Java for the first + // time used to take a global reference that was never deleted, so these + // loops cross more distinct buffers than the table can hold. + var MORE_THAN_THE_GLOBAL_REF_TABLE = 60000; + + it("should not keep a global reference per buffer passed as a method argument", function () { + var holder = new com.tns.tests.ByteBufferHolder(); + for (var i = 0; i < MORE_THAN_THE_GLOBAL_REF_TABLE; i++) { + var buffer = i % 2 ? new ArrayBuffer(1) : new Uint8Array(1); + new Uint8Array(buffer.buffer || buffer)[0] = i & 0x7f; + holder.hold(buffer); + } + expect(holder.get(0)).toBe((MORE_THAN_THE_GLOBAL_REF_TABLE - 1) & 0x7f); + }); + + it("should not keep a global reference per buffer stored in a Java array", function () { + var array = Array.create(java.nio.ByteBuffer, 1); + for (var i = 0; i < MORE_THAN_THE_GLOBAL_REF_TABLE; i++) { + array[0] = new ArrayBuffer(1); + } + // reaching here without the runtime aborting is the real assertion + expect(array[0]).toBeTruthy(); + }); +}); diff --git a/test-app/runtime/src/main/cpp/JsArgConverter.cpp b/test-app/runtime/src/main/cpp/JsArgConverter.cpp index e5d82f0bd..14ac72260 100644 --- a/test-app/runtime/src/main/cpp/JsArgConverter.cpp +++ b/test-app/runtime/src/main/cpp/JsArgConverter.cpp @@ -331,8 +331,11 @@ bool JsArgConverter::ConvertArg(const Local &arg, int index) { buffer = directBuffer; } - buffer = env.NewGlobalRef(buffer); - + // The buffer needs no global ref of its own: GetOrCreateObjectId keeps + // it strongly reachable from Java until the JS object is collected. + // One taken here would never be deleted, pinning the buffer (and, via + // JSObjectFinalizer, its ArrayBuffer) and filling the JNI global + // reference table. int id = objectManager->GetOrCreateObjectId(buffer); auto clazz = env.GetObjectClass(buffer); diff --git a/test-app/runtime/src/main/cpp/JsArgToArrayConverter.cpp b/test-app/runtime/src/main/cpp/JsArgToArrayConverter.cpp index 63216a06f..b702b8474 100644 --- a/test-app/runtime/src/main/cpp/JsArgToArrayConverter.cpp +++ b/test-app/runtime/src/main/cpp/JsArgToArrayConverter.cpp @@ -334,8 +334,11 @@ bool JsArgToArrayConverter::ConvertArg(Local context, const LocalGetOrCreateObjectId(buffer); auto clazz = env.GetObjectClass(buffer); objectManager->Link(jsObj, id, clazz);