From bf4280ff75511b802f21bb644e89ac2263f22016 Mon Sep 17 00:00:00 2001 From: Martin Kustermann Date: Thu, 12 Nov 2020 10:22:16 +0000 Subject: [PATCH] [vm/concurrency] Add Dart_CreateLightweightIsolate/Dart_RunLoopAsync APIs Dart_CreateLightweightIsolate API: This API can be used by embedders to create a lightweight isolate (inside an existing isolate group). It is the analogous to Dart_CreateIsolateGroup - though taking a parent isolate as parameter (inside whose IG we create a new isolate) instead of taking kernel/snapshot data. Right now this API works on AOT and returns an error in JIT, both cases are covered by the test. Dart_RunLoopAsync: The API can be used by embedders to transfer ownership of an isolate to the VM, which will take care of running the message handling loop and shuts the isolate down once the last receive port has been closed. It does allow listening to error/exit events generated by the message loop implementation as well as allows setting errors-are-fatal, effectively the same event-loop related parameters from the `Isolate.spawn()` API, just in Dart. It's the embedders responsibility to first launch initial dart code which will take care of responding to events (i.e. directly/indirectly open a receive port) - otherwise the isolate cannot be talked to and would immediately shut down. Since our vm/cc tests do support running in AOT, we use a hybrid approach to test the functionality: We let a normal Dart test call a small C wrapper using FFI, to create a lightweight isolate, run it on a new thread and join that thread. TEST=vm/dart{,_2}/isolates/dart_api_create_lightweight_isolate_test Issue https://github.com/dart-lang/sdk/issues/36097 Closes https://github.com/dart-lang/sdk/issues/44088 Change-Id: Id77ba928793fdb517f6cb7e8130df98a0366ddd6 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/170983 Commit-Queue: Martin Kustermann Reviewed-by: Ryan Macnak Reviewed-by: Alexander Aprelev --- .../ffi_test/ffi_test_functions_vmspecific.cc | 90 +++++++ runtime/include/dart_api.h | 56 +++++ runtime/lib/isolate.cc | 2 +- ...t_api_create_lightweight_isolate_test.dart | 225 +++++++++++++++++ ...t_api_create_lightweight_isolate_test.dart | 234 ++++++++++++++++++ runtime/tests/vm/vm.status | 2 + runtime/vm/dart.cc | 2 +- runtime/vm/dart_api_impl.cc | 80 ++++++ runtime/vm/dart_api_impl_test.cc | 6 +- runtime/vm/isolate.cc | 15 +- runtime/vm/isolate.h | 27 +- 11 files changed, 728 insertions(+), 11 deletions(-) create mode 100644 runtime/tests/vm/dart/isolates/dart_api_create_lightweight_isolate_test.dart create mode 100644 runtime/tests/vm/dart_2/isolates/dart_api_create_lightweight_isolate_test.dart diff --git a/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc b/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc index ace374747c2..e6428191a15 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 #include "platform/globals.h" +#include "platform/memory_sanitizer.h" #if defined(HOST_OS_WINDOWS) #include #include @@ -47,6 +48,12 @@ namespace dart { #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 @@ DART_EXPORT intptr_t TestCallbackWrongIsolate(void (*fn)()) { #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(isolate_data); + ENSURE(string[0] == 'a'); + string[0] = 'x'; + } + static void CleanupCallback(void* ig_data, void* isolate_data) { + char* string = reinterpret_cast(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/include/dart_api.h b/runtime/include/dart_api.h index db0e630d50f..189a6b758f4 100644 --- a/runtime/include/dart_api.h +++ b/runtime/include/dart_api.h @@ -1007,6 +1007,37 @@ Dart_CreateIsolateGroup(const char* script_uri, 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 @@ DART_EXPORT bool Dart_HasServiceMessages(); * 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 74a9681716b..247adc32db0 100644 --- a/runtime/lib/isolate.cc +++ b/runtime/lib/isolate.cc @@ -420,7 +420,7 @@ class SpawnIsolateTask : public ThreadPool::Task { child->set_origin_id(state_->origin_id()); child->set_spawn_state(std::move(state_)); - child->Run(); + child->RunViaSpawnApi(); } void FailedSpawn(const char* error) { 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 00000000000..9f9359dbddc --- /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 Function(Pointer, Pointer), + Pointer Function( + Pointer, Pointer)>('IGH_CreateIsolate'); + + static final IGH_StartIsolate = ffiTestFunctions.lookupFunction< + Pointer Function(Pointer, Int64, Pointer, + Pointer, IntPtr, Int64, Int64), + Pointer Function(Pointer, int, Pointer, + Pointer, int, int, int)>('IGH_StartIsolate'); + + static final Dart_CurrentIsolate = DynamicLibrary.executable() + .lookupFunction Function(), Pointer Function()>( + "Dart_CurrentIsolate"); + + static final Dart_IsolateData = DynamicLibrary.executable().lookupFunction< + Pointer Function(Pointer), + Pointer Function(Pointer)>("Dart_IsolateData"); + + static final Dart_PostInteger = DynamicLibrary.executable() + .lookupFunction( + "Dart_PostInteger"); + + static Pointer createLightweightIsolate( + String name, Pointer 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, 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 peer)) async { + final Pointer 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 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 peer) async { + final rp = ReceivePort(); + final accumulatedErrors = []; + 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 peer) async { + final rp = ReceivePort(); + final accumulatedErrors = []; + 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 00000000000..89045e8a936 --- /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 Function(Pointer, IntPtr), + Pointer Function(Pointer, int)>('IGH_MsanUnpoison'); + + static final IGH_CreateIsolate = ffiTestFunctions.lookupFunction< + Pointer Function(Pointer, Pointer), + Pointer Function( + Pointer, Pointer)>('IGH_CreateIsolate'); + + static final IGH_StartIsolate = ffiTestFunctions.lookupFunction< + Pointer Function(Pointer, Int64, Pointer, + Pointer, IntPtr, Int64, Int64), + Pointer Function(Pointer, int, Pointer, + Pointer, int, int, int)>('IGH_StartIsolate'); + + static final Dart_CurrentIsolate = DynamicLibrary.executable() + .lookupFunction Function(), Pointer Function()>( + "Dart_CurrentIsolate"); + + static final Dart_IsolateData = DynamicLibrary.executable().lookupFunction< + Pointer Function(Pointer), + Pointer Function(Pointer)>("Dart_IsolateData"); + + static final Dart_PostInteger = DynamicLibrary.executable() + .lookupFunction( + "Dart_PostInteger"); + + static Pointer createLightweightIsolate( + String name, Pointer 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, 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 peer)) async { + final Pointer 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 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 peer) async { + final rp = ReceivePort(); + final accumulatedErrors = []; + 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 peer) async { + final rp = ReceivePort(); + final accumulatedErrors = []; + 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 16ee4039fea..3091db9b405 100644 --- a/runtime/tests/vm/vm.status +++ b/runtime/tests/vm/vm.status @@ -327,12 +327,14 @@ cc/Profiler_ToggleRecordAllocation: SkipByDesign 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 5d7112a1e53..f570648e901 100644 --- a/runtime/vm/dart.cc +++ b/runtime/vm/dart.cc @@ -1068,7 +1068,7 @@ void Dart::RunShutdownCallback() { 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 4644894af33..7209a5e73d4 100644 --- a/runtime/vm/dart_api_impl.cc +++ b/runtime/vm/dart_api_impl.cc @@ -1563,6 +1563,39 @@ Dart_CreateIsolateGroupFromKernel(const char* script_uri, 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(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(); @@ -2090,6 +2123,53 @@ DART_EXPORT Dart_Handle Dart_RunLoop() { 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 fea59ebe0cc..e65800f1335 100644 --- a/runtime/vm/dart_api_impl_test.cc +++ b/runtime/vm/dart_api_impl_test.cc @@ -7866,10 +7866,10 @@ VM_UNIT_TEST_CASE(DartAPI_IsolateShutdownRunDartCode) { "}\n"; // Create an isolate. - Dart_Isolate isolate = TestCase::CreateTestIsolate(); + auto isolate = reinterpret_cast(TestCase::CreateTestIsolate()); EXPECT(isolate != NULL); - Isolate::SetShutdownCallback(IsolateShutdownRunDartCodeTestCallback); + isolate->set_on_shutdown_callback(IsolateShutdownRunDartCodeTestCallback); { Dart_EnterScope(); @@ -7887,8 +7887,6 @@ VM_UNIT_TEST_CASE(DartAPI_IsolateShutdownRunDartCode) { // 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 7c796fd5250..f85ce06d79f 100644 --- a/runtime/vm/isolate.cc +++ b/runtime/vm/isolate.cc @@ -1644,6 +1644,8 @@ Isolate::Isolate(IsolateGroup* isolate_group, 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,8 +2050,6 @@ void Isolate::DeleteReloadContext() { #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) { @@ -2417,11 +2417,18 @@ void Isolate::SetStickyError(ErrorPtr sticky_error) { sticky_error_ = sticky_error; } -void Isolate::Run() { +void Isolate::RunViaSpawnApi() { + ASSERT(spawn_state() != nullptr); message_handler()->Run(group()->thread_pool(), RunIsolate, ShutdownIsolate, reinterpret_cast(this)); } +void Isolate::RunViaEmbedder() { + ASSERT(spawn_state() == nullptr); + message_handler()->Run(group()->thread_pool(), nullptr, ShutdownIsolate, + reinterpret_cast(this)); +} + void Isolate::AddClosureFunction(const Function& function) const { ASSERT(!Compiler::IsBackgroundCompilation()); GrowableObjectArray& closures = @@ -2634,7 +2641,7 @@ void Isolate::LowLevelCleanup(Isolate* isolate) { // 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 bdf7af321d0..9a8d26e2f18 100644 --- a/runtime/vm/isolate.h +++ b/runtime/vm/isolate.h @@ -786,6 +786,8 @@ class Isolate : public BaseIsolate, public IntrusiveDListEntry { 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 @@ class Isolate : public BaseIsolate, public IntrusiveDListEntry { 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_ = std::move(bequest); } @@ -924,7 +939,15 @@ class Isolate : public BaseIsolate, public IntrusiveDListEntry { const char* MakeRunnable(); void MakeRunnableLocked(); - void Run(); + + // 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; } @@ -1565,6 +1588,8 @@ class Isolate : public BaseIsolate, public IntrusiveDListEntry { // 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.