Version 2.12.0-35.0.dev
Merge commit 'd03b7e31f52927de75d4495eeb033eed490af438' into 'dev'
diff --git a/pkg/test_runner/bin/test_runner.dart b/pkg/test_runner/bin/test_runner.dart
index 3f5e048..7cb77ae 100644
--- a/pkg/test_runner/bin/test_runner.dart
+++ b/pkg/test_runner/bin/test_runner.dart
@@ -23,22 +23,22 @@
/// The default test directory layout is documented in "test_suite.dart", above
/// `factory StandardTestSuite.forDirectory`.
import "package:test_runner/src/options.dart";
+import "package:test_runner/src/build_configurations.dart";
import "package:test_runner/src/test_configurations.dart";
/// Runs all of the tests specified by the given command line [arguments].
-void main(List<String> arguments) {
+void main(List<String> arguments) async {
// Parse the command line arguments to a configuration.
var parser = OptionsParser();
+ var configurations = <TestConfiguration>[];
try {
- var configurations = parser.parse(arguments);
- if (configurations == null || configurations.isEmpty) return;
-
- // Run all of the configured tests.
- // TODO(26372): Ensure that all tasks complete and return a future from this
- // function.
- testConfigurations(configurations);
+ configurations = parser.parse(arguments);
} on OptionParseException catch (exception) {
print(exception.message);
exit(1);
}
+ if (configurations.isEmpty) return;
+ await buildConfigurations(configurations);
+ // Run all of the configured tests.
+ await testConfigurations(configurations);
}
diff --git a/pkg/test_runner/lib/src/build_configurations.dart b/pkg/test_runner/lib/src/build_configurations.dart
new file mode 100644
index 0000000..6179997
--- /dev/null
+++ b/pkg/test_runner/lib/src/build_configurations.dart
@@ -0,0 +1,92 @@
+// 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:async';
+import 'dart:io';
+
+import 'configuration.dart';
+import 'utils.dart';
+
+Future buildConfigurations(List<TestConfiguration> configurations) async {
+ var startTime = DateTime.now();
+ if (!configurations.first.build) return;
+ final buildTargets = <String>{};
+ final modes = <Mode>{};
+ final architectures = <Architecture>{};
+ final systems = <System>{};
+ for (final configuration in configurations) {
+ final inner = configuration.configuration;
+ buildTargets.addAll(_selectBuildTargets(inner));
+ modes.add(inner.mode);
+ architectures.add(inner.architecture);
+ systems.add(inner.system);
+ }
+ if (buildTargets.isEmpty) return;
+ if (systems.length > 1) {
+ print('Unimplemented: building for multiple systems ${systems.join(',')}');
+ exit(1);
+ }
+ final system = systems.single;
+ final osFlags = <String>[];
+ if (system == System.android) {
+ osFlags.addAll(['--os', 'android']);
+ } else if (system == System.fuchsia) {
+ osFlags.addAll(['--os', 'fuchsia']);
+ } else {
+ final host = System.find(Platform.operatingSystem);
+ if (system != host) {
+ print('Unimplemented: running tests for $system on $host');
+ exit(1);
+ }
+ }
+ final command = [
+ 'tools/build.py',
+ '-m',
+ modes.join(','),
+ '-a',
+ architectures.join(','),
+ ...osFlags,
+ ...buildTargets
+ ];
+ print('Running command: python ${command.join(' ')}');
+ final process = await Process.start('python', command);
+ stdout.nonBlocking.addStream(process.stdout);
+ stderr.nonBlocking.addStream(process.stderr);
+ final exitCode = await process.exitCode;
+ if (exitCode != 0) {
+ print('exit code: $exitCode');
+ }
+ var buildTime = niceTime(DateTime.now().difference(startTime));
+ print('--- Build time: $buildTime ---');
+}
+
+List<String> _selectBuildTargets(Configuration inner) {
+ final result = <String>[];
+ final compiler = inner.compiler;
+ const targetsForCompilers = {
+ Compiler.dartk: ['runtime'],
+ Compiler.dartkp: ['runtime', 'dart_precompiled_runtime'],
+ Compiler.appJitk: ['runtime'],
+ Compiler.fasta: ['create_sdk', 'dartdevc_test', 'kernel_platform_files'],
+ Compiler.dartdevk: ['dartdevc_test'],
+ Compiler.dart2js: ['create_sdk'],
+ Compiler.dart2analyzer: ['create_sdk'],
+ Compiler.specParser: <String>[],
+ };
+ result.addAll(targetsForCompilers[compiler]);
+
+ if (compiler == Compiler.dartkp &&
+ [Architecture.arm, Architecture.arm64, Architecture.arm_x64]
+ .contains(inner.architecture)) {
+ result.add('gen_snapshot');
+ }
+
+ if (compiler == Compiler.dartdevk && !inner.useSdk) {
+ result
+ ..remove('dartdevc_test')
+ ..add('dartdevc_test_local');
+ }
+
+ return result;
+}
diff --git a/pkg/test_runner/lib/src/configuration.dart b/pkg/test_runner/lib/src/configuration.dart
index a0bafae..12af614 100644
--- a/pkg/test_runner/lib/src/configuration.dart
+++ b/pkg/test_runner/lib/src/configuration.dart
@@ -27,6 +27,7 @@
{this.configuration,
this.progress,
this.selectors,
+ this.build,
this.testList,
this.repeat,
this.batch,
@@ -82,6 +83,7 @@
final bool batch;
final bool batchDart2JS;
+ final bool build;
final bool copyCoreDumps;
final bool rr;
final bool fastTestsOnly;
diff --git a/pkg/test_runner/lib/src/options.dart b/pkg/test_runner/lib/src/options.dart
index 231dd81..b73f36c 100644
--- a/pkg/test_runner/lib/src/options.dart
+++ b/pkg/test_runner/lib/src/options.dart
@@ -164,6 +164,8 @@
test options, specifying how tests should be run.''',
abbr: 'n',
hide: true),
+ _Option.bool(
+ 'build', 'Build the necessary targets to test this configuration'),
// TODO(sigmund): rename flag once we migrate all dart2js bots to the test
// matrix.
_Option.bool('host_checked', 'Run compiler with assertions enabled.',
@@ -355,6 +357,7 @@
/// For printing out reproducing command lines, we don't want to add these
/// options.
static final _denylistedOptions = {
+ 'build',
'build_directory',
'chrome',
'clean_exit',
@@ -749,6 +752,7 @@
configuration: innerConfiguration,
progress: progress,
selectors: selectors,
+ build: data["build"] as bool,
testList: data["test_list_contents"] as List<String>,
repeat: data["repeat"] as int,
batch: !(data["noBatch"] as bool),
diff --git a/pkg/test_runner/lib/src/test_configurations.dart b/pkg/test_runner/lib/src/test_configurations.dart
index be34361..4d3925a 100644
--- a/pkg/test_runner/lib/src/test_configurations.dart
+++ b/pkg/test_runner/lib/src/test_configurations.dart
@@ -18,6 +18,8 @@
import 'test_suite.dart';
import 'utils.dart';
+export 'configuration.dart' show TestConfiguration;
+
/// The directories that contain test suites which follow the conventions
/// required by [StandardTestSuite]'s forDirectory constructor.
///
@@ -52,6 +54,7 @@
Path('utils/tests/peg'),
];
+// TODO(26372): Ensure that the returned future awaits on all started tasks.
Future testConfigurations(List<TestConfiguration> configurations) async {
var startTime = DateTime.now();
diff --git a/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc b/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc
index ace3747..e642819 100644
--- a/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc
+++ b/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc
@@ -10,6 +10,7 @@
#include <csignal>
#include "platform/globals.h"
+#include "platform/memory_sanitizer.h"
#if defined(HOST_OS_WINDOWS)
#include <psapi.h>
#include <windows.h>
@@ -47,6 +48,12 @@
#define CHECK_EQ(X, Y) CHECK((X) == (Y))
+#define ENSURE(X) \
+ if (!(X)) { \
+ fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, "Check failed: " #X); \
+ exit(1); \
+ }
+
////////////////////////////////////////////////////////////////////////////////
// Functions for stress-testing.
@@ -271,6 +278,89 @@
#endif // defined(TARGET_OS_LINUX)
+DART_EXPORT void IGH_MsanUnpoison(void* start, intptr_t length) {
+ MSAN_UNPOISON(start, length);
+}
+
+DART_EXPORT Dart_Isolate IGH_CreateIsolate(const char* name, void* peer) {
+ struct Helper {
+ static void ShutdownCallback(void* ig_data, void* isolate_data) {
+ char* string = reinterpret_cast<char*>(isolate_data);
+ ENSURE(string[0] == 'a');
+ string[0] = 'x';
+ }
+ static void CleanupCallback(void* ig_data, void* isolate_data) {
+ char* string = reinterpret_cast<char*>(isolate_data);
+ ENSURE(string[2] == 'c');
+ string[2] = 'z';
+ }
+ };
+
+ Dart_Isolate parent = Dart_CurrentIsolate();
+ Dart_ExitIsolate();
+
+ char* error = nullptr;
+ Dart_Isolate child =
+ Dart_CreateIsolateInGroup(parent, name, &Helper::ShutdownCallback,
+ &Helper::CleanupCallback, peer, &error);
+ if (child == nullptr) {
+ Dart_EnterIsolate(parent);
+ Dart_Handle error_obj = Dart_NewStringFromCString(error);
+ free(error);
+ Dart_ThrowException(error_obj);
+ return nullptr;
+ }
+ Dart_ExitIsolate();
+ Dart_EnterIsolate(parent);
+ return child;
+}
+
+DART_EXPORT void IGH_StartIsolate(Dart_Isolate child_isolate,
+ int64_t main_isolate_port,
+ const char* library_uri,
+ const char* function_name,
+ bool errors_are_fatal,
+ Dart_Port on_error_port,
+ Dart_Port on_exit_port) {
+ Dart_Isolate parent = Dart_CurrentIsolate();
+ Dart_ExitIsolate();
+ Dart_EnterIsolate(child_isolate);
+ {
+ Dart_EnterScope();
+
+ Dart_Handle library_name = Dart_NewStringFromCString(library_uri);
+ ENSURE(!Dart_IsError(library_name));
+
+ Dart_Handle library = Dart_LookupLibrary(library_name);
+ ENSURE(!Dart_IsError(library));
+
+ Dart_Handle fun = Dart_NewStringFromCString(function_name);
+ ENSURE(!Dart_IsError(fun));
+
+ Dart_Handle port = Dart_NewInteger(main_isolate_port);
+ ENSURE(!Dart_IsError(port));
+
+ Dart_Handle args[] = {
+ port,
+ };
+
+ Dart_Handle result = Dart_Invoke(library, fun, 1, args);
+ if (Dart_IsError(result)) {
+ fprintf(stderr, "Failed to invoke %s/%s in child isolate: %s\n",
+ library_uri, function_name, Dart_GetError(result));
+ }
+ ENSURE(!Dart_IsError(result));
+
+ Dart_ExitScope();
+ }
+
+ char* error = nullptr;
+ ENSURE(
+ Dart_RunLoopAsync(errors_are_fatal, on_error_port, on_exit_port, &error));
+
+ Dart_EnterIsolate(parent);
+}
+
////////////////////////////////////////////////////////////////////////////////
// Initialize `dart_api_dl.h`
DART_EXPORT intptr_t InitDartApiDL(void* data) {
diff --git a/runtime/bin/main.cc b/runtime/bin/main.cc
index 4b51532..a453899 100644
--- a/runtime/bin/main.cc
+++ b/runtime/bin/main.cc
@@ -245,12 +245,8 @@
if (Dart_IsError(result)) goto failed;
}
- // Make the isolate runnable so that it is ready to handle messages.
Dart_ExitScope();
- Dart_ExitIsolate();
- *error = Dart_IsolateMakeRunnable(isolate);
- Dart_EnterIsolate(isolate);
- return *error == nullptr;
+ return true;
failed:
*error = Utils::StrDup(Dart_GetError(result));
diff --git a/runtime/include/dart_api.h b/runtime/include/dart_api.h
index db0e630..189a6b7 100644
--- a/runtime/include/dart_api.h
+++ b/runtime/include/dart_api.h
@@ -1007,6 +1007,37 @@
void* isolate_group_data,
void* isolate_data,
char** error);
+/**
+ * Creates a new isolate inside the isolate group of [group_member].
+ *
+ * Requires there to be no current isolate.
+ *
+ * \param group_member An isolate from the same group into which the newly created
+ * isolate should be born into. Other threads may not have entered / enter this
+ * member isolate.
+ * \param name A short name for the isolate for debugging purposes.
+ * \param shutdown_callback A callback to be called when the isolate is being
+ * shutdown (may be NULL).
+ * \param cleanup_callback A callback to be called when the isolate is being
+ * cleaned up (may be NULL).
+ * \param isolate_data The embedder-specific data associated with this isolate.
+ * \param error Set to NULL if creation is successful, set to an error
+ * message otherwise. The caller is responsible for calling free() on the
+ * error message.
+ *
+ * \return The newly created isolate on success, or NULL if isolate creation
+ * failed.
+ *
+ * If successful, the newly created isolate will become the current isolate.
+ */
+DART_EXPORT Dart_Isolate
+Dart_CreateIsolateInGroup(Dart_Isolate group_member,
+ const char* name,
+ Dart_IsolateShutdownCallback shutdown_callback,
+ Dart_IsolateCleanupCallback cleanup_callback,
+ void* child_isolate_data,
+ char** error);
+
/* TODO(turnidge): Document behavior when there is already a current
* isolate. */
@@ -1483,6 +1514,31 @@
* error handle is returned.
*/
DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_RunLoop();
+
+/**
+ * Lets the VM run message processing for the isolate.
+ *
+ * This function expects there to a current isolate and the current isolate
+ * must not have an active api scope. The VM will take care of making the
+ * isolate runnable (if not already), handles its message loop and will take
+ * care of shutting the isolate down once it's done.
+ *
+ * \param errors_are_fatal Whether uncaught errors should be fatal.
+ * \param on_error_port A port to notify on uncaught errors (or ILLEGAL_PORT).
+ * \param on_exit_port A port to notify on exit (or ILLEGAL_PORT).
+ * \param error A non-NULL pointer which will hold an error message if the call
+ * fails. The error has to be free()ed by the caller.
+ *
+ * \return If successfull the VM takes owernship of the isolate and takes care
+ * of its message loop. If not successful the caller retains owernship of the
+ * isolate.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT bool Dart_RunLoopAsync(
+ bool errors_are_fatal,
+ Dart_Port on_error_port,
+ Dart_Port on_exit_port,
+ char** error);
+
/* TODO(turnidge): Should this be removed from the public api? */
/**
diff --git a/runtime/lib/isolate.cc b/runtime/lib/isolate.cc
index 94de7aa..247adc3 100644
--- a/runtime/lib/isolate.cc
+++ b/runtime/lib/isolate.cc
@@ -352,6 +352,13 @@
return;
}
+ if (isolate->object_store()->root_library() == Library::null()) {
+ Dart_ShutdownIsolate();
+ FailedSpawn(
+ "The embedder has to ensure there is a root library (e.g. by calling "
+ "Dart_LoadScriptFromKernel).");
+ }
+
Run(isolate);
}
@@ -401,15 +408,19 @@
state_->set_isolate(child);
MutexLocker ml(child->mutex());
+
+ // We called out to the embedder to create/initialize a new isolate. The
+ // embedder callback sucessfully did so. It is now our responsibility to
+ // run the isolate.
+ // If the isolate was not marked as runnable, we'll do so here and run it
+ if (!child->is_runnable()) {
+ child->MakeRunnableLocked();
+ }
+ ASSERT(child->is_runnable());
+
child->set_origin_id(state_->origin_id());
child->set_spawn_state(std::move(state_));
-
- // If the isolate is not marked as runnable, then the embedder might do so
- // later on and the launch of the isolate will happen inside
- // `Dart_IsolateMakeRunnable`.
- if (child->is_runnable()) {
- child->Run();
- }
+ child->RunViaSpawnApi();
}
void FailedSpawn(const char* error) {
diff --git a/runtime/observatory/tests/service/service.status b/runtime/observatory/tests/service/service.status
index bc2bb42..f95c71d 100644
--- a/runtime/observatory/tests/service/service.status
+++ b/runtime/observatory/tests/service/service.status
@@ -11,6 +11,7 @@
pause_on_start_and_exit_with_child_test: Pass, RuntimeError # Issue 33049
reload_sources_test: Pass, Slow # Reload is slow on the bots
valid_source_locations_test: Pass, Slow # Generally slow, even in release-x64.
+validate_timer_port_behavior_test: Skip # Issue 44166
[ $arch == arm ]
process_service_test: Pass, Fail # Issue 24344
diff --git a/runtime/observatory_2/tests/service_2/service_2.status b/runtime/observatory_2/tests/service_2/service_2.status
index c16feab..6c5e1b4 100644
--- a/runtime/observatory_2/tests/service_2/service_2.status
+++ b/runtime/observatory_2/tests/service_2/service_2.status
@@ -11,6 +11,7 @@
pause_on_start_and_exit_with_child_test: Pass, RuntimeError # Issue 33049
reload_sources_test: Pass, Slow # Reload is slow on the bots
valid_source_locations_test: Pass, Slow # Generally slow, even in release-x64.
+validate_timer_port_behavior_test: Skip # Issue 44166
[ $arch == arm ]
process_service_test: Pass, Fail # Issue 24344
diff --git a/runtime/tests/vm/dart/isolates/dart_api_create_lightweight_isolate_test.dart b/runtime/tests/vm/dart/isolates/dart_api_create_lightweight_isolate_test.dart
new file mode 100644
index 0000000..9f9359d
--- /dev/null
+++ b/runtime/tests/vm/dart/isolates/dart_api_create_lightweight_isolate_test.dart
@@ -0,0 +1,225 @@
+// 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();
+ }
+}
diff --git a/runtime/tests/vm/dart_2/isolates/dart_api_create_lightweight_isolate_test.dart b/runtime/tests/vm/dart_2/isolates/dart_api_create_lightweight_isolate_test.dart
new file mode 100644
index 0000000..89045e8
--- /dev/null
+++ b/runtime/tests/vm/dart_2/isolates/dart_api_create_lightweight_isolate_test.dart
@@ -0,0 +1,234 @@
+// 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_MsanUnpoison = ffiTestFunctions.lookupFunction<
+ Pointer<Isolate> Function(Pointer<Void>, IntPtr),
+ Pointer<Isolate> Function(Pointer<Void>, int)>('IGH_MsanUnpoison');
+
+ 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);
+ IGH_MsanUnpoison(cname.cast(), name.length + 10);
+ 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 dartScriptUri = sdkRoot.resolve(
+ 'runtime/tests/vm/dart_2/isolates/dart_api_create_lightweight_isolate_test.dart');
+ final dartScript = dartScriptUri.toString();
+ final libraryUri = Utf8.toUtf8(dartScript);
+ IGH_MsanUnpoison(libraryUri.cast(), dartScript.length + 1);
+ final functionName = Utf8.toUtf8(name);
+ IGH_MsanUnpoison(functionName.cast(), name.length + 1);
+
+ 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();
+ FfiBindings.IGH_MsanUnpoison(peer.cast(), 'abc'.length + 1);
+ 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();
+ }
+}
diff --git a/runtime/tests/vm/vm.status b/runtime/tests/vm/vm.status
index 16ee403..3091db9 100644
--- a/runtime/tests/vm/vm.status
+++ b/runtime/tests/vm/vm.status
@@ -327,12 +327,14 @@
cc/Profiler_TrivialRecordAllocation: SkipByDesign
cc/Profiler_TypedArrayAllocation: SkipByDesign
cc/Service_Profile: SkipByDesign
+dart/isolates/dart_api_create_lightweight_isolate_test: SkipByDesign # Test uses dart:ffi which is not supported on simulators.
dart/isolates/thread_pool_test: SkipByDesign # Test uses dart:ffi which is not supported on simulators.
dart/regress_41971_test: SkipByDesign # dart:ffi is not supported on simulator
dart/sdk_hash_test: SkipSlow # gen_kernel is slow to run on simarm
dart/unboxed_param_args_descriptor_test: SkipByDesign # FFI helper not supported on simulator
dart/unboxed_param_tear_off_test: SkipByDesign # FFI helper not supported on simulator
dart/unboxed_param_test: SkipByDesign # FFI helper not supported on simulator
+dart_2/isolates/dart_api_create_lightweight_isolate_test: SkipByDesign # Test uses dart:ffi which is not supported on simulators.
dart_2/isolates/thread_pool_test: SkipByDesign # Test uses dart:ffi which is not supported on simulators.
dart_2/regress_41971_test: SkipByDesign # dart:ffi is not supported on simulator
dart_2/sdk_hash_test: SkipSlow # gen_kernel is slow to run on simarm
diff --git a/runtime/vm/dart.cc b/runtime/vm/dart.cc
index 5d7112a..f570648 100644
--- a/runtime/vm/dart.cc
+++ b/runtime/vm/dart.cc
@@ -1068,7 +1068,7 @@
Isolate* isolate = thread->isolate();
void* isolate_group_data = isolate->group()->embedder_data();
void* isolate_data = isolate->init_callback_data();
- Dart_IsolateShutdownCallback callback = Isolate::ShutdownCallback();
+ Dart_IsolateShutdownCallback callback = isolate->on_shutdown_callback();
if (callback != NULL) {
TransitionVMToNative transition(thread);
(callback)(isolate_group_data, isolate_data);
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 7c1e68c..7209a5e 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -1563,6 +1563,39 @@
return isolate;
}
+DART_EXPORT Dart_Isolate
+Dart_CreateIsolateInGroup(Dart_Isolate group_member,
+ const char* name,
+ Dart_IsolateShutdownCallback shutdown_callback,
+ Dart_IsolateCleanupCallback cleanup_callback,
+ void* child_isolate_data,
+ char** error) {
+ CHECK_NO_ISOLATE(Isolate::Current());
+ auto member = reinterpret_cast<Isolate*>(group_member);
+ if (member->IsScheduled()) {
+ FATAL("The given member isolate (%s) must not have been entered.",
+ member->name());
+ }
+
+ *error = nullptr;
+
+ Isolate* isolate;
+#if defined(DART_PRECOMPILED_RUNTIME)
+ isolate = CreateWithinExistingIsolateGroupAOT(member->group(), name, error);
+ if (isolate != nullptr) {
+ isolate->set_origin_id(member->origin_id());
+ isolate->set_init_callback_data(child_isolate_data);
+ isolate->set_on_shutdown_callback(shutdown_callback);
+ isolate->set_on_cleanup_callback(cleanup_callback);
+ }
+#else
+ *error = Utils::StrDup("Lightweight isolates are not yet ready in JIT mode.");
+ isolate = nullptr;
+#endif
+
+ return Api::CastIsolate(isolate);
+}
+
DART_EXPORT void Dart_ShutdownIsolate() {
Thread* T = Thread::Current();
Isolate* I = T->isolate();
@@ -2001,18 +2034,11 @@
FATAL1("%s expects argument 'isolate' to be non-null.", CURRENT_FUNC);
}
// TODO(16615): Validate isolate parameter.
- Isolate* iso = reinterpret_cast<Isolate*>(isolate);
- const char* error;
- if (iso->object_store()->root_library() == Library::null()) {
- // The embedder should have called Dart_LoadScriptFromKernel by now.
- error = "Missing root library";
- } else {
- error = iso->MakeRunnable();
- }
- if (error != NULL) {
+ const char* error = reinterpret_cast<Isolate*>(isolate)->MakeRunnable();
+ if (error != nullptr) {
return Utils::StrDup(error);
}
- return NULL;
+ return nullptr;
}
// --- Messages and Ports ---
@@ -2097,6 +2123,53 @@
return Api::Success();
}
+DART_EXPORT bool Dart_RunLoopAsync(bool errors_are_fatal,
+ Dart_Port on_error_port,
+ Dart_Port on_exit_port,
+ char** error) {
+ auto thread = Thread::Current();
+ auto isolate = thread->isolate();
+ CHECK_ISOLATE(isolate);
+ *error = nullptr;
+
+ if (thread->api_top_scope() != nullptr) {
+ *error = Utils::StrDup("There must not be an active api scope.");
+ return false;
+ }
+
+ if (!isolate->is_runnable()) {
+ const char* error_msg = isolate->MakeRunnable();
+ if (error_msg != nullptr) {
+ *error = Utils::StrDup(error_msg);
+ return false;
+ }
+ }
+
+ isolate->SetErrorsFatal(errors_are_fatal);
+
+ if (on_error_port != ILLEGAL_PORT || on_exit_port != ILLEGAL_PORT) {
+ auto thread = Thread::Current();
+ TransitionNativeToVM transition(thread);
+ StackZone zone(thread);
+ HANDLESCOPE(thread);
+
+ if (on_error_port != ILLEGAL_PORT) {
+ const auto& port =
+ SendPort::Handle(thread->zone(), SendPort::New(on_error_port));
+ isolate->AddErrorListener(port);
+ }
+ if (on_exit_port != ILLEGAL_PORT) {
+ const auto& port =
+ SendPort::Handle(thread->zone(), SendPort::New(on_exit_port));
+ isolate->AddExitListener(port, Instance::null_instance());
+ }
+ }
+
+ Dart_ExitIsolate();
+ isolate->RunViaEmbedder();
+ return true;
+}
+
DART_EXPORT Dart_Handle Dart_HandleMessage() {
Thread* T = Thread::Current();
Isolate* I = T->isolate();
diff --git a/runtime/vm/dart_api_impl_test.cc b/runtime/vm/dart_api_impl_test.cc
index fea59eb..e65800f 100644
--- a/runtime/vm/dart_api_impl_test.cc
+++ b/runtime/vm/dart_api_impl_test.cc
@@ -7866,10 +7866,10 @@
"}\n";
// Create an isolate.
- Dart_Isolate isolate = TestCase::CreateTestIsolate();
+ auto isolate = reinterpret_cast<Isolate*>(TestCase::CreateTestIsolate());
EXPECT(isolate != NULL);
- Isolate::SetShutdownCallback(IsolateShutdownRunDartCodeTestCallback);
+ isolate->set_on_shutdown_callback(IsolateShutdownRunDartCodeTestCallback);
{
Dart_EnterScope();
@@ -7887,8 +7887,6 @@
// The shutdown callback has not been called.
EXPECT_EQ(0, add_result);
- EXPECT(isolate != NULL);
-
// Shutdown the isolate.
Dart_ShutdownIsolate();
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index 914eaa9..f85ce06 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -1644,6 +1644,8 @@
reload_every_n_stack_overflow_checks_(FLAG_reload_every),
#endif // !defined(PRODUCT)
start_time_micros_(OS::GetCurrentMonotonicMicros()),
+ on_shutdown_callback_(Isolate::ShutdownCallback()),
+ on_cleanup_callback_(Isolate::CleanupCallback()),
random_(),
mutex_(NOT_IN_PRODUCT("Isolate::mutex_")),
constant_canonicalization_mutex_(
@@ -2048,16 +2050,31 @@
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
const char* Isolate::MakeRunnable() {
- ASSERT(Isolate::Current() == nullptr);
-
MutexLocker ml(&mutex_);
// Check if we are in a valid state to make the isolate runnable.
if (is_runnable() == true) {
return "Isolate is already runnable";
}
+ if (spawn_state() != nullptr) {
+ return "The embedder has to make the isolate runnable during isolate "
+ "creation / initialization callback.";
+ }
+ if (object_store()->root_library() == Library::null()) {
+ return "The embedder has to ensure there is a root library (e.g. by "
+ "calling Dart_LoadScriptFromKernel ).";
+ }
+ MakeRunnableLocked();
+ return nullptr;
+}
+
+void Isolate::MakeRunnableLocked() {
+ ASSERT(mutex_.IsOwnedByCurrentThread());
+ ASSERT(!is_runnable());
+ ASSERT(spawn_state() == nullptr);
+ ASSERT(object_store()->root_library() != Library::null());
+
// Set the isolate as runnable and if we are being spawned schedule
// isolate on thread pool for execution.
- ASSERT(object_store()->root_library() != Library::null());
set_is_runnable(true);
#ifndef PRODUCT
if (!Isolate::IsSystemIsolate(this)) {
@@ -2066,16 +2083,6 @@
}
}
#endif // !PRODUCT
- IsolateSpawnState* state = spawn_state();
- if (state != nullptr) {
- // If the embedder does not make the isolate runnable during the
- // `create_isolate_group`/`initialize_isolate` embedder callbacks but rather
- // some time in the future, we'll hit this case.
- // WARNING: This is currently untested - we might consider changing our APIs
- // to disallow two different flows.
- ASSERT(this == state->isolate());
- Run();
- }
#if defined(SUPPORT_TIMELINE)
TimelineStream* stream = Timeline::GetIsolateStream();
ASSERT(stream != nullptr);
@@ -2092,7 +2099,6 @@
}
GetRunnableLatencyMetric()->set_value(UptimeMicros());
#endif // !PRODUCT
- return nullptr;
}
bool Isolate::VerifyPauseCapability(const Object& capability) const {
@@ -2411,11 +2417,18 @@
sticky_error_ = sticky_error;
}
-void Isolate::Run() {
+void Isolate::RunViaSpawnApi() {
+ ASSERT(spawn_state() != nullptr);
message_handler()->Run(group()->thread_pool(), RunIsolate, ShutdownIsolate,
reinterpret_cast<uword>(this));
}
+void Isolate::RunViaEmbedder() {
+ ASSERT(spawn_state() == nullptr);
+ message_handler()->Run(group()->thread_pool(), nullptr, ShutdownIsolate,
+ reinterpret_cast<uword>(this));
+}
+
void Isolate::AddClosureFunction(const Function& function) const {
ASSERT(!Compiler::IsBackgroundCompilation());
GrowableObjectArray& closures =
@@ -2628,7 +2641,7 @@
// Cache these two fields, since they are no longer available after the
// `delete this` further down.
IsolateGroup* isolate_group = isolate->isolate_group_;
- Dart_IsolateCleanupCallback cleanup = Isolate::CleanupCallback();
+ Dart_IsolateCleanupCallback cleanup = isolate->on_cleanup_callback();
auto callback_data = isolate->init_callback_data_;
// From this point on the isolate is no longer visited by GC (which is ok,
diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h
index 0d3279d..9a8d26e 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -786,6 +786,8 @@
return thread == nullptr ? nullptr : thread->isolate();
}
+ bool IsScheduled() { return scheduled_mutator_thread_ != nullptr; }
+
// Register a newly introduced class.
void RegisterClass(const Class& cls);
#if defined(DEBUG)
@@ -857,6 +859,19 @@
message_notify_callback_ = value;
}
+ void set_on_shutdown_callback(Dart_IsolateShutdownCallback value) {
+ on_shutdown_callback_ = value;
+ }
+ Dart_IsolateShutdownCallback on_shutdown_callback() {
+ return on_shutdown_callback_;
+ }
+ void set_on_cleanup_callback(Dart_IsolateCleanupCallback value) {
+ on_cleanup_callback_ = value;
+ }
+ Dart_IsolateCleanupCallback on_cleanup_callback() {
+ return on_cleanup_callback_;
+ }
+
void bequeath(std::unique_ptr<Bequest> bequest) {
bequest_ = std::move(bequest);
}
@@ -923,7 +938,16 @@
void ScheduleInterrupts(uword interrupt_bits);
const char* MakeRunnable();
- void Run();
+ void MakeRunnableLocked();
+
+ // Runs the isolate if it was created inside the VM as a response to
+ // invocation of Dart's `Isolate.spawn` api.
+ //
+ // It will wake up a potential await'er (e.g. `await Isolate.spawn()`).
+ void RunViaSpawnApi();
+
+ // Runs the isolate if it was created by the embedder.
+ void RunViaEmbedder();
MessageHandler* message_handler() const { return message_handler_; }
void set_message_handler(MessageHandler* value) { message_handler_ = value; }
@@ -1564,6 +1588,8 @@
// All other fields go here.
int64_t start_time_micros_;
Dart_MessageNotifyCallback message_notify_callback_ = nullptr;
+ Dart_IsolateShutdownCallback on_shutdown_callback_ = nullptr;
+ Dart_IsolateCleanupCallback on_cleanup_callback_ = nullptr;
char* name_ = nullptr;
Dart_Port main_port_ = 0;
// Isolates created by Isolate.spawn have the same origin id.
diff --git a/tools/VERSION b/tools/VERSION
index da3e7b1..5a2abd7 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 12
PATCH 0
-PRERELEASE 34
+PRERELEASE 35
PRERELEASE_PATCH 0
\ No newline at end of file