blob: 9f9359dbddccf71d16874114315aca49cd401b01 [file] [log] [blame]
// Copyright (c) 2020, 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.
// SharedObjects=ffi_test_functions
// VMOptions=--enable-isolate-groups --disable-heap-verification
import 'dart:async';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
import '../../../../../tests/ffi/dylib_utils.dart';
final bool isAOT = Platform.executable.contains('dart_precompiled_runtime');
final sdkRoot = Platform.script.resolve('../../../../../');
class Isolate extends Struct {}
abstract class FfiBindings {
static final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
static final IGH_CreateIsolate = ffiTestFunctions.lookupFunction<
Pointer<Isolate> Function(Pointer<Utf8>, Pointer<Void>),
Pointer<Isolate> Function(
Pointer<Utf8>, Pointer<Void>)>('IGH_CreateIsolate');
static final IGH_StartIsolate = ffiTestFunctions.lookupFunction<
Pointer<Void> Function(Pointer<Isolate>, Int64, Pointer<Utf8>,
Pointer<Utf8>, IntPtr, Int64, Int64),
Pointer<Void> Function(Pointer<Isolate>, int, Pointer<Utf8>,
Pointer<Utf8>, int, int, int)>('IGH_StartIsolate');
static final Dart_CurrentIsolate = DynamicLibrary.executable()
.lookupFunction<Pointer<Isolate> Function(), Pointer<Isolate> Function()>(
"Dart_CurrentIsolate");
static final Dart_IsolateData = DynamicLibrary.executable().lookupFunction<
Pointer<Isolate> Function(Pointer<Isolate>),
Pointer<Isolate> Function(Pointer<Isolate>)>("Dart_IsolateData");
static final Dart_PostInteger = DynamicLibrary.executable()
.lookupFunction<IntPtr Function(Int64, Int64), int Function(int, int)>(
"Dart_PostInteger");
static Pointer<Isolate> createLightweightIsolate(
String name, Pointer<Void> peer) {
final cname = Utf8.toUtf8(name);
try {
final isolate = IGH_CreateIsolate(cname, peer);
Expect.isTrue(isolate.address != 0);
return isolate;
} finally {
free(cname);
}
}
static void invokeTopLevelAndRunLoopAsync(
Pointer<Isolate> isolate, SendPort sendPort, String name,
{bool? errorsAreFatal, SendPort? onError, SendPort? onExit}) {
final dartScript = sdkRoot.resolve(
'runtime/tests/vm/dart/isolates/dart_api_create_lightweight_isolate_test.dart');
final libraryUri = Utf8.toUtf8(dartScript.toString());
final functionName = Utf8.toUtf8(name);
IGH_StartIsolate(
isolate,
sendPort.nativePort,
libraryUri,
functionName,
errorsAreFatal == false ? 0 : 1,
onError != null ? onError.nativePort : 0,
onExit != null ? onExit.nativePort : 0);
free(libraryUri);
free(functionName);
}
}
void scheduleAsyncInvocation(void fun()) {
final rp = RawReceivePort();
rp.handler = (_) {
try {
fun();
} finally {
rp.close();
}
};
rp.sendPort.send(null);
}
Future withPeerPointer(fun(Pointer<Void> peer)) async {
final Pointer<Void> peer = Utf8.toUtf8('abc').cast();
try {
await fun(peer);
} catch (e, s) {
print('Exception: $e\nStack:$s');
rethrow;
} finally {
// The shutdown callback is called before the exit listeners are notified, so
// we can validate that a->x has been changed.
Expect.isTrue(Utf8.fromUtf8(peer.cast()).startsWith('xb'));
// The cleanup callback is called after after notifying exit listeners. So we
// wait a little here to ensure the write of the callback has arrived.
await Future.delayed(const Duration(milliseconds: 100));
Expect.equals('xbz', Utf8.fromUtf8(peer.cast()));
free(peer);
}
}
@pragma('vm:entry-point')
void childTestIsolateData(int mainPort) {
final peerIsolateData =
FfiBindings.Dart_IsolateData(FfiBindings.Dart_CurrentIsolate());
FfiBindings.Dart_PostInteger(mainPort, peerIsolateData.address);
}
Future testIsolateData() async {
await withPeerPointer((Pointer<Void> peer) async {
final rp = ReceivePort();
final exit = ReceivePort();
final isolate = FfiBindings.createLightweightIsolate('debug-name', peer);
FfiBindings.invokeTopLevelAndRunLoopAsync(
isolate, rp.sendPort, 'childTestIsolateData',
onExit: exit.sendPort);
Expect.equals(peer.address, await rp.first);
await exit.first;
exit.close();
rp.close();
});
}
@pragma('vm:entry-point')
void childTestMultipleErrors(int mainPort) {
scheduleAsyncInvocation(() {
for (int i = 0; i < 10; ++i) {
scheduleAsyncInvocation(() => throw 'error-$i');
}
});
}
Future testMultipleErrors() async {
await withPeerPointer((Pointer<Void> peer) async {
final rp = ReceivePort();
final accumulatedErrors = <dynamic>[];
final errors = ReceivePort()..listen(accumulatedErrors.add);
final exit = ReceivePort();
final isolate = FfiBindings.createLightweightIsolate('debug-name', peer);
FfiBindings.invokeTopLevelAndRunLoopAsync(
isolate, rp.sendPort, 'childTestMultipleErrors',
errorsAreFatal: false, onError: errors.sendPort, onExit: exit.sendPort);
await exit.first;
Expect.equals(10, accumulatedErrors.length);
for (int i = 0; i < 10; ++i) {
Expect.equals('error-$i', accumulatedErrors[i][0]);
Expect.isTrue(
accumulatedErrors[i][1].contains('childTestMultipleErrors'));
}
exit.close();
errors.close();
rp.close();
});
}
@pragma('vm:entry-point')
void childTestFatalError(int mainPort) {
scheduleAsyncInvocation(() {
scheduleAsyncInvocation(() => throw 'error-0');
scheduleAsyncInvocation(() => throw 'error-1');
});
}
Future testFatalError() async {
await withPeerPointer((Pointer<Void> peer) async {
final rp = ReceivePort();
final accumulatedErrors = <dynamic>[];
final errors = ReceivePort()..listen(accumulatedErrors.add);
final exit = ReceivePort();
final isolate = FfiBindings.createLightweightIsolate('debug-name', peer);
FfiBindings.invokeTopLevelAndRunLoopAsync(
isolate, rp.sendPort, 'childTestFatalError',
errorsAreFatal: true, onError: errors.sendPort, onExit: exit.sendPort);
await exit.first;
Expect.equals(1, accumulatedErrors.length);
Expect.equals('error-0', accumulatedErrors[0][0]);
Expect.isTrue(accumulatedErrors[0][1].contains('childTestFatalError'));
exit.close();
errors.close();
rp.close();
});
}
Future testAot() async {
await testIsolateData();
await testMultipleErrors();
await testFatalError();
}
Future testJit() async {
dynamic exception;
try {
FfiBindings.createLightweightIsolate('debug-name', Pointer.fromAddress(0));
} catch (e) {
exception = e;
}
Expect.isTrue(exception
.toString()
.contains('Lightweight isolates are not yet ready in JIT mode'));
}
Future main(args) async {
if (isAOT) {
await testAot();
} else {
await testJit();
}
}