Skip to content

Commit 5f9ec9f

Browse files
mkustermanncommit-bot@chromium.org
authored andcommitted
[vm/concurrency] Allow closures as entrypoints in Isolate.spawn calls
We already allow sending closures (if immutable via sharing otherwise via copying). This CL makes use of this to allow the argument to `Isolate.spawn()` to be any closure. Similar to normal message sending, the spawn will fail if the closure cannot be sent (or causes an error on the new isolate, e.g. rehashing error). Issue #46623 TEST=vm/dart{_2,}/isolates/closure_entrypoint_test Change-Id: Iab342267d87bd87bc8c0c82d16aec58a69a3df44 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/212295 Reviewed-by: Alexander Aprelev <[email protected]> Commit-Queue: Martin Kustermann <[email protected]>
1 parent 21cbb2a commit 5f9ec9f

File tree

7 files changed

+398
-104
lines changed

7 files changed

+398
-104
lines changed

runtime/lib/isolate.cc

Lines changed: 127 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "vm/message_handler.h"
2222
#include "vm/message_snapshot.h"
2323
#include "vm/object.h"
24+
#include "vm/object_graph_copy.h"
2425
#include "vm/object_store.h"
2526
#include "vm/port.h"
2627
#include "vm/resolver.h"
@@ -319,6 +320,7 @@ class IsolateSpawnState {
319320
Dart_Port origin_id,
320321
const char* script_url,
321322
const Function& func,
323+
PersistentHandle* closure_tuple_handle,
322324
SerializedObjectBuffer* message_buffer,
323325
const char* package_config,
324326
bool paused,
@@ -353,10 +355,16 @@ class IsolateSpawnState {
353355
const char* class_name() const { return class_name_; }
354356
const char* function_name() const { return function_name_; }
355357
const char* debug_name() const { return debug_name_; }
356-
bool is_spawn_uri() const { return library_url_ == nullptr; }
358+
bool is_spawn_uri() const {
359+
return library_url_ == nullptr && // No top-level entrypoint.
360+
closure_tuple_handle_ == nullptr; // No closure entrypoint.
361+
}
357362
bool paused() const { return paused_; }
358363
bool errors_are_fatal() const { return errors_are_fatal_; }
359364
Dart_IsolateFlags* isolate_flags() { return &isolate_flags_; }
365+
PersistentHandle* closure_tuple_handle() const {
366+
return closure_tuple_handle_;
367+
}
360368

361369
ObjectPtr ResolveFunction();
362370
ObjectPtr BuildArgs(Thread* thread);
@@ -376,6 +384,7 @@ class IsolateSpawnState {
376384
const char* class_name_ = nullptr;
377385
const char* function_name_ = nullptr;
378386
const char* debug_name_;
387+
PersistentHandle* closure_tuple_handle_ = nullptr;
379388
IsolateGroup* isolate_group_;
380389
std::unique_ptr<Message> serialized_args_;
381390
std::unique_ptr<Message> serialized_message_;
@@ -396,6 +405,7 @@ IsolateSpawnState::IsolateSpawnState(Dart_Port parent_port,
396405
Dart_Port origin_id,
397406
const char* script_url,
398407
const Function& func,
408+
PersistentHandle* closure_tuple_handle,
399409
SerializedObjectBuffer* message_buffer,
400410
const char* package_config,
401411
bool paused,
@@ -411,25 +421,34 @@ IsolateSpawnState::IsolateSpawnState(Dart_Port parent_port,
411421
script_url_(script_url),
412422
package_config_(package_config),
413423
debug_name_(debug_name),
424+
closure_tuple_handle_(closure_tuple_handle),
414425
isolate_group_(isolate_group),
415426
serialized_args_(nullptr),
416427
serialized_message_(message_buffer->StealMessage()),
417428
paused_(paused),
418429
errors_are_fatal_(errors_are_fatal) {
430+
// Either we have a top-level function or we have a closure.
431+
ASSERT((closure_tuple_handle_ != nullptr) == func.IsNull());
432+
419433
auto thread = Thread::Current();
420434
auto isolate = thread->isolate();
421-
auto zone = thread->zone();
422-
const auto& cls = Class::Handle(zone, func.Owner());
423-
const auto& lib = Library::Handle(zone, cls.library());
424-
const auto& lib_url = String::Handle(zone, lib.url());
425-
library_url_ = NewConstChar(lib_url.ToCString());
426-
427-
String& func_name = String::Handle(zone);
428-
func_name = func.name();
429-
function_name_ = NewConstChar(String::ScrubName(func_name));
430-
if (!cls.IsTopLevel()) {
431-
const auto& class_name = String::Handle(zone, cls.Name());
432-
class_name_ = NewConstChar(class_name.ToCString());
435+
436+
if (!func.IsNull()) {
437+
auto zone = thread->zone();
438+
const auto& cls = Class::Handle(zone, func.Owner());
439+
const auto& lib = Library::Handle(zone, cls.library());
440+
const auto& lib_url = String::Handle(zone, lib.url());
441+
library_url_ = NewConstChar(lib_url.ToCString());
442+
443+
String& func_name = String::Handle(zone);
444+
func_name = func.name();
445+
function_name_ = NewConstChar(String::ScrubName(func_name));
446+
if (!cls.IsTopLevel()) {
447+
const auto& class_name = String::Handle(zone, cls.Name());
448+
class_name_ = NewConstChar(class_name.ToCString());
449+
}
450+
} else {
451+
ASSERT(closure_tuple_handle != nullptr);
433452
}
434453

435454
// Inherit flags from spawning isolate.
@@ -446,14 +465,14 @@ IsolateSpawnState::IsolateSpawnState(Dart_Port parent_port,
446465
Dart_Port on_exit_port,
447466
Dart_Port on_error_port,
448467
const char* debug_name,
449-
IsolateGroup* group)
468+
IsolateGroup* isolate_group)
450469
: parent_port_(parent_port),
451470
on_exit_port_(on_exit_port),
452471
on_error_port_(on_error_port),
453472
script_url_(script_url),
454473
package_config_(package_config),
455474
debug_name_(debug_name),
456-
isolate_group_(group),
475+
isolate_group_(isolate_group),
457476
serialized_args_(args_buffer->StealMessage()),
458477
serialized_message_(message_buffer->StealMessage()),
459478
isolate_flags_(),
@@ -734,20 +753,32 @@ class SpawnIsolateTask : public ThreadPool::Task {
734753
bool EnqueueEntrypointInvocationAndNotifySpawner(Thread* thread) {
735754
auto isolate = thread->isolate();
736755
auto zone = thread->zone();
756+
const bool is_spawn_uri = state_->is_spawn_uri();
737757

738758
// Step 1) Resolve the entrypoint function.
739-
auto& result = Object::Handle(zone, state_->ResolveFunction());
740-
const bool is_spawn_uri = state_->is_spawn_uri();
741-
if (result.IsError()) {
742-
ASSERT(is_spawn_uri);
743-
ReportError("Failed to resolve entrypoint function.");
744-
return false;
759+
auto& entrypoint_closure = Closure::Handle(zone);
760+
if (state_->closure_tuple_handle() != nullptr) {
761+
const auto& result = Object::Handle(
762+
zone,
763+
ReadObjectGraphCopyMessage(thread, state_->closure_tuple_handle()));
764+
if (result.IsError()) {
765+
ReportError(
766+
"Failed to deserialize the passed entrypoint to the new isolate.");
767+
return false;
768+
}
769+
entrypoint_closure = Closure::RawCast(result.ptr());
770+
} else {
771+
const auto& result = Object::Handle(zone, state_->ResolveFunction());
772+
if (result.IsError()) {
773+
ASSERT(is_spawn_uri);
774+
ReportError("Failed to resolve entrypoint function.");
775+
return false;
776+
}
777+
ASSERT(result.IsFunction());
778+
auto& func = Function::Handle(zone, Function::Cast(result).ptr());
779+
func = func.ImplicitClosureFunction();
780+
entrypoint_closure = func.ImplicitStaticClosure();
745781
}
746-
ASSERT(result.IsFunction());
747-
auto& func = Function::Handle(zone, Function::Cast(result).ptr());
748-
func = func.ImplicitClosureFunction();
749-
const auto& entrypoint_closure =
750-
Object::Handle(zone, func.ImplicitStaticClosure());
751782

752783
// Step 2) Enqueue delayed invocation of entrypoint callback.
753784
const auto& args_obj = Object::Handle(zone, state_->BuildArgs(thread));
@@ -776,7 +807,8 @@ class SpawnIsolateTask : public ThreadPool::Task {
776807
const auto& entry_point =
777808
Function::Handle(zone, lib.LookupLocalFunction(entry_name));
778809
ASSERT(entry_point.IsFunction() && !entry_point.IsNull());
779-
result = DartEntry::InvokeFunction(entry_point, args);
810+
const auto& result =
811+
Object::Handle(zone, DartEntry::InvokeFunction(entry_point, args));
780812
if (result.IsError()) {
781813
ReportError("Failed to enqueue delayed entrypoint invocation.");
782814
return false;
@@ -858,10 +890,23 @@ static const char* String2UTF8(const String& str) {
858890
return result;
859891
}
860892

893+
static FunctionPtr GetTopLevelFunction(Zone* zone, const Instance& closure) {
894+
if (closure.IsClosure()) {
895+
auto& func = Function::Handle(zone);
896+
func = Closure::Cast(closure).function();
897+
if (func.IsImplicitClosureFunction() && func.is_static()) {
898+
ASSERT(Closure::Cast(closure).context() == Context::null());
899+
// Get the parent function so that we get the right function name.
900+
return func.parent_function();
901+
}
902+
}
903+
return Function::null();
904+
}
905+
861906
DEFINE_NATIVE_ENTRY(Isolate_spawnFunction, 0, 10) {
862907
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
863908
GET_NON_NULL_NATIVE_ARGUMENT(String, script_uri, arguments->NativeArgAt(1));
864-
GET_NON_NULL_NATIVE_ARGUMENT(Instance, closure, arguments->NativeArgAt(2));
909+
GET_NON_NULL_NATIVE_ARGUMENT(Closure, closure, arguments->NativeArgAt(2));
865910
GET_NON_NULL_NATIVE_ARGUMENT(Instance, message, arguments->NativeArgAt(3));
866911
GET_NON_NULL_NATIVE_ARGUMENT(Bool, paused, arguments->NativeArgAt(4));
867912
GET_NATIVE_ARGUMENT(Bool, fatalErrors, arguments->NativeArgAt(5));
@@ -870,51 +915,62 @@ DEFINE_NATIVE_ENTRY(Isolate_spawnFunction, 0, 10) {
870915
GET_NATIVE_ARGUMENT(String, packageConfig, arguments->NativeArgAt(8));
871916
GET_NATIVE_ARGUMENT(String, debugName, arguments->NativeArgAt(9));
872917

873-
if (closure.IsClosure()) {
874-
Function& func = Function::Handle();
875-
func = Closure::Cast(closure).function();
876-
if (func.IsImplicitClosureFunction() && func.is_static()) {
877-
#if defined(DEBUG)
878-
Context& ctx = Context::Handle();
879-
ctx = Closure::Cast(closure).context();
880-
ASSERT(ctx.IsNull());
881-
#endif
882-
// Get the parent function so that we get the right function name.
883-
func = func.parent_function();
884-
885-
bool fatal_errors = fatalErrors.IsNull() ? true : fatalErrors.value();
886-
Dart_Port on_exit_port = onExit.IsNull() ? ILLEGAL_PORT : onExit.Id();
887-
Dart_Port on_error_port = onError.IsNull() ? ILLEGAL_PORT : onError.Id();
888-
889-
// We first try to serialize the message. In case the message is not
890-
// serializable this will throw an exception.
891-
SerializedObjectBuffer message_buffer;
892-
message_buffer.set_message(WriteMessage(
893-
/* can_send_any_object */ true,
894-
/* same_group */ FLAG_enable_isolate_groups, message, ILLEGAL_PORT,
895-
Message::kNormalPriority));
896-
897-
const char* utf8_package_config =
898-
packageConfig.IsNull() ? NULL : String2UTF8(packageConfig);
899-
const char* utf8_debug_name =
900-
debugName.IsNull() ? NULL : String2UTF8(debugName);
901-
902-
std::unique_ptr<IsolateSpawnState> state(new IsolateSpawnState(
903-
port.Id(), isolate->origin_id(), String2UTF8(script_uri), func,
904-
&message_buffer, utf8_package_config, paused.value(), fatal_errors,
905-
on_exit_port, on_error_port, utf8_debug_name, isolate->group()));
906-
907-
// Since this is a call to Isolate.spawn, copy the parent isolate's code.
908-
state->isolate_flags()->copy_parent_code = true;
909-
910-
isolate->group()->thread_pool()->Run<SpawnIsolateTask>(isolate,
911-
std::move(state));
912-
return Object::null();
918+
const auto& func = Function::Handle(zone, GetTopLevelFunction(zone, closure));
919+
PersistentHandle* closure_tuple_handle = nullptr;
920+
if (func.IsNull()) {
921+
if (!FLAG_enable_isolate_groups) {
922+
const String& msg = String::Handle(String::New(
923+
"Isolate.spawn expects to be passed a static or top-level function"));
924+
Exceptions::ThrowArgumentError(msg);
925+
} else {
926+
// We have a non-toplevel closure that we might need to copy.
927+
// Result will be [<closure-copy>, <objects-in-msg-to-rehash>]
928+
const auto& closure_copy_tuple = Object::Handle(
929+
zone, CopyMutableObjectGraph(closure)); // Throws if it fails.
930+
ASSERT(closure_copy_tuple.IsArray());
931+
ASSERT(Object::Handle(zone, Array::Cast(closure_copy_tuple).At(0))
932+
.IsClosure());
933+
closure_tuple_handle =
934+
isolate->group()->api_state()->AllocatePersistentHandle();
935+
closure_tuple_handle->set_ptr(closure_copy_tuple.ptr());
913936
}
914937
}
915-
const String& msg = String::Handle(String::New(
916-
"Isolate.spawn expects to be passed a static or top-level function"));
917-
Exceptions::ThrowArgumentError(msg);
938+
939+
bool fatal_errors = fatalErrors.IsNull() ? true : fatalErrors.value();
940+
Dart_Port on_exit_port = onExit.IsNull() ? ILLEGAL_PORT : onExit.Id();
941+
Dart_Port on_error_port = onError.IsNull() ? ILLEGAL_PORT : onError.Id();
942+
943+
// We first try to serialize the message. In case the message is not
944+
// serializable this will throw an exception.
945+
SerializedObjectBuffer message_buffer;
946+
message_buffer.set_message(WriteMessage(
947+
/* can_send_any_object */ true,
948+
/* same_group */ FLAG_enable_isolate_groups, message, ILLEGAL_PORT,
949+
Message::kNormalPriority));
950+
951+
const char* utf8_package_config =
952+
packageConfig.IsNull() ? NULL : String2UTF8(packageConfig);
953+
const char* utf8_debug_name =
954+
debugName.IsNull() ? NULL : String2UTF8(debugName);
955+
if (closure_tuple_handle != nullptr && utf8_debug_name == nullptr) {
956+
ASSERT(func.IsNull());
957+
958+
const auto& closure_function = Function::Handle(zone, closure.function());
959+
utf8_debug_name =
960+
NewConstChar(closure_function.QualifiedUserVisibleNameCString());
961+
}
962+
963+
std::unique_ptr<IsolateSpawnState> state(new IsolateSpawnState(
964+
port.Id(), isolate->origin_id(), String2UTF8(script_uri), func,
965+
closure_tuple_handle, &message_buffer, utf8_package_config,
966+
paused.value(), fatal_errors, on_exit_port, on_error_port,
967+
utf8_debug_name, isolate->group()));
968+
969+
// Since this is a call to Isolate.spawn, copy the parent isolate's code.
970+
state->isolate_flags()->copy_parent_code = true;
971+
972+
isolate->group()->thread_pool()->Run<SpawnIsolateTask>(isolate,
973+
std::move(state));
918974
return Object::null();
919975
}
920976

0 commit comments

Comments
 (0)