38e250b4d5
Includes all the files which required no manual treatment. Change-Id: I3d493bcbc238a2016579a0f804d32f4829efb87b Cq-Include-Trybots: dart/try:vm-aot-android-release-arm_x64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-debug-x64c-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try,vm-aot-win-debug-x64c-try,vm-checked-mac-release-arm64-try,vm-ffi-qemu-linux-release-arm-try,vm-ffi-qemu-linux-release-riscv64-try,vm-linux-debug-ia32-try,vm-linux-debug-x64-try,vm-linux-debug-x64c-try,vm-mac-debug-arm64-try,vm-reload-linux-debug-x64-try,vm-reload-rollback-linux-debug-x64-try,vm-win-release-ia32-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/399102 Reviewed-by: Bob Nystrom <rnystrom@google.com> Commit-Queue: Daco Harkes <dacoharkes@google.com>
829 lines
24 KiB
Dart
829 lines
24 KiB
Dart
// Copyright (c) 2023, 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.
|
|
|
|
// Dart test program for testing dart:ffi async callbacks.
|
|
//
|
|
// VMOptions=
|
|
// VMOptions=--use-slow-path
|
|
// VMOptions=--use-slow-path --stacktrace-every=100
|
|
// VMOptions=--dwarf_stack_traces --no-retain_function_objects --no-retain_code_objects
|
|
// VMOptions=--test_il_serialization
|
|
// VMOptions=--profiler --profile_vm=true
|
|
// VMOptions=--profiler --profile_vm=false
|
|
// SharedObjects=ffi_test_functions
|
|
|
|
import 'dart:async';
|
|
import 'dart:ffi';
|
|
import 'dart:isolate';
|
|
|
|
import 'dart:io';
|
|
|
|
import "package:expect/expect.dart";
|
|
|
|
import 'dylib_utils.dart';
|
|
|
|
main(args, message) async {
|
|
if (message != null) {
|
|
// We've been spawned by Isolate.spawnUri. Run IsolateB.
|
|
await IsolateB.entryPoint(message);
|
|
return;
|
|
}
|
|
|
|
// Simple tests.
|
|
await testNativeCallableHelloWorld();
|
|
testNativeCallableDoubleCloseError();
|
|
await testNativeCallableUseAfterFree();
|
|
await testNativeCallableNestedCloseCall();
|
|
await testNativeCallableThrowInsideCallback();
|
|
await testNativeCallableDontKeepAlive();
|
|
testNativeCallableKeepAliveGetter();
|
|
await testNativeCallableClosure();
|
|
|
|
// Message passing tests.
|
|
globalVar = 1000;
|
|
for (final sameGroup in [true, false]) {
|
|
final isolateA = IsolateA(sameGroup);
|
|
await isolateA.messageLoop();
|
|
isolateA.close();
|
|
}
|
|
print("All tests completed :)");
|
|
}
|
|
|
|
final List<TestCase> messagePassingTestCases = [
|
|
SanityCheck(),
|
|
CallFromIsoAToIsoB(),
|
|
CallFromIsoBToIsoA(),
|
|
CallFromIsoAToIsoBViaNewThreadBlocking(),
|
|
CallFromIsoBToIsoAViaNewThreadBlocking(),
|
|
CallFromIsoAToIsoBViaNewThreadNonBlocking(),
|
|
CallFromIsoBToIsoAViaNewThreadNonBlocking(),
|
|
CallFromIsoAToBToA(),
|
|
CallFromIsoBToAToB(),
|
|
ManyCallsBetweenIsolates(),
|
|
ManyCallsBetweenIsolatesViaNewThreadBlocking(),
|
|
ManyCallsBetweenIsolatesViaNewThreadNonBlocking(),
|
|
];
|
|
|
|
var simpleFunctionResult = Completer<int>();
|
|
void simpleFunction(int a, int b) {
|
|
simpleFunctionResult.complete(a + b);
|
|
}
|
|
|
|
Future<void> testNativeCallableHelloWorld() async {
|
|
final lib = NativeLibrary();
|
|
final callback = NativeCallable<CallbackNativeType>.listener(simpleFunction);
|
|
|
|
simpleFunctionResult = Completer<int>();
|
|
lib.callFunctionOnSameThread(1000, callback.nativeFunction);
|
|
|
|
Expect.equals(1123, await simpleFunctionResult.future);
|
|
callback.close();
|
|
}
|
|
|
|
testNativeCallableDoubleCloseError() {
|
|
final callback = NativeCallable<CallbackNativeType>.listener(simpleFunction);
|
|
Expect.notEquals(nullptr, callback.nativeFunction);
|
|
callback.close();
|
|
|
|
Expect.throwsStateError(() {
|
|
final _ = callback.nativeFunction;
|
|
});
|
|
|
|
// Expect that these do not throw.
|
|
callback.close();
|
|
callback.keepIsolateAlive = true;
|
|
Expect.isFalse(callback.keepIsolateAlive);
|
|
}
|
|
|
|
Future<void> testNativeCallableUseAfterFree() async {
|
|
final lib = NativeLibrary();
|
|
|
|
final callback = NativeCallable<CallbackNativeType>.listener(simpleFunction);
|
|
final nativeFunction = callback.nativeFunction;
|
|
callback.close();
|
|
|
|
simpleFunctionResult = Completer<int>();
|
|
lib.callFunctionOnSameThread(123, nativeFunction);
|
|
|
|
await Future.delayed(Duration(milliseconds: 100));
|
|
|
|
// The callback wasn't invoked, but we didn't crash either.
|
|
Expect.equals(false, simpleFunctionResult.isCompleted);
|
|
}
|
|
|
|
NativeCallable? simpleFunctionAndCloseSelf_callable;
|
|
void simpleFunctionAndCloseSelf(int a, int b) {
|
|
simpleFunctionAndCloseSelf_callable!.close();
|
|
simpleFunctionResult.complete(a + b);
|
|
}
|
|
|
|
Future<void> testNativeCallableNestedCloseCall() async {
|
|
final lib = NativeLibrary();
|
|
simpleFunctionAndCloseSelf_callable =
|
|
NativeCallable<CallbackNativeType>.listener(simpleFunctionAndCloseSelf);
|
|
|
|
simpleFunctionResult = Completer<int>();
|
|
lib.callFunctionOnSameThread(
|
|
1000,
|
|
simpleFunctionAndCloseSelf_callable!.nativeFunction,
|
|
);
|
|
|
|
Expect.equals(1123, await simpleFunctionResult.future);
|
|
|
|
// The callback is already closed.
|
|
Expect.throwsStateError(() {
|
|
final _ = simpleFunctionAndCloseSelf_callable!.nativeFunction;
|
|
});
|
|
}
|
|
|
|
void simpleFunctionThrows(int a, int b) {
|
|
throw a + b;
|
|
}
|
|
|
|
Future<void> testNativeCallableThrowInsideCallback() async {
|
|
final lib = NativeLibrary();
|
|
var caughtError;
|
|
late final callback;
|
|
|
|
runZonedGuarded(
|
|
() {
|
|
callback = NativeCallable<CallbackNativeType>.listener(
|
|
simpleFunctionThrows,
|
|
);
|
|
},
|
|
(Object error, StackTrace stack) {
|
|
caughtError = error;
|
|
},
|
|
);
|
|
|
|
lib.callFunctionOnSameThread(1000, callback.nativeFunction);
|
|
await Future.delayed(Duration(milliseconds: 100));
|
|
|
|
Expect.equals(1123, caughtError);
|
|
|
|
callback.close();
|
|
}
|
|
|
|
Future<void> testNativeCallableDontKeepAlive() async {
|
|
final exitPort = ReceivePort();
|
|
await Isolate.spawn(
|
|
(_) async {
|
|
final lib = NativeLibrary();
|
|
final callback = NativeCallable<CallbackNativeType>.listener(
|
|
simpleFunction,
|
|
);
|
|
|
|
simpleFunctionResult = Completer<int>();
|
|
lib.callFunctionOnSameThread(1000, callback.nativeFunction);
|
|
|
|
Expect.equals(1123, await simpleFunctionResult.future);
|
|
callback.keepIsolateAlive = false;
|
|
},
|
|
null,
|
|
onExit: exitPort.sendPort,
|
|
);
|
|
await exitPort.first;
|
|
exitPort.close();
|
|
}
|
|
|
|
testNativeCallableKeepAliveGetter() {
|
|
final callback = NativeCallable<CallbackNativeType>.listener(simpleFunction);
|
|
Expect.isTrue(callback.keepIsolateAlive);
|
|
callback.keepIsolateAlive = false;
|
|
Expect.isFalse(callback.keepIsolateAlive);
|
|
callback.keepIsolateAlive = true;
|
|
Expect.isTrue(callback.keepIsolateAlive);
|
|
callback.close();
|
|
}
|
|
|
|
Future<void> testNativeCallableClosure() async {
|
|
final lib = NativeLibrary();
|
|
int c = 70000;
|
|
|
|
final callback = NativeCallable<CallbackNativeType>.listener((int a, int b) {
|
|
simpleFunctionResult.complete(a + b + c);
|
|
});
|
|
|
|
simpleFunctionResult = Completer<int>();
|
|
lib.callFunctionOnSameThread(1000, callback.nativeFunction);
|
|
Expect.equals(71123, await simpleFunctionResult.future);
|
|
|
|
c = 80000;
|
|
simpleFunctionResult = Completer<int>();
|
|
lib.callFunctionOnSameThread(4000, callback.nativeFunction);
|
|
Expect.equals(84123, await simpleFunctionResult.future);
|
|
|
|
callback.close();
|
|
}
|
|
|
|
final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
|
|
|
|
// Global variable that is 1000 on isolate A, and 2000 on isolate B.
|
|
late final int globalVar;
|
|
|
|
class SanityCheck extends TestCase {
|
|
@override
|
|
SendPort? sendIsoAResultsTo(AsyncTestManager atm) => atm.toIsoA;
|
|
|
|
@override
|
|
SendPort? sendIsoBResultsTo(AsyncTestManager atm) => atm.toIsoB;
|
|
|
|
@override
|
|
Future<void> runOnIsoA(IsolateA iso) async {
|
|
Expect.equals(1000, globalVar);
|
|
final result = iso.atm.call(
|
|
(responseId) => iso.natLib.callFunctionOnSameThread(
|
|
responseId,
|
|
Pointer.fromAddress(iso.fnPtrsA.addGlobalVarPtr),
|
|
),
|
|
);
|
|
print("SanityCheck.runOnIsoA message sent. Awaiting result...");
|
|
Expect.equals(1123, await result);
|
|
}
|
|
|
|
@override
|
|
Future<void> runOnIsoB(IsolateB iso) async {
|
|
Expect.equals(2000, globalVar);
|
|
final result = iso.atm.call(
|
|
(responseId) => iso.natLib.callFunctionOnSameThread(
|
|
responseId,
|
|
Pointer.fromAddress(iso.fnPtrsB.addGlobalVarPtr),
|
|
),
|
|
);
|
|
print("SanityCheck.runOnIsoB message sent. Awaiting result...");
|
|
Expect.equals(2123, await result);
|
|
}
|
|
}
|
|
|
|
class CallFromIsoAToIsoB extends TestCase {
|
|
@override
|
|
SendPort? sendIsoBResultsTo(AsyncTestManager atm) => atm.toIsoA;
|
|
|
|
@override
|
|
Future<void> runOnIsoA(IsolateA iso) async {
|
|
final result = iso.atm.call(
|
|
(responseId) => iso.natLib.callFunctionOnSameThread(
|
|
responseId,
|
|
Pointer.fromAddress(iso.fnPtrsB.addGlobalVarPtr),
|
|
),
|
|
);
|
|
print("CallFromIsoAToIsoB.runOnIsoA message sent. Awaiting result...");
|
|
Expect.equals(2123, await result);
|
|
}
|
|
}
|
|
|
|
class CallFromIsoBToIsoA extends TestCase {
|
|
@override
|
|
SendPort? sendIsoAResultsTo(AsyncTestManager atm) => atm.toIsoB;
|
|
|
|
@override
|
|
Future<void> runOnIsoB(IsolateB iso) async {
|
|
final result = iso.atm.call(
|
|
(responseId) => iso.natLib.callFunctionOnSameThread(
|
|
responseId,
|
|
Pointer.fromAddress(iso.fnPtrsA.addGlobalVarPtr),
|
|
),
|
|
);
|
|
print("CallFromIsoBToIsoA.runOnIsoB message sent. Awaiting result...");
|
|
Expect.equals(1123, await result);
|
|
}
|
|
}
|
|
|
|
class CallFromIsoAToIsoBViaNewThreadBlocking extends TestCase {
|
|
@override
|
|
SendPort? sendIsoBResultsTo(AsyncTestManager atm) => atm.toIsoA;
|
|
|
|
@override
|
|
Future<void> runOnIsoA(IsolateA iso) async {
|
|
final result = iso.atm.call(
|
|
(responseId) => iso.natLib.callFunctionOnNewThreadBlocking(
|
|
responseId,
|
|
Pointer.fromAddress(iso.fnPtrsB.addGlobalVarPtr),
|
|
),
|
|
);
|
|
print(
|
|
"CallFromIsoAToIsoBViaNewThreadBlocking.runOnIsoA message sent. Awaiting result...",
|
|
);
|
|
Expect.equals(2123, await result);
|
|
}
|
|
}
|
|
|
|
class CallFromIsoBToIsoAViaNewThreadBlocking extends TestCase {
|
|
@override
|
|
SendPort? sendIsoAResultsTo(AsyncTestManager atm) => atm.toIsoB;
|
|
|
|
@override
|
|
Future<void> runOnIsoB(IsolateB iso) async {
|
|
final result = iso.atm.call(
|
|
(responseId) => iso.natLib.callFunctionOnNewThreadBlocking(
|
|
responseId,
|
|
Pointer.fromAddress(iso.fnPtrsA.addGlobalVarPtr),
|
|
),
|
|
);
|
|
print(
|
|
"CallFromIsoBToIsoAViaNewThreadBlocking.runOnIsoB message sent. Awaiting result...",
|
|
);
|
|
Expect.equals(1123, await result);
|
|
}
|
|
}
|
|
|
|
class CallFromIsoAToIsoBViaNewThreadNonBlocking extends TestCase {
|
|
@override
|
|
SendPort? sendIsoBResultsTo(AsyncTestManager atm) => atm.toIsoA;
|
|
|
|
@override
|
|
Future<void> runOnIsoA(IsolateA iso) async {
|
|
final result = iso.atm.call(
|
|
(responseId) => iso.natLib.callFunctionOnNewThreadNonBlocking(
|
|
responseId,
|
|
Pointer.fromAddress(iso.fnPtrsB.addGlobalVarPtr),
|
|
),
|
|
);
|
|
print(
|
|
"CallFromIsoAToIsoBViaNewThreadNonBlocking.runOnIsoA message sent. Awaiting result...",
|
|
);
|
|
Expect.equals(2123, await result);
|
|
}
|
|
}
|
|
|
|
class CallFromIsoBToIsoAViaNewThreadNonBlocking extends TestCase {
|
|
@override
|
|
SendPort? sendIsoAResultsTo(AsyncTestManager atm) => atm.toIsoB;
|
|
|
|
@override
|
|
Future<void> runOnIsoB(IsolateB iso) async {
|
|
final result = iso.atm.call(
|
|
(responseId) => iso.natLib.callFunctionOnNewThreadNonBlocking(
|
|
responseId,
|
|
Pointer.fromAddress(iso.fnPtrsA.addGlobalVarPtr),
|
|
),
|
|
);
|
|
print(
|
|
"CallFromIsoBToIsoAViaNewThreadNonBlocking.runOnIsoB message sent. Awaiting result...",
|
|
);
|
|
Expect.equals(1123, await result);
|
|
}
|
|
}
|
|
|
|
class CallFromIsoAToBToA extends TestCase {
|
|
@override
|
|
SendPort? sendIsoAResultsTo(AsyncTestManager atm) => atm.toIsoB;
|
|
|
|
@override
|
|
SendPort? sendIsoBResultsTo(AsyncTestManager atm) => atm.toIsoA;
|
|
|
|
@override
|
|
Future<void> runOnIsoA(IsolateA iso) async {
|
|
final result = iso.atm.call(
|
|
(responseId) => iso.natLib.callFunctionOnSameThread(
|
|
responseId,
|
|
Pointer.fromAddress(iso.fnPtrsB.callFromIsoBToAAndMultByGlobalVarPtr),
|
|
),
|
|
);
|
|
print("CallFromIsoAToBToA.runOnIsoA message sent. Awaiting result...");
|
|
Expect.equals(2000 * 1123, await result);
|
|
}
|
|
}
|
|
|
|
class CallFromIsoBToAToB extends TestCase {
|
|
@override
|
|
SendPort? sendIsoAResultsTo(AsyncTestManager atm) => atm.toIsoB;
|
|
|
|
@override
|
|
SendPort? sendIsoBResultsTo(AsyncTestManager atm) => atm.toIsoA;
|
|
|
|
@override
|
|
Future<void> runOnIsoB(IsolateB iso) async {
|
|
final result = iso.atm.call(
|
|
(responseId) => iso.natLib.callFunctionOnSameThread(
|
|
responseId,
|
|
Pointer.fromAddress(iso.fnPtrsA.callFromIsoAToBAndMultByGlobalVarPtr),
|
|
),
|
|
);
|
|
print("CallFromIsoBToAToB.runOnIsoB message sent. Awaiting result...");
|
|
Expect.equals(1000 * 2123, await result);
|
|
}
|
|
}
|
|
|
|
class ManyCallsBetweenIsolates extends TestCase {
|
|
@override
|
|
SendPort? sendIsoAResultsTo(AsyncTestManager atm) => atm.toIsoB;
|
|
|
|
@override
|
|
SendPort? sendIsoBResultsTo(AsyncTestManager atm) => atm.toIsoA;
|
|
|
|
@override
|
|
Future<void> runOnIsoA(IsolateA iso) async {
|
|
print("ManyCallsBetweenIsolates.runOnIsoA sending messages.");
|
|
await Future.wait(
|
|
List.filled(100, null).map((_) async {
|
|
final result = iso.atm.call(
|
|
(responseId) => iso.natLib.callFunctionOnSameThread(
|
|
responseId,
|
|
Pointer.fromAddress(iso.fnPtrsB.addGlobalVarPtr),
|
|
),
|
|
);
|
|
Expect.equals(2123, await result);
|
|
}),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> runOnIsoB(IsolateB iso) async {
|
|
print("ManyCallsBetweenIsolates.runOnIsoB sending messages.");
|
|
await Future.wait(
|
|
List.filled(100, null).map((_) async {
|
|
final result = iso.atm.call(
|
|
(responseId) => iso.natLib.callFunctionOnSameThread(
|
|
responseId,
|
|
Pointer.fromAddress(iso.fnPtrsA.addGlobalVarPtr),
|
|
),
|
|
);
|
|
Expect.equals(1123, await result);
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
class ManyCallsBetweenIsolatesViaNewThreadBlocking extends TestCase {
|
|
@override
|
|
SendPort? sendIsoAResultsTo(AsyncTestManager atm) => atm.toIsoB;
|
|
|
|
@override
|
|
SendPort? sendIsoBResultsTo(AsyncTestManager atm) => atm.toIsoA;
|
|
|
|
@override
|
|
Future<void> runOnIsoA(IsolateA iso) async {
|
|
print(
|
|
"ManyCallsBetweenIsolatesViaNewThreadBlocking.runOnIsoA sending messages.",
|
|
);
|
|
await Future.wait(
|
|
List.filled(100, null).map((_) async {
|
|
final result = iso.atm.call(
|
|
(responseId) => iso.natLib.callFunctionOnNewThreadBlocking(
|
|
responseId,
|
|
Pointer.fromAddress(iso.fnPtrsB.addGlobalVarPtr),
|
|
),
|
|
);
|
|
Expect.equals(2123, await result);
|
|
}),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> runOnIsoB(IsolateB iso) async {
|
|
print(
|
|
"ManyCallsBetweenIsolatesViaNewThreadBlocking.runOnIsoB sending messages.",
|
|
);
|
|
await Future.wait(
|
|
List.filled(100, null).map((_) async {
|
|
final result = iso.atm.call(
|
|
(responseId) => iso.natLib.callFunctionOnNewThreadBlocking(
|
|
responseId,
|
|
Pointer.fromAddress(iso.fnPtrsA.addGlobalVarPtr),
|
|
),
|
|
);
|
|
Expect.equals(1123, await result);
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
class ManyCallsBetweenIsolatesViaNewThreadNonBlocking extends TestCase {
|
|
@override
|
|
SendPort? sendIsoAResultsTo(AsyncTestManager atm) => atm.toIsoB;
|
|
|
|
@override
|
|
SendPort? sendIsoBResultsTo(AsyncTestManager atm) => atm.toIsoA;
|
|
|
|
@override
|
|
Future<void> runOnIsoA(IsolateA iso) async {
|
|
print(
|
|
"ManyCallsBetweenIsolatesViaNewThreadNonBlocking.runOnIsoA sending messages.",
|
|
);
|
|
await Future.wait(
|
|
List.filled(100, null).map((_) async {
|
|
final result = iso.atm.call(
|
|
(responseId) => iso.natLib.callFunctionOnNewThreadNonBlocking(
|
|
responseId,
|
|
Pointer.fromAddress(iso.fnPtrsB.addGlobalVarPtr),
|
|
),
|
|
);
|
|
Expect.equals(2123, await result);
|
|
}),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> runOnIsoB(IsolateB iso) async {
|
|
print(
|
|
"ManyCallsBetweenIsolatesViaNewThreadNonBlocking.runOnIsoB sending messages.",
|
|
);
|
|
await Future.wait(
|
|
List.filled(100, null).map((_) async {
|
|
final result = iso.atm.call(
|
|
(responseId) => iso.natLib.callFunctionOnNewThreadNonBlocking(
|
|
responseId,
|
|
Pointer.fromAddress(iso.fnPtrsA.addGlobalVarPtr),
|
|
),
|
|
);
|
|
Expect.equals(1123, await result);
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
class AsyncTestManager {
|
|
int _lastResponseId = 0;
|
|
final _pending = <int, Completer<Object>>{};
|
|
final _recvPort = ReceivePort("AsyncTestManager");
|
|
|
|
late final SendPort toIsoA;
|
|
late final SendPort toIsoB;
|
|
SendPort get toThis => _recvPort.sendPort;
|
|
|
|
AsyncTestManager(this._lastResponseId) {
|
|
_recvPort.listen((msg) {
|
|
final response = msg as List;
|
|
final id = response[0];
|
|
final value = response[1];
|
|
_pending[id]!.complete(value);
|
|
_pending.remove(id);
|
|
});
|
|
}
|
|
|
|
Future<Object> call(void Function(int) asyncFunc) {
|
|
final responseId = ++_lastResponseId;
|
|
final completer = Completer<Object>();
|
|
_pending[responseId] = completer;
|
|
asyncFunc(responseId);
|
|
return completer.future;
|
|
}
|
|
|
|
void close() {
|
|
_recvPort.close();
|
|
}
|
|
}
|
|
|
|
SendPort? _callbackResultPort;
|
|
|
|
void addGlobalVar(int responseId, int x) {
|
|
final result = x + globalVar;
|
|
_callbackResultPort!.send([responseId, result]);
|
|
}
|
|
|
|
void callFromIsoBToAAndMultByGlobalVar(int responseIdToA, int x) {
|
|
final iso = IsolateB.instance!;
|
|
iso.atm
|
|
.call(
|
|
(responseIdToB) => iso.natLib.callFunctionOnSameThread(
|
|
responseIdToB,
|
|
Pointer.fromAddress(iso.fnPtrsA.addGlobalVarPtr),
|
|
),
|
|
)
|
|
.then((response) {
|
|
final result = (response as int) * globalVar;
|
|
_callbackResultPort!.send([responseIdToA, result]);
|
|
});
|
|
print("callFromIsoBToAAndMultByGlobalVar message sent. Awaiting result...");
|
|
}
|
|
|
|
void callFromIsoAToBAndMultByGlobalVar(int responseIdToB, int x) {
|
|
final iso = IsolateA.instance!;
|
|
iso.atm
|
|
.call(
|
|
(responseIdToA) => iso.natLib.callFunctionOnSameThread(
|
|
responseIdToA,
|
|
Pointer.fromAddress(iso.fnPtrsB.addGlobalVarPtr),
|
|
),
|
|
)
|
|
.then((response) {
|
|
final result = (response as int) * globalVar;
|
|
_callbackResultPort!.send([responseIdToB, result]);
|
|
});
|
|
print("callFromIsoAToBAndMultByGlobalVar message sent. Awaiting result...");
|
|
}
|
|
|
|
typedef CallbackNativeType = Void Function(Int64, Int32);
|
|
|
|
class Callbacks {
|
|
final NativeCallable addGlobalVarFn;
|
|
final NativeCallable callFromIsoBToAAndMultByGlobalVarFn;
|
|
final NativeCallable callFromIsoAToBAndMultByGlobalVarFn;
|
|
|
|
Callbacks()
|
|
: addGlobalVarFn = NativeCallable<CallbackNativeType>.listener(
|
|
addGlobalVar,
|
|
),
|
|
callFromIsoBToAAndMultByGlobalVarFn =
|
|
NativeCallable<CallbackNativeType>.listener(
|
|
callFromIsoBToAAndMultByGlobalVar,
|
|
),
|
|
callFromIsoAToBAndMultByGlobalVarFn =
|
|
NativeCallable<CallbackNativeType>.listener(
|
|
callFromIsoAToBAndMultByGlobalVar,
|
|
);
|
|
|
|
void close() {
|
|
addGlobalVarFn.close();
|
|
callFromIsoBToAAndMultByGlobalVarFn.close();
|
|
callFromIsoAToBAndMultByGlobalVarFn.close();
|
|
}
|
|
}
|
|
|
|
class FnPtrs {
|
|
// Storing function pointers as ints so they can be sent to other isolates.
|
|
final int addGlobalVarPtr;
|
|
final int callFromIsoBToAAndMultByGlobalVarPtr;
|
|
final int callFromIsoAToBAndMultByGlobalVarPtr;
|
|
|
|
FnPtrs._(
|
|
this.addGlobalVarPtr,
|
|
this.callFromIsoBToAAndMultByGlobalVarPtr,
|
|
this.callFromIsoAToBAndMultByGlobalVarPtr,
|
|
);
|
|
|
|
static FnPtrs fromCallbacks(Callbacks callbacks) => FnPtrs._(
|
|
callbacks.addGlobalVarFn.nativeFunction.address,
|
|
callbacks.callFromIsoBToAAndMultByGlobalVarFn.nativeFunction.address,
|
|
callbacks.callFromIsoAToBAndMultByGlobalVarFn.nativeFunction.address,
|
|
);
|
|
|
|
static FnPtrs fromList(List<int> ptrs) => FnPtrs._(ptrs[0], ptrs[1], ptrs[2]);
|
|
List<int> toList() => [
|
|
addGlobalVarPtr,
|
|
callFromIsoBToAAndMultByGlobalVarPtr,
|
|
callFromIsoAToBAndMultByGlobalVarPtr,
|
|
];
|
|
}
|
|
|
|
typedef FnRunnerNativeType = Void Function(Int64, Pointer);
|
|
typedef FnRunnerType = void Function(int, Pointer);
|
|
|
|
class NativeLibrary {
|
|
late final FnRunnerType callFunctionOnSameThread;
|
|
late final FnRunnerType callFunctionOnNewThreadBlocking;
|
|
late final FnRunnerType callFunctionOnNewThreadNonBlocking;
|
|
|
|
NativeLibrary() {
|
|
callFunctionOnSameThread = ffiTestFunctions
|
|
.lookupFunction<FnRunnerNativeType, FnRunnerType>(
|
|
"CallFunctionOnSameThread",
|
|
);
|
|
callFunctionOnNewThreadBlocking = ffiTestFunctions
|
|
.lookupFunction<FnRunnerNativeType, FnRunnerType>(
|
|
"CallFunctionOnNewThreadBlocking",
|
|
);
|
|
callFunctionOnNewThreadNonBlocking = ffiTestFunctions
|
|
.lookupFunction<FnRunnerNativeType, FnRunnerType>(
|
|
"CallFunctionOnNewThreadNonBlocking",
|
|
);
|
|
}
|
|
}
|
|
|
|
class TestCase {
|
|
SendPort? sendIsoAResultsTo(AsyncTestManager atm) => null;
|
|
SendPort? sendIsoBResultsTo(AsyncTestManager atm) => null;
|
|
Future<void> runOnIsoA(IsolateA isoA) async {}
|
|
Future<void> runOnIsoB(IsolateB isoB) async {}
|
|
}
|
|
|
|
class TestCaseSendPort {
|
|
final SendPort sendPort;
|
|
TestCaseSendPort(this.sendPort);
|
|
}
|
|
|
|
// IsolateA is the main isolate of the test. It spawns IsolateB.
|
|
class IsolateA {
|
|
static IsolateA? instance;
|
|
late final SendPort sendPort;
|
|
final recvPort = ReceivePort("Isolate A ReceivePort");
|
|
final atm = AsyncTestManager(1000000);
|
|
final natLib = NativeLibrary();
|
|
final callbacksA = Callbacks();
|
|
late final FnPtrs fnPtrsA;
|
|
late final FnPtrs fnPtrsB;
|
|
final bool sameGroup;
|
|
|
|
IsolateA(this.sameGroup) {
|
|
instance = this;
|
|
fnPtrsA = FnPtrs.fromCallbacks(callbacksA);
|
|
atm.toIsoA = atm.toThis;
|
|
}
|
|
|
|
Future<void> messageLoop() async {
|
|
if (sameGroup) {
|
|
await Isolate.spawn(IsolateB.entryPoint, recvPort.sendPort);
|
|
} else {
|
|
await Isolate.spawnUri(Platform.script, [], recvPort.sendPort);
|
|
}
|
|
int testIndex = 0;
|
|
await for (final List msg in recvPort) {
|
|
final cmd = msg[0] as String;
|
|
final arg = msg[1];
|
|
if (cmd == 'sendPort') {
|
|
sendPort = arg;
|
|
sendPort.send(['testPort', atm.toThis]);
|
|
sendPort.send(['fnPtrs', fnPtrsA.toList()]);
|
|
} else if (cmd == 'fnPtrs') {
|
|
fnPtrsB = FnPtrs.fromList(arg);
|
|
} else if (cmd == 'testPort') {
|
|
atm.toIsoB = arg;
|
|
} else if (cmd == 'next') {
|
|
if (testIndex >= messagePassingTestCases.length) {
|
|
sendPort.send(['exit', null]);
|
|
} else {
|
|
_callbackResultPort = null;
|
|
sendPort.send(['testCase', testIndex]);
|
|
final testCase = messagePassingTestCases[testIndex];
|
|
print('\nRunning $testCase on IsoA');
|
|
_callbackResultPort = testCase.sendIsoAResultsTo(atm);
|
|
}
|
|
} else if (cmd == 'run') {
|
|
final testCase = messagePassingTestCases[testIndex];
|
|
await testCase.runOnIsoA(this);
|
|
print('Running $testCase on IsoA DONE\n');
|
|
testIndex += 1;
|
|
sendPort.send(['next', null]);
|
|
} else if (cmd == 'exit') {
|
|
break;
|
|
} else {
|
|
Expect.fail('Unknown message: $msg');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void close() {
|
|
print("Closing Isolate A");
|
|
recvPort.close();
|
|
atm.close();
|
|
callbacksA.close();
|
|
}
|
|
}
|
|
|
|
// IsolateB is the secondary isolate of the test. It's spawned by IsolateA.
|
|
class IsolateB {
|
|
static IsolateB? instance;
|
|
final SendPort sendPort;
|
|
final recvPort = ReceivePort("Isolate B ReceivePort");
|
|
final atm = AsyncTestManager(2000000);
|
|
final natLib = NativeLibrary();
|
|
final callbacksB = Callbacks();
|
|
late final FnPtrs fnPtrsA;
|
|
late final FnPtrs fnPtrsB;
|
|
|
|
IsolateB(this.sendPort) {
|
|
instance = this;
|
|
fnPtrsB = FnPtrs.fromCallbacks(callbacksB);
|
|
atm.toIsoB = atm.toThis;
|
|
sendPort.send(['sendPort', recvPort.sendPort]);
|
|
sendPort.send(['testPort', atm.toThis]);
|
|
sendPort.send(['fnPtrs', fnPtrsB.toList()]);
|
|
sendPort.send(['next', null]);
|
|
}
|
|
|
|
Future<void> messageLoop() async {
|
|
await for (final List msg in recvPort) {
|
|
final cmd = msg[0] as String;
|
|
final arg = msg[1];
|
|
if (cmd == 'fnPtrs') {
|
|
fnPtrsA = FnPtrs.fromList(arg);
|
|
} else if (cmd == 'testPort') {
|
|
atm.toIsoA = arg;
|
|
} else if (cmd == 'testCase') {
|
|
final testCase = messagePassingTestCases[arg];
|
|
_callbackResultPort = testCase.sendIsoBResultsTo(atm);
|
|
sendPort.send(['run', null]);
|
|
print('\nRunning $testCase on IsoB');
|
|
await testCase.runOnIsoB(this);
|
|
print('Running $testCase on IsoB DONE\n');
|
|
} else if (cmd == 'next') {
|
|
_callbackResultPort = null;
|
|
sendPort.send(['next', null]);
|
|
} else if (cmd == 'exit') {
|
|
sendPort.send(['exit', null]);
|
|
break;
|
|
} else {
|
|
Expect.fail('Unknown message: $msg');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void close() {
|
|
print("Closing Isolate B");
|
|
recvPort.close();
|
|
atm.close();
|
|
callbacksB.close();
|
|
}
|
|
|
|
static Future<void> entryPoint(SendPort sendPort) async {
|
|
globalVar = 2000;
|
|
final isolateB = IsolateB(sendPort);
|
|
await isolateB.messageLoop();
|
|
isolateB.close();
|
|
}
|
|
}
|