82ff34d775
This is a *breaking change*. See https://github.com/dart-lang/sdk/issues/53311 Make nativeFunction throw a StateError if the NativeCallable is already closed. Also make close and keepIsolateAlive not throw. I'm not adding an isClosed getter, as discussed on the bug. Bug: https://github.com/dart-lang/sdk/issues/53311 Fixes: https://github.com/dart-lang/sdk/issues/53311 Change-Id: Ib0066352d3cfc01d31df8ae8fd61be426fcdf6e1 CoreLibraryReviewExempt: The FFI package is VM-only TEST=async_void_function_callbacks_test and isolate_local_function_callbacks_test Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/328280 Reviewed-by: Daco Harkes <dacoharkes@google.com> Commit-Queue: Liam Appelbe <liama@google.com>
742 lines
23 KiB
Dart
742 lines
23 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=--stacktrace-every=100
|
|
// VMOptions=--write-protect-code --no-dual-map-code
|
|
// VMOptions=--write-protect-code --no-dual-map-code --stacktrace-every=100
|
|
// VMOptions=--use-slow-path
|
|
// VMOptions=--use-slow-path --stacktrace-every=100
|
|
// VMOptions=--use-slow-path --write-protect-code --no-dual-map-code
|
|
// VMOptions=--use-slow-path --write-protect-code --no-dual-map-code --stacktrace-every=100
|
|
// VMOptions=--dwarf_stack_traces --no-retain_function_objects --no-retain_code_objects
|
|
// VMOptions=--test_il_serialization
|
|
// VMOptions=--profiler
|
|
// SharedObjects=ffi_test_functions
|
|
|
|
import 'dart:async';
|
|
import 'dart:ffi';
|
|
import 'dart:isolate';
|
|
import 'dart:math';
|
|
|
|
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;
|
|
void foo(int a, int b) {
|
|
simpleFunctionResult.complete(a + b + c);
|
|
}
|
|
|
|
final callback = NativeCallable<CallbackNativeType>.listener(foo);
|
|
|
|
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();
|
|
}
|
|
}
|