blob: 6e0c013f9e867d8667a98e57750a1ae47480083d [file] [edit]
// Copyright (c) 2024, 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.
// Objective C support is only available on mac.
@TestOn('mac-os')
import 'dart:async';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:async/async.dart';
import 'package:ffi/ffi.dart';
import 'package:objective_c/objective_c.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import '../test_utils.dart';
import 'isolate_test_bindings.dart';
import 'util.dart';
void main() {
group('isolate', () {
// Runs on other isolate (can't use expect function).
void sendingObjectTest(SendPort sendPort) async {
final port = ReceivePort();
final queue = StreamQueue(port);
sendPort.send(port.sendPort);
final sendable = await queue.next as Sendable;
final oldValue = sendable.value;
sendable.value = 456;
sendPort.send(oldValue);
port.close();
}
test('Sending object through a port', () async {
await using((arena) async {
final sendableTracker = ReferenceTracker(arena);
Sendable? sendable = Sendable();
sendable.value = 123;
sendableTracker.track(sendable);
final port = ReceivePort();
final queue = StreamQueue(port);
final isolate = await Isolate.spawn(
sendingObjectTest,
port.sendPort,
onExit: port.sendPort,
);
final sendPort = await queue.next as SendPort;
sendPort.send(sendable);
final oldValue = await queue.next;
expect(oldValue, 123);
expect(sendable.value, 456);
expect(sendableTracker.isAlive, true);
expect(await queue.next, null); // onExit
port.close();
sendable = null;
doGC();
expect(sendableTracker.isAlive, false);
});
}, skip: !canDoGC);
test('Capturing object in closure', () async {
await using((arena) async {
final sendableTracker = ReferenceTracker(arena);
Sendable? sendable = Sendable();
sendable.value = 123;
sendableTracker.track(sendable);
final oldValue = await Isolate.run(() {
final oldValue = sendable!.value;
sendable!.value = 456;
return oldValue;
});
expect(oldValue, 123);
expect(sendable.value, 456);
expect(sendableTracker.isAlive, true);
sendable = null;
doGC();
expect(sendableTracker.isAlive, false);
});
}, skip: !canDoGC);
// Runs on other isolate (can't use expect function).
void sendingBlockTest(SendPort sendPort) async {
final port = ReceivePort();
final queue = StreamQueue(port);
sendPort.send(port.sendPort);
final block = await queue.next as ObjCBlock<Void Function(Int32)>;
block(123);
port.close();
}
test('Sending block through a port', () async {
final completer = Completer<int>();
ObjCBlock<Void Function(Int32)>? block = ObjCBlock_ffiVoid_Int32.listener(
(int value) {
completer.complete(value);
},
);
await using((arena) async {
final blockTracker = ReferenceTracker(arena);
blockTracker.trackBlock(block!);
final port = ReceivePort();
final queue = StreamQueue(port);
final isolate = await Isolate.spawn(
sendingBlockTest,
port.sendPort,
onExit: port.sendPort,
);
final sendPort = await queue.next as SendPort;
sendPort.send(block);
final value = await completer.future;
expect(value, 123);
expect(blockTracker.isAlive, true);
expect(await queue.next, null); // onExit
port.close();
block = null;
doGC();
expect(blockTracker.isAlive, false);
});
}, skip: !canDoGC);
@pragma('vm:never-inline')
ObjCBlock<Void Function(Int32)> makeBlock(Completer<int> completer) {
// Creating this block in a separate function to make sure completer is
// not captured in Isolate.run's lambda.
return ObjCBlock_ffiVoid_Int32.listener((int value) {
completer.complete(value);
});
}
@pragma('vm:never-inline')
Future<void> runIsolateWithBlock(ObjCBlock<Void Function(Int32)> block) {
return Isolate.run(() {
block(123);
});
}
test('Capturing block in closure', () async {
final completer = Completer<int>();
ObjCBlock<Void Function(Int32)>? block = makeBlock(completer);
await using((arena) async {
final blockTracker = ReferenceTracker(arena);
blockTracker.trackBlock(block!);
await runIsolateWithBlock(block!);
final value = await completer.future;
expect(value, 123);
expect(blockTracker.isAlive, true);
block = null;
doGC();
expect(blockTracker.isAlive, false);
});
}, skip: !canDoGC);
test('Manual release across isolates', () async {
await using((arena) async {
final sendableTracker = ReferenceTracker(arena);
Sendable? sendable = Sendable();
sendableTracker.track(sendable);
expect(sendableTracker.isAlive, true);
expect(sendable!.ref.isReleased, isFalse);
final (oldIsReleased, newIsReleased) = await Isolate.run(() {
final oldIsReleased = sendable!.ref.isReleased;
sendable!.ref.release();
return (oldIsReleased, sendable!.ref.isReleased);
});
expect(oldIsReleased, isFalse);
expect(newIsReleased, isTrue);
expect(sendable!.ref.isReleased, isTrue);
sendable = null;
doGC();
expect(sendableTracker.isAlive, false);
});
});
test('Use after release and double release', () async {
final sendable = Sendable();
sendable.value = 123;
expect(sendable.ref.isReleased, isFalse);
await Isolate.run(() {
sendable!.ref.release();
});
expect(sendable.ref.isReleased, isTrue);
expect(() => sendable.value, throwsA(isA<UseAfterReleaseError>()));
expect(() => sendable.ref.release(), throwsA(isA<DoubleReleaseError>()));
});
});
}