| // Copyright (c) 2022, 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. | |
| @Tags(['load_test']) | |
| library; | |
| import 'dart:ffi'; | |
| import 'dart:io'; | |
| import 'dart:math'; | |
| import 'package:ffi/ffi.dart'; | |
| import 'package:jni/jni.dart'; | |
| import 'package:jni/src/third_party/generated_bindings.dart'; | |
| import 'package:test/test.dart'; | |
| import 'test_util/test_util.dart'; | |
| /// Taken from | |
| /// https://github.com/dart-lang/ffigen/blob/master/test/native_objc_test/automated_ref_count_test.dart | |
| final executeInternalCommand = DynamicLibrary.process().lookupFunction< | |
| Void Function(Pointer<Char>, Pointer<Void>), | |
| void Function(Pointer<Char>, Pointer<Void>)>('Dart_ExecuteInternalCommand'); | |
| void doGC() { | |
| final gcNow = 'gc-now'.toNativeUtf8(); | |
| executeInternalCommand(gcNow.cast(), nullptr); | |
| calloc.free(gcNow); | |
| } | |
| void main() { | |
| if (!Platform.isAndroid) { | |
| checkDylibIsUpToDate(); | |
| spawnJvm(); | |
| } | |
| run(testRunner: test); | |
| } | |
| const k4 = 4 * 1024; | |
| const k256 = 256 * 1024; | |
| const secureRandomSeedBound = 4294967296; | |
| final random = Random.secure(); | |
| final randomClass = JClass.forName('java/util/Random'); | |
| JObject newRandom() => randomClass | |
| .constructorId('(J)V') | |
| .call(randomClass, [random.nextInt(secureRandomSeedBound)]); | |
| void run({required TestRunnerCallback testRunner}) { | |
| testRunner('Test 4K refs can be created in a row', () { | |
| final list = <JObject>[]; | |
| for (var i = 0; i < k4; i++) { | |
| list.add(newRandom()); | |
| } | |
| for (final jobject in list) { | |
| jobject.release(); | |
| } | |
| }); | |
| testRunner('Create and release 256K references in a loop using arena', () { | |
| for (var i = 0; i < k256; i++) { | |
| using((arena) { | |
| final random = newRandom()..releasedBy(arena); | |
| // The actual expect here does not matter. I am just being paranoid | |
| // against assigning to `_` because compiler may optimize it. (It has | |
| // side effect of calling FFI but still.) | |
| expect(random.reference.pointer, isNot(nullptr)); | |
| }); | |
| } | |
| }); | |
| testRunner('Create and release 256K references in a loop (explicit release)', | |
| () { | |
| for (var i = 0; i < k256; i++) { | |
| final random = newRandom(); | |
| expect(random.reference.pointer, isNot(nullptr)); | |
| random.release(); | |
| } | |
| }); | |
| testRunner('Create and release 64K references, in batches of 256', () { | |
| for (var i = 0; i < 64 * 4; i++) { | |
| using((arena) { | |
| for (var i = 0; i < 256; i++) { | |
| final r = newRandom()..releasedBy(arena); | |
| expect(r.reference.pointer, isNot(nullptr)); | |
| } | |
| }); | |
| } | |
| }); | |
| // We don't have a direct way to check if something creates JNI references. | |
| // So we are checking if we can run this for large number of times. | |
| testRunner('Verify a call returning primitive can be run any times', () { | |
| final random = newRandom(); | |
| final nextInt = randomClass.instanceMethodId('nextInt', '()I'); | |
| for (var i = 0; i < k256; i++) { | |
| final rInt = nextInt(random, jint.type, []); | |
| expect(rInt, isA<int>()); | |
| } | |
| }); | |
| void testFinalizer() { | |
| testRunner('Finalizer correctly removes the references', () { | |
| // We are checking if we can run this for large number of times. | |
| // More than the number of available global references. | |
| for (var i = 0; i < k256; ++i) { | |
| final random = newRandom(); | |
| expect(random.reference.pointer, isNot(nullptr)); | |
| if (i % k4 == 0) { | |
| doGC(); | |
| } | |
| } | |
| }); | |
| } | |
| void testRefValidityAfterGC(int delayInSeconds) { | |
| testRunner('Validate reference after GC & ${delayInSeconds}s sleep', () { | |
| final random = newRandom(); | |
| final nextInt = randomClass.instanceMethodId('nextInt', '()I'); | |
| doGC(); | |
| sleep(Duration(seconds: delayInSeconds)); | |
| expect( | |
| nextInt(random, jint.type, []), | |
| isA<int>(), | |
| ); | |
| expect( | |
| Jni.env.GetObjectRefType(random.reference.pointer), | |
| equals(JObjectRefType.JNIGlobalRefType), | |
| ); | |
| }); | |
| } | |
| // Dart_ExecuteInternalCommand doesn't exist in Android. | |
| if (!Platform.isAndroid) { | |
| testFinalizer(); | |
| testRefValidityAfterGC(1); | |
| testRefValidityAfterGC(10); | |
| } | |
| } |