[vm/sendandexit] Introduce Isolate.exit([port, message]).

This exits current isolate immediately. If [port] is provided, then [message] is verified and sent out as isolate exits.

TEST=ci
Fixes https://github.com/dart-lang/sdk/issues/47164

Change-Id: I513f4d7ceb5d74820f4aee60f5799b7b5193f2e4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/214312
Reviewed-by: Nate Bosch <nbosch@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
This commit is contained in:
Alexander Aprelev 2021-09-30 20:20:24 +00:00 committed by commit-bot@chromium.org
parent ead8b7f6ac
commit 56403a0d35
22 changed files with 97 additions and 98 deletions

View file

@ -10,8 +10,6 @@ import 'dart:typed_data';
import 'package:benchmark_harness/benchmark_harness.dart' show BenchmarkBase;
import 'runtime/tests/vm/dart/export_sendAndExit_helper.dart' show sendAndExit;
class JsonDecodingBenchmark {
JsonDecodingBenchmark(this.name,
{required this.sample,
@ -80,7 +78,7 @@ Future<Map> decodeJson(bool useSendAndExit, Uint8List encodedJson) async {
Future<void> jsonDecodingIsolate(JsonDecodeRequest request) async {
final result = json.decode(utf8.decode(request.encodedJson));
if (request.useSendAndExit) {
sendAndExit(request.sendPort, result);
Isolate.exit(request.sendPort, result);
} else {
request.sendPort.send(result);
}

View file

@ -1 +0,0 @@
export 'dart:_internal' show sendAndExit;

View file

@ -13,8 +13,6 @@ import 'dart:typed_data';
import 'package:benchmark_harness/benchmark_harness.dart' show BenchmarkBase;
import 'package:meta/meta.dart';
import 'runtime/tests/vm/dart/export_sendAndExit_helper.dart' show sendAndExit;
class JsonDecodingBenchmark {
JsonDecodingBenchmark(this.name,
{@required this.sample,
@ -83,7 +81,7 @@ Future<Map> decodeJson(bool useSendAndExit, Uint8List encodedJson) async {
Future<void> jsonDecodingIsolate(JsonDecodeRequest request) async {
final result = json.decode(utf8.decode(request.encodedJson));
if (request.useSendAndExit) {
sendAndExit(request.sendPort, result);
Isolate.exit(request.sendPort, result);
} else {
request.sendPort.send(result);
}

View file

@ -1 +0,0 @@
export 'dart:_internal' show sendAndExit;

View file

@ -272,36 +272,37 @@ static ObjectPtr ValidateMessageObject(Zone* zone,
return obj.ptr();
}
DEFINE_NATIVE_ENTRY(SendPortImpl_sendAndExitInternal_, 0, 2) {
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
if (!PortMap::IsReceiverInThisIsolateGroup(port.Id(), isolate->group())) {
const auto& error =
String::Handle(String::New("sendAndExit is only supported across "
"isolates spawned via spawnFunction."));
Exceptions::ThrowArgumentError(error);
UNREACHABLE();
}
DEFINE_NATIVE_ENTRY(Isolate_exit_, 0, 2) {
GET_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
if (!port.IsNull()) {
GET_NATIVE_ARGUMENT(Instance, obj, arguments->NativeArgAt(1));
if (!PortMap::IsReceiverInThisIsolateGroup(port.Id(), isolate->group())) {
const auto& error =
String::Handle(String::New("exit with final message is only allowed "
"for isolates in one isolate group."));
Exceptions::ThrowArgumentError(error);
UNREACHABLE();
}
GET_NON_NULL_NATIVE_ARGUMENT(Instance, obj, arguments->NativeArgAt(1));
Object& validated_result = Object::Handle(zone);
const Object& msg_obj = Object::Handle(zone, obj.ptr());
validated_result = ValidateMessageObject(zone, isolate, msg_obj);
// msg_array = [
// <message>,
// <collection-lib-objects-to-rehash>,
// <core-lib-objects-to-rehash>,
// ]
const Array& msg_array = Array::Handle(zone, Array::New(3));
msg_array.SetAt(0, msg_obj);
if (validated_result.IsUnhandledException()) {
Exceptions::PropagateError(Error::Cast(validated_result));
UNREACHABLE();
Object& validated_result = Object::Handle(zone);
const Object& msg_obj = Object::Handle(zone, obj.ptr());
validated_result = ValidateMessageObject(zone, isolate, msg_obj);
// msg_array = [
// <message>,
// <collection-lib-objects-to-rehash>,
// <core-lib-objects-to-rehash>,
// ]
const Array& msg_array = Array::Handle(zone, Array::New(3));
msg_array.SetAt(0, msg_obj);
if (validated_result.IsUnhandledException()) {
Exceptions::PropagateError(Error::Cast(validated_result));
UNREACHABLE();
}
PersistentHandle* handle =
isolate->group()->api_state()->AllocatePersistentHandle();
handle->set_ptr(msg_array);
isolate->bequeath(std::unique_ptr<Bequest>(new Bequest(handle, port.Id())));
}
PersistentHandle* handle =
isolate->group()->api_state()->AllocatePersistentHandle();
handle->set_ptr(msg_array);
isolate->bequeath(std::unique_ptr<Bequest>(new Bequest(handle, port.Id())));
Isolate::KillIfExists(isolate, Isolate::LibMsgId::kKillMsg);
// Drain interrupts before running so any IMMEDIATE operations on the current
// isolate happen synchronously.

View file

@ -1,14 +0,0 @@
// 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.
import 'dart:io';
import 'dart:isolate';
import 'dart:async';
import 'dart:_internal' as dart_internal;
extension SendPortSendAndExit on SendPort {
void sendAndExit(var message) {
dart_internal.sendAndExit(this, message);
}
}

View file

@ -24,7 +24,7 @@ main(args) async {
final ring = await Ring.create(numIsolates);
// Let each node produce a tree, send it to it's neighbour and let it return
// the one it received (via sendAndExit).
// the one it received (via Isolate.exit).
final results = await ring.runAndClose((int id) => Worker(id));
Expect.equals(numIsolates, results.length);

View file

@ -6,8 +6,6 @@ import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'internal.dart';
export '../../../../../benchmarks/IsolateFibonacci/dart/IsolateFibonacci.dart'
show fibonacciRecursive;
@ -102,7 +100,7 @@ class Ring {
case Command.kRunAndClose:
final RingElement re = args[1];
final SendPort nextNeighbor = args[2];
port.sendAndExit(await re.run(nextNeighbor, siData));
Isolate.exit(port, await re.run(nextNeighbor, siData));
break;
case Command.kClose:
port.send('done');

View file

@ -4,9 +4,8 @@
//
// VMOptions=--enable-isolate-groups
//
// Validates functionality of sendAndExit.
// Validates functionality of Isolate.exit().
import 'dart:_internal' show sendAndExit;
import 'dart:async';
import 'dart:isolate';
import 'dart:nativewrappers';
@ -27,7 +26,7 @@ spawnWorker(worker, data) async {
verifyCantSendAnonymousClosure() async {
final receivePort = ReceivePort();
Expect.throws(
() => sendAndExit(receivePort.sendPort, () {}),
() => Isolate.exit(receivePort.sendPort, () {}),
(e) =>
e.toString() ==
'Invalid argument: "Illegal argument in isolate message : '
@ -40,7 +39,7 @@ class NativeWrapperClass extends NativeFieldWrapperClass1 {}
verifyCantSendNative() async {
final receivePort = ReceivePort();
Expect.throws(
() => sendAndExit(receivePort.sendPort, NativeWrapperClass()),
() => Isolate.exit(receivePort.sendPort, NativeWrapperClass()),
(e) => e.toString().startsWith('Invalid argument: '
'"Illegal argument in isolate message : '
'(object extends NativeWrapper'));
@ -50,7 +49,7 @@ verifyCantSendNative() async {
verifyCantSendReceivePort() async {
final receivePort = ReceivePort();
Expect.throws(
() => sendAndExit(receivePort.sendPort, receivePort),
() => Isolate.exit(receivePort.sendPort, receivePort),
// closure is encountered first before we reach ReceivePort instance
(e) => e.toString().startsWith(
'Invalid argument: "Illegal argument in isolate message : '
@ -62,7 +61,7 @@ verifyCantSendRegexp() async {
final receivePort = ReceivePort();
final regexp = RegExp("");
Expect.throws(
() => sendAndExit(receivePort.sendPort, regexp),
() => Isolate.exit(receivePort.sendPort, regexp),
(e) =>
e.toString() ==
'Invalid argument: '
@ -73,7 +72,7 @@ verifyCantSendRegexp() async {
add(a, b) => a + b;
worker(SendPort sendPort) async {
sendAndExit(sendPort, add);
Isolate.exit(sendPort, add);
}
verifyCanSendStaticMethod() async {

View file

@ -1,16 +0,0 @@
// 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.
// @dart = 2.9
import 'dart:io';
import 'dart:isolate';
import 'dart:async';
import 'dart:_internal' as dart_internal;
extension SendPortSendAndExit on SendPort {
void sendAndExit(var message) {
dart_internal.sendAndExit(this, message);
}
}

View file

@ -26,7 +26,7 @@ main(args) async {
final ring = await Ring.create(numIsolates);
// Let each node produce a tree, send it to it's neighbour and let it return
// the one it received (via sendAndExit).
// the one it received (via Isolate.exit()).
final results = await ring.runAndClose((int id) => Worker(id));
Expect.equals(numIsolates, results.length);

View file

@ -8,8 +8,6 @@ import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'internal.dart';
export '../../../../../benchmarks/IsolateFibonacci/dart2/IsolateFibonacci.dart'
show fibonacciRecursive;
@ -104,7 +102,7 @@ class Ring {
case Command.kRunAndClose:
final RingElement re = args[1];
final SendPort nextNeighbor = args[2];
port.sendAndExit(await re.run(nextNeighbor, siData));
Isolate.exit(port, await re.run(nextNeighbor, siData));
break;
case Command.kClose:
port.send('done');

View file

@ -4,11 +4,10 @@
//
// VMOptions=--enable-isolate-groups
//
// Validates functionality of sendAndExit.
// Validates functionality of Isolate.exit().
// @dart = 2.9
import 'dart:_internal' show sendAndExit;
import 'dart:async';
import 'dart:isolate';
import 'dart:nativewrappers';
@ -29,7 +28,7 @@ spawnWorker(worker, data) async {
verifyCantSendAnonymousClosure() async {
final receivePort = ReceivePort();
Expect.throws(
() => sendAndExit(receivePort.sendPort, () {}),
() => Isolate.exit(receivePort.sendPort, () {}),
(e) =>
e.toString() ==
'Invalid argument: "Illegal argument in isolate message : '
@ -42,7 +41,7 @@ class NativeWrapperClass extends NativeFieldWrapperClass1 {}
verifyCantSendNative() async {
final receivePort = ReceivePort();
Expect.throws(
() => sendAndExit(receivePort.sendPort, NativeWrapperClass()),
() => Isolate.exit(receivePort.sendPort, NativeWrapperClass()),
(e) => e.toString().startsWith('Invalid argument: '
'"Illegal argument in isolate message : '
'(object extends NativeWrapper'));
@ -52,7 +51,7 @@ verifyCantSendNative() async {
verifyCantSendReceivePort() async {
final receivePort = ReceivePort();
Expect.throws(
() => sendAndExit(receivePort.sendPort, receivePort),
() => Isolate.exit(receivePort.sendPort, receivePort),
// closure is encountered first before we reach ReceivePort instance
(e) => e.toString().startsWith(
'Invalid argument: "Illegal argument in isolate message : '
@ -64,7 +63,7 @@ verifyCantSendRegexp() async {
final receivePort = ReceivePort();
final regexp = RegExp("");
Expect.throws(
() => sendAndExit(receivePort.sendPort, regexp),
() => Isolate.exit(receivePort.sendPort, regexp),
(e) =>
e.toString() ==
'Invalid argument: '
@ -75,7 +74,7 @@ verifyCantSendRegexp() async {
add(a, b) => a + b;
worker(SendPort sendPort) async {
sendAndExit(sendPort, add);
Isolate.exit(sendPort, add);
}
verifyCanSendStaticMethod() async {

View file

@ -65,7 +65,6 @@ namespace dart {
V(SendPortImpl_get_id, 1) \
V(SendPortImpl_get_hashcode, 1) \
V(SendPortImpl_sendInternal_, 2) \
V(SendPortImpl_sendAndExitInternal_, 2) \
V(Smi_bitNegate, 1) \
V(Smi_bitLength, 1) \
V(Mint_bitNegate, 1) \
@ -316,12 +315,13 @@ namespace dart {
V(Int32x4_setFlagZ, 2) \
V(Int32x4_setFlagW, 2) \
V(Int32x4_select, 3) \
V(Isolate_exit_, 2) \
V(Isolate_getCurrentRootUriStr, 0) \
V(Isolate_getDebugName, 1) \
V(Isolate_getPortAndCapabilitiesOfCurrentIsolate, 0) \
V(Isolate_sendOOB, 2) \
V(Isolate_spawnFunction, 10) \
V(Isolate_spawnUri, 12) \
V(Isolate_getPortAndCapabilitiesOfCurrentIsolate, 0) \
V(Isolate_getCurrentRootUriStr, 0) \
V(Isolate_sendOOB, 2) \
V(Isolate_getDebugName, 1) \
V(GrowableList_allocate, 2) \
V(GrowableList_getIndexed, 2) \
V(GrowableList_setIndexed, 3) \

View file

@ -756,11 +756,10 @@ void Func1() {
TEST_CASE(DartAPI_EnsureUnwindErrorHandled_WhenSendAndExit) {
const char* kScriptChars = R"(
import 'dart:isolate';
import 'dart:_internal' show sendAndExit;
sendAndExitNow() {
final receivePort = ReceivePort();
sendAndExit(receivePort.sendPort, true);
Isolate.exit(receivePort.sendPort, true);
}
@pragma("vm:external-name", "Test_nativeFunc")

View file

@ -77,6 +77,10 @@ class Isolate {
@patch
void removeErrorListener(SendPort port) => _unsupported();
@patch
static Never exit([SendPort? finalMessagePort, Object? message]) =>
_unsupported();
}
/** Default factory for receive ports. */

View file

@ -106,6 +106,11 @@ class Isolate {
void removeErrorListener(SendPort port) {
throw new UnsupportedError("Isolate.removeErrorListener");
}
@patch
static Never exit([SendPort? finalMessagePort, Object? message]) {
throw new UnsupportedError("Isolate.exit");
}
}
@patch

View file

@ -178,9 +178,6 @@ external void reachabilityFence(Object object);
@pragma("vm:external-name", "Internal_nativeEffect")
external void _nativeEffect(Object object);
@pragma("vm:external-name", "SendPortImpl_sendAndExitInternal_")
external void sendAndExit(SendPort sendPort, var message);
// Collection of functions which should only be used for testing purposes.
abstract class VMInternalsForTesting {
// This function can be used by tests to enforce garbage collection.

View file

@ -645,6 +645,13 @@ class Isolate {
@pragma("vm:external-name", "Isolate_getCurrentRootUriStr")
external static String _getCurrentRootUriStr();
@pragma("vm:external-name", "Isolate_exit_")
external static Never _exit(SendPort? finalMessagePort, Object? message);
static Never exit([SendPort? finalMessagePort, Object? message]) {
_exit(finalMessagePort, message);
}
}
@patch

View file

@ -553,6 +553,32 @@ class Isolate {
};
return controller.stream;
}
/// Terminates the current isolate synchronously.
///
/// This operations is potentially dangerous and should be used judiciously.
/// The isolate stops operating *immediately*. It throws if optional [message]
/// does not adhere to the limitation on what can be send from one isolate to
/// another. It also throws if a [finalMessagePort] is associated with an
/// isolate spawned outside of current isolate group, spawned via [spawnUri].
///
/// If successful, a call to this method does not return. Pending `finally`
/// blocks are not executed, control flow will not go back to the event loop,
/// scheduled asynchronous asks will never run, and even pending isolate
/// control commands may be ignored. (The isolate will send messages to ports
/// already registered using [Isolate.addOnExitListener], but no further Dart
/// code will run in the isolate.)
///
/// If [finalMessagePort] is provided, and the [message] can be sent through
/// it, then the message is sent through that port as the final operation of
/// the current isolate. The isolate terminates immediately after
/// that [SendPort.send] call returns.
///
/// (If the port is a native port, one provided by [ReceivePort.sendPort]
/// or [RawReceivePort.sendPort], the system may be able to send this final
/// message more efficiently than normal port communication between live
/// isolates.)
external static Never exit([SendPort? finalMessagePort, Object? message]);
}
/// Sends messages to its [ReceivePort]s.

View file

@ -20,6 +20,7 @@ var denylist = [
// Don't exit the test pre-maturely.
'dart.io.exit',
'dart.isolate.Isolate.exit',
// Don't change the exit code, which may fool the test harness.
'dart.io.exitCode',

View file

@ -22,6 +22,7 @@ var denylist = [
// Don't exit the test pre-maturely.
'dart.io.exit',
'dart.isolate.Isolate.exit',
// Don't change the exit code, which may fool the test harness.
'dart.io.exitCode',