bf4280ff75
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 <kustermann@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Alexander Aprelev <aam@google.com>
784 lines
28 KiB
C++
784 lines
28 KiB
C++
// Copyright (c) 2012, 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.
|
|
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
#include "include/dart_native_api.h"
|
|
#include "platform/assert.h"
|
|
#include "platform/unicode.h"
|
|
#include "vm/bootstrap_natives.h"
|
|
#include "vm/class_finalizer.h"
|
|
#include "vm/dart.h"
|
|
#include "vm/dart_api_impl.h"
|
|
#include "vm/dart_api_message.h"
|
|
#include "vm/dart_entry.h"
|
|
#include "vm/exceptions.h"
|
|
#include "vm/hash_table.h"
|
|
#include "vm/lockers.h"
|
|
#include "vm/longjump.h"
|
|
#include "vm/message_handler.h"
|
|
#include "vm/object.h"
|
|
#include "vm/object_store.h"
|
|
#include "vm/port.h"
|
|
#include "vm/resolver.h"
|
|
#include "vm/service.h"
|
|
#include "vm/snapshot.h"
|
|
#include "vm/symbols.h"
|
|
|
|
namespace dart {
|
|
|
|
DEFINE_NATIVE_ENTRY(CapabilityImpl_factory, 0, 1) {
|
|
ASSERT(
|
|
TypeArguments::CheckedHandle(zone, arguments->NativeArgAt(0)).IsNull());
|
|
uint64_t id = isolate->random()->NextUInt64();
|
|
return Capability::New(id);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(CapabilityImpl_equals, 0, 2) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Capability, recv, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Capability, other, arguments->NativeArgAt(1));
|
|
return (recv.Id() == other.Id()) ? Bool::True().raw() : Bool::False().raw();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(CapabilityImpl_get_hashcode, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Capability, cap, arguments->NativeArgAt(0));
|
|
int64_t id = cap.Id();
|
|
int32_t hi = static_cast<int32_t>(id >> 32);
|
|
int32_t lo = static_cast<int32_t>(id);
|
|
int32_t hash = (hi ^ lo) & kSmiMax;
|
|
return Smi::New(hash);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(RawReceivePortImpl_factory, 0, 2) {
|
|
ASSERT(
|
|
TypeArguments::CheckedHandle(zone, arguments->NativeArgAt(0)).IsNull());
|
|
GET_NON_NULL_NATIVE_ARGUMENT(String, debug_name, arguments->NativeArgAt(1));
|
|
Dart_Port port_id = PortMap::CreatePort(isolate->message_handler());
|
|
return ReceivePort::New(port_id, debug_name, false /* not control port */);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(RawReceivePortImpl_get_id, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0));
|
|
return Integer::New(port.Id());
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(RawReceivePortImpl_get_sendport, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0));
|
|
return port.send_port();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(RawReceivePortImpl_closeInternal, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0));
|
|
Dart_Port id = port.Id();
|
|
PortMap::ClosePort(id);
|
|
return Integer::New(id);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(RawReceivePortImpl_setActive, 0, 2) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Bool, active, arguments->NativeArgAt(1));
|
|
Dart_Port id = port.Id();
|
|
PortMap::SetPortState(
|
|
id, active.value() ? PortMap::kLivePort : PortMap::kInactivePort);
|
|
return Object::null();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(SendPortImpl_get_id, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
|
|
return Integer::New(port.Id());
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(SendPortImpl_get_hashcode, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
|
|
int64_t id = port.Id();
|
|
int32_t hi = static_cast<int32_t>(id >> 32);
|
|
int32_t lo = static_cast<int32_t>(id);
|
|
int32_t hash = (hi ^ lo) & kSmiMax;
|
|
return Smi::New(hash);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(SendPortImpl_sendInternal_, 0, 2) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
|
|
// TODO(iposva): Allow for arbitrary messages to be sent.
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Instance, obj, arguments->NativeArgAt(1));
|
|
|
|
const Dart_Port destination_port_id = port.Id();
|
|
const bool can_send_any_object = isolate->origin_id() == port.origin_id();
|
|
|
|
if (ApiObjectConverter::CanConvert(obj.raw())) {
|
|
PortMap::PostMessage(
|
|
Message::New(destination_port_id, obj.raw(), Message::kNormalPriority));
|
|
} else {
|
|
MessageWriter writer(can_send_any_object);
|
|
// TODO(turnidge): Throw an exception when the return value is false?
|
|
PortMap::PostMessage(writer.WriteMessage(obj, destination_port_id,
|
|
Message::kNormalPriority));
|
|
}
|
|
return Object::null();
|
|
}
|
|
|
|
class ObjectPtrSetTraitsLayout {
|
|
public:
|
|
static bool ReportStats() { return false; }
|
|
static const char* Name() { return "RawObjectPtrSetTraits"; }
|
|
|
|
static bool IsMatch(const ObjectPtr a, const ObjectPtr b) { return a == b; }
|
|
|
|
static uword Hash(const ObjectPtr obj) { return static_cast<uword>(obj); }
|
|
};
|
|
|
|
static ObjectPtr ValidateMessageObject(Zone* zone,
|
|
Isolate* isolate,
|
|
const Object& obj) {
|
|
TIMELINE_DURATION(Thread::Current(), Isolate, "ValidateMessageObject");
|
|
|
|
class SendMessageValidator : public ObjectPointerVisitor {
|
|
public:
|
|
SendMessageValidator(IsolateGroup* isolate_group,
|
|
WeakTable* visited,
|
|
MallocGrowableArray<ObjectPtr>* const working_set)
|
|
: ObjectPointerVisitor(isolate_group),
|
|
visited_(visited),
|
|
working_set_(working_set) {}
|
|
|
|
private:
|
|
void VisitPointers(ObjectPtr* from, ObjectPtr* to) {
|
|
for (ObjectPtr* raw = from; raw <= to; raw++) {
|
|
if (!(*raw)->IsHeapObject() || (*raw)->ptr()->IsCanonical()) {
|
|
continue;
|
|
}
|
|
if (visited_->GetValueExclusive(*raw) == 1) {
|
|
continue;
|
|
}
|
|
visited_->SetValueExclusive(*raw, 1);
|
|
working_set_->Add(*raw);
|
|
}
|
|
}
|
|
|
|
WeakTable* visited_;
|
|
MallocGrowableArray<ObjectPtr>* const working_set_;
|
|
};
|
|
if (!obj.raw()->IsHeapObject() || obj.raw()->ptr()->IsCanonical()) {
|
|
return obj.raw();
|
|
}
|
|
ClassTable* class_table = isolate->class_table();
|
|
|
|
Class& klass = Class::Handle(zone);
|
|
Closure& closure = Closure::Handle(zone);
|
|
|
|
bool error_found = false;
|
|
Function& erroneous_closure_function = Function::Handle(zone);
|
|
Class& erroneous_nativewrapper_class = Class::Handle(zone);
|
|
const char* error_message = nullptr;
|
|
|
|
{
|
|
NoSafepointScope no_safepoint;
|
|
// working_set contains only elements that have not been visited yet that
|
|
// need to be processed.
|
|
// So before adding elements to working_set ensure to check visited flag,
|
|
// set visited flag at the same time as the element is added.
|
|
MallocGrowableArray<ObjectPtr> working_set;
|
|
std::unique_ptr<WeakTable> visited(new WeakTable());
|
|
|
|
SendMessageValidator visitor(isolate->group(), visited.get(), &working_set);
|
|
|
|
visited->SetValueExclusive(obj.raw(), 1);
|
|
working_set.Add(obj.raw());
|
|
|
|
while (!working_set.is_empty() && !error_found) {
|
|
ObjectPtr raw = working_set.RemoveLast();
|
|
|
|
const intptr_t cid = raw->GetClassId();
|
|
switch (cid) {
|
|
// List below matches the one in raw_object_snapshot.cc
|
|
#define MESSAGE_SNAPSHOT_ILLEGAL(type) \
|
|
case k##type##Cid: \
|
|
error_message = \
|
|
"Illegal argument in isolate message : (object is a " #type ")"; \
|
|
error_found = true; \
|
|
break;
|
|
|
|
MESSAGE_SNAPSHOT_ILLEGAL(DynamicLibrary);
|
|
MESSAGE_SNAPSHOT_ILLEGAL(MirrorReference);
|
|
MESSAGE_SNAPSHOT_ILLEGAL(Pointer);
|
|
MESSAGE_SNAPSHOT_ILLEGAL(ReceivePort);
|
|
MESSAGE_SNAPSHOT_ILLEGAL(RegExp);
|
|
MESSAGE_SNAPSHOT_ILLEGAL(StackTrace);
|
|
MESSAGE_SNAPSHOT_ILLEGAL(UserTag);
|
|
|
|
case kClosureCid: {
|
|
closure = Closure::RawCast(raw);
|
|
FunctionPtr func = closure.function();
|
|
// We only allow closure of top level methods or static functions in a
|
|
// class to be sent in isolate messages.
|
|
if (!Function::IsImplicitStaticClosureFunction(func)) {
|
|
// All other closures are errors.
|
|
erroneous_closure_function = func;
|
|
error_found = true;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
if (cid >= kNumPredefinedCids) {
|
|
klass = class_table->At(cid);
|
|
if (klass.num_native_fields() != 0) {
|
|
erroneous_nativewrapper_class = klass.raw();
|
|
error_found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
raw->ptr()->VisitPointers(&visitor);
|
|
}
|
|
}
|
|
if (error_found) {
|
|
const char* exception_message;
|
|
if (error_message != nullptr) {
|
|
exception_message = error_message;
|
|
} else if (!erroneous_closure_function.IsNull()) {
|
|
exception_message = OS::SCreate(zone,
|
|
"Illegal argument in isolate message"
|
|
" : (object is a closure - %s)",
|
|
erroneous_closure_function.ToCString());
|
|
} else {
|
|
ASSERT(!erroneous_nativewrapper_class.IsNull());
|
|
exception_message =
|
|
OS::SCreate(zone,
|
|
"Illegal argument in isolate message"
|
|
" : (object extends NativeWrapper - %s)",
|
|
erroneous_nativewrapper_class.ToCString());
|
|
}
|
|
return Exceptions::CreateUnhandledException(
|
|
zone, Exceptions::kArgumentValue, exception_message);
|
|
}
|
|
isolate->set_forward_table_new(nullptr);
|
|
return obj.raw();
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Instance, obj, arguments->NativeArgAt(1));
|
|
|
|
Object& validated_result = Object::Handle(zone);
|
|
Object& msg_obj = Object::Handle(zone, obj.raw());
|
|
validated_result = ValidateMessageObject(zone, isolate, msg_obj);
|
|
if (validated_result.IsUnhandledException()) {
|
|
Exceptions::PropagateError(Error::Cast(validated_result));
|
|
UNREACHABLE();
|
|
}
|
|
PersistentHandle* handle =
|
|
isolate->group()->api_state()->AllocatePersistentHandle();
|
|
handle->set_raw(msg_obj);
|
|
isolate->bequeath(std::unique_ptr<Bequest>(new Bequest(handle, port.Id())));
|
|
// TODO(aam): Ensure there are no dart api calls after this point as we want
|
|
// to ensure that validated message won't get tampered with.
|
|
Isolate::KillIfExists(isolate, Isolate::LibMsgId::kKillMsg);
|
|
// Drain interrupts before running so any IMMEDIATE operations on the current
|
|
// isolate happen synchronously.
|
|
const Error& error = Error::Handle(thread->HandleInterrupts());
|
|
RELEASE_ASSERT(error.IsUnwindError());
|
|
Exceptions::PropagateError(error);
|
|
// We will never execute dart code again in this isolate.
|
|
return Object::null();
|
|
}
|
|
|
|
static void ThrowIsolateSpawnException(const String& message) {
|
|
const Array& args = Array::Handle(Array::New(1));
|
|
args.SetAt(0, message);
|
|
Exceptions::ThrowByType(Exceptions::kIsolateSpawn, args);
|
|
}
|
|
|
|
class SpawnIsolateTask : public ThreadPool::Task {
|
|
public:
|
|
SpawnIsolateTask(Isolate* parent_isolate,
|
|
std::unique_ptr<IsolateSpawnState> state)
|
|
: parent_isolate_(parent_isolate), state_(std::move(state)) {
|
|
parent_isolate->IncrementSpawnCount();
|
|
}
|
|
|
|
~SpawnIsolateTask() override {
|
|
if (parent_isolate_ != nullptr) {
|
|
parent_isolate_->DecrementSpawnCount();
|
|
}
|
|
}
|
|
|
|
void Run() override {
|
|
const char* name = (state_->debug_name() == nullptr)
|
|
? state_->function_name()
|
|
: state_->debug_name();
|
|
ASSERT(name != nullptr);
|
|
|
|
auto group = state_->isolate_group();
|
|
if (!FLAG_enable_isolate_groups || group == nullptr) {
|
|
RunHeavyweight(name);
|
|
} else {
|
|
RunLightweight(name);
|
|
}
|
|
}
|
|
|
|
void RunHeavyweight(const char* name) {
|
|
// The create isolate group callback is mandatory. If not provided we
|
|
// cannot spawn isolates.
|
|
auto create_group_callback = Isolate::CreateGroupCallback();
|
|
if (create_group_callback == nullptr) {
|
|
FailedSpawn("Isolate spawn is not supported by this Dart embedder\n");
|
|
return;
|
|
}
|
|
|
|
char* error = nullptr;
|
|
|
|
// Make a copy of the state's isolate flags and hand it to the callback.
|
|
Dart_IsolateFlags api_flags = *(state_->isolate_flags());
|
|
Isolate* isolate = reinterpret_cast<Isolate*>((create_group_callback)(
|
|
state_->script_url(), name, nullptr, state_->package_config(),
|
|
&api_flags, parent_isolate_->init_callback_data(), &error));
|
|
parent_isolate_->DecrementSpawnCount();
|
|
parent_isolate_ = nullptr;
|
|
|
|
if (isolate == nullptr) {
|
|
FailedSpawn(error);
|
|
free(error);
|
|
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);
|
|
}
|
|
|
|
void RunLightweight(const char* name) {
|
|
// The create isolate initialize callback is mandatory if
|
|
// --enable-isolate-groups was passed.
|
|
auto initialize_callback = Isolate::InitializeCallback();
|
|
if (initialize_callback == nullptr) {
|
|
FailedSpawn(
|
|
"Lightweight isolate spawn is not supported by this Dart embedder\n");
|
|
return;
|
|
}
|
|
|
|
char* error = nullptr;
|
|
|
|
auto group = state_->isolate_group();
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
Isolate* isolate = CreateWithinExistingIsolateGroupAOT(group, name, &error);
|
|
#else
|
|
Isolate* isolate = CreateWithinExistingIsolateGroup(group, name, &error);
|
|
#endif
|
|
parent_isolate_->DecrementSpawnCount();
|
|
parent_isolate_ = nullptr;
|
|
|
|
if (isolate == nullptr) {
|
|
FailedSpawn(error);
|
|
free(error);
|
|
return;
|
|
}
|
|
|
|
void* child_isolate_data = nullptr;
|
|
const bool success = initialize_callback(&child_isolate_data, &error);
|
|
if (!success) {
|
|
Dart_ShutdownIsolate();
|
|
FailedSpawn(error);
|
|
free(error);
|
|
return;
|
|
}
|
|
|
|
isolate->set_init_callback_data(child_isolate_data);
|
|
Dart_ExitIsolate();
|
|
Run(isolate);
|
|
}
|
|
|
|
private:
|
|
void Run(Isolate* child) {
|
|
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_));
|
|
child->RunViaSpawnApi();
|
|
}
|
|
|
|
void FailedSpawn(const char* error) {
|
|
ReportError(error != nullptr
|
|
? error
|
|
: "Unknown error occured during Isolate spawning.");
|
|
state_ = nullptr;
|
|
}
|
|
|
|
void ReportError(const char* error) {
|
|
Dart_CObject error_cobj;
|
|
error_cobj.type = Dart_CObject_kString;
|
|
error_cobj.value.as_string = const_cast<char*>(error);
|
|
if (!Dart_PostCObject(state_->parent_port(), &error_cobj)) {
|
|
// Perhaps the parent isolate died or closed the port before we
|
|
// could report the error. Ignore.
|
|
}
|
|
}
|
|
|
|
Isolate* parent_isolate_;
|
|
std::unique_ptr<IsolateSpawnState> state_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(SpawnIsolateTask);
|
|
};
|
|
|
|
static const char* String2UTF8(const String& str) {
|
|
intptr_t len = Utf8::Length(str);
|
|
char* result = new char[len + 1];
|
|
str.ToUTF8(reinterpret_cast<uint8_t*>(result), len);
|
|
result[len] = 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(Isolate_spawnFunction, 0, 11) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(String, script_uri, arguments->NativeArgAt(1));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Instance, closure, arguments->NativeArgAt(2));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Instance, message, arguments->NativeArgAt(3));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Bool, paused, arguments->NativeArgAt(4));
|
|
GET_NATIVE_ARGUMENT(Bool, fatalErrors, arguments->NativeArgAt(5));
|
|
GET_NATIVE_ARGUMENT(SendPort, onExit, arguments->NativeArgAt(6));
|
|
GET_NATIVE_ARGUMENT(SendPort, onError, arguments->NativeArgAt(7));
|
|
GET_NATIVE_ARGUMENT(String, packageConfig, arguments->NativeArgAt(8));
|
|
GET_NATIVE_ARGUMENT(Bool, newIsolateGroup, arguments->NativeArgAt(9));
|
|
GET_NATIVE_ARGUMENT(String, debugName, arguments->NativeArgAt(10));
|
|
|
|
if (closure.IsClosure()) {
|
|
Function& func = Function::Handle();
|
|
func = Closure::Cast(closure).function();
|
|
if (func.IsImplicitClosureFunction() && func.is_static()) {
|
|
#if defined(DEBUG)
|
|
Context& ctx = Context::Handle();
|
|
ctx = Closure::Cast(closure).context();
|
|
ASSERT(ctx.IsNull());
|
|
#endif
|
|
// Get the parent function so that we get the right function name.
|
|
func = func.parent_function();
|
|
|
|
bool fatal_errors = fatalErrors.IsNull() ? true : fatalErrors.value();
|
|
Dart_Port on_exit_port = onExit.IsNull() ? ILLEGAL_PORT : onExit.Id();
|
|
Dart_Port on_error_port = onError.IsNull() ? ILLEGAL_PORT : onError.Id();
|
|
|
|
// We first try to serialize the message. In case the message is not
|
|
// serializable this will throw an exception.
|
|
SerializedObjectBuffer message_buffer;
|
|
{
|
|
MessageWriter writer(/* can_send_any_object = */ true);
|
|
message_buffer.set_message(writer.WriteMessage(
|
|
message, ILLEGAL_PORT, Message::kNormalPriority));
|
|
}
|
|
|
|
const char* utf8_package_config =
|
|
packageConfig.IsNull() ? NULL : String2UTF8(packageConfig);
|
|
const char* utf8_debug_name =
|
|
debugName.IsNull() ? NULL : String2UTF8(debugName);
|
|
const bool in_new_isolate_group = newIsolateGroup.value();
|
|
|
|
std::unique_ptr<IsolateSpawnState> state(new IsolateSpawnState(
|
|
port.Id(), isolate->origin_id(), String2UTF8(script_uri), func,
|
|
&message_buffer, utf8_package_config, paused.value(), fatal_errors,
|
|
on_exit_port, on_error_port, utf8_debug_name,
|
|
in_new_isolate_group ? nullptr : isolate->group()));
|
|
|
|
// Since this is a call to Isolate.spawn, copy the parent isolate's code.
|
|
state->isolate_flags()->copy_parent_code = true;
|
|
|
|
isolate->group()->thread_pool()->Run<SpawnIsolateTask>(isolate,
|
|
std::move(state));
|
|
return Object::null();
|
|
}
|
|
}
|
|
const String& msg = String::Handle(String::New(
|
|
"Isolate.spawn expects to be passed a static or top-level function"));
|
|
Exceptions::ThrowArgumentError(msg);
|
|
return Object::null();
|
|
}
|
|
|
|
static const char* CanonicalizeUri(Thread* thread,
|
|
const Library& library,
|
|
const String& uri,
|
|
char** error) {
|
|
const char* result = NULL;
|
|
Zone* zone = thread->zone();
|
|
Isolate* isolate = thread->isolate();
|
|
if (isolate->HasTagHandler()) {
|
|
const Object& obj = Object::Handle(
|
|
isolate->CallTagHandler(Dart_kCanonicalizeUrl, library, uri));
|
|
if (obj.IsString()) {
|
|
result = String2UTF8(String::Cast(obj));
|
|
} else if (obj.IsError()) {
|
|
Error& error_obj = Error::Handle();
|
|
error_obj ^= obj.raw();
|
|
*error = zone->PrintToString("Unable to canonicalize uri '%s': %s",
|
|
uri.ToCString(), error_obj.ToErrorCString());
|
|
} else {
|
|
*error = zone->PrintToString(
|
|
"Unable to canonicalize uri '%s': "
|
|
"library tag handler returned wrong type",
|
|
uri.ToCString());
|
|
}
|
|
} else {
|
|
*error = zone->PrintToString(
|
|
"Unable to canonicalize uri '%s': no library tag handler found.",
|
|
uri.ToCString());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(Isolate_spawnUri, 0, 12) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(String, uri, arguments->NativeArgAt(1));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Instance, args, arguments->NativeArgAt(2));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Instance, message, arguments->NativeArgAt(3));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Bool, paused, arguments->NativeArgAt(4));
|
|
GET_NATIVE_ARGUMENT(SendPort, onExit, arguments->NativeArgAt(5));
|
|
GET_NATIVE_ARGUMENT(SendPort, onError, arguments->NativeArgAt(6));
|
|
GET_NATIVE_ARGUMENT(Bool, fatalErrors, arguments->NativeArgAt(7));
|
|
GET_NATIVE_ARGUMENT(Bool, checked, arguments->NativeArgAt(8));
|
|
GET_NATIVE_ARGUMENT(Array, environment, arguments->NativeArgAt(9));
|
|
GET_NATIVE_ARGUMENT(String, packageConfig, arguments->NativeArgAt(10));
|
|
GET_NATIVE_ARGUMENT(String, debugName, arguments->NativeArgAt(11));
|
|
|
|
bool fatal_errors = fatalErrors.IsNull() ? true : fatalErrors.value();
|
|
Dart_Port on_exit_port = onExit.IsNull() ? ILLEGAL_PORT : onExit.Id();
|
|
Dart_Port on_error_port = onError.IsNull() ? ILLEGAL_PORT : onError.Id();
|
|
|
|
// We first try to serialize the arguments and the message. In case the
|
|
// arguments or the message are not serializable this will throw an exception.
|
|
SerializedObjectBuffer arguments_buffer;
|
|
SerializedObjectBuffer message_buffer;
|
|
{
|
|
MessageWriter writer(/* can_send_any_object = */ false);
|
|
arguments_buffer.set_message(
|
|
writer.WriteMessage(args, ILLEGAL_PORT, Message::kNormalPriority));
|
|
}
|
|
{
|
|
MessageWriter writer(/* can_send_any_object = */ false);
|
|
message_buffer.set_message(
|
|
writer.WriteMessage(message, ILLEGAL_PORT, Message::kNormalPriority));
|
|
}
|
|
|
|
// Canonicalize the uri with respect to the current isolate.
|
|
const Library& root_lib =
|
|
Library::Handle(isolate->object_store()->root_library());
|
|
char* error = NULL;
|
|
const char* canonical_uri = CanonicalizeUri(thread, root_lib, uri, &error);
|
|
if (canonical_uri == NULL) {
|
|
const String& msg = String::Handle(String::New(error));
|
|
ThrowIsolateSpawnException(msg);
|
|
}
|
|
|
|
const char* utf8_package_config =
|
|
packageConfig.IsNull() ? NULL : String2UTF8(packageConfig);
|
|
const char* utf8_debug_name =
|
|
debugName.IsNull() ? NULL : String2UTF8(debugName);
|
|
|
|
std::unique_ptr<IsolateSpawnState> state(new IsolateSpawnState(
|
|
port.Id(), canonical_uri, utf8_package_config, &arguments_buffer,
|
|
&message_buffer, paused.value(), fatal_errors, on_exit_port,
|
|
on_error_port, utf8_debug_name, /*group=*/nullptr));
|
|
|
|
// If we were passed a value then override the default flags state for
|
|
// checked mode.
|
|
if (!checked.IsNull()) {
|
|
Dart_IsolateFlags* flags = state->isolate_flags();
|
|
flags->enable_asserts = checked.value();
|
|
}
|
|
|
|
// Since this is a call to Isolate.spawnUri, don't copy the parent's code.
|
|
state->isolate_flags()->copy_parent_code = false;
|
|
|
|
isolate->group()->thread_pool()->Run<SpawnIsolateTask>(isolate,
|
|
std::move(state));
|
|
return Object::null();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(Isolate_getDebugName, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
|
|
auto name = Isolate::LookupIsolateNameByPort(port.Id());
|
|
if (name == nullptr) {
|
|
return String::null();
|
|
}
|
|
return String::New(name.get());
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(Isolate_getPortAndCapabilitiesOfCurrentIsolate, 0, 0) {
|
|
const Array& result = Array::Handle(Array::New(3));
|
|
result.SetAt(0, SendPort::Handle(SendPort::New(isolate->main_port())));
|
|
result.SetAt(
|
|
1, Capability::Handle(Capability::New(isolate->pause_capability())));
|
|
result.SetAt(
|
|
2, Capability::Handle(Capability::New(isolate->terminate_capability())));
|
|
return result.raw();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(Isolate_getCurrentRootUriStr, 0, 0) {
|
|
const Library& root_lib =
|
|
Library::Handle(zone, isolate->object_store()->root_library());
|
|
return root_lib.url();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(Isolate_sendOOB, 0, 2) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Array, msg, arguments->NativeArgAt(1));
|
|
|
|
// Make sure to route this request to the isolate library OOB mesage handler.
|
|
msg.SetAt(0, Smi::Handle(Smi::New(Message::kIsolateLibOOBMsg)));
|
|
|
|
MessageWriter writer(false);
|
|
PortMap::PostMessage(
|
|
writer.WriteMessage(msg, port.Id(), Message::kOOBPriority));
|
|
|
|
// Drain interrupts before running so any IMMEDIATE operations on the current
|
|
// isolate happen synchronously.
|
|
const Error& error = Error::Handle(thread->HandleInterrupts());
|
|
if (!error.IsNull()) {
|
|
Exceptions::PropagateError(error);
|
|
UNREACHABLE();
|
|
}
|
|
|
|
return Object::null();
|
|
}
|
|
|
|
static void ExternalTypedDataFinalizer(void* isolate_callback_data,
|
|
void* peer) {
|
|
free(peer);
|
|
}
|
|
|
|
static intptr_t GetTypedDataSizeOrThrow(const Instance& instance) {
|
|
// From the Dart side we are guaranteed that the type of [instance] is a
|
|
// subtype of TypedData.
|
|
if (instance.IsTypedDataBase()) {
|
|
return TypedDataBase::Cast(instance).LengthInBytes();
|
|
}
|
|
|
|
// This can happen if [instance] is `null` or an instance of a 3rd party class
|
|
// which implements [TypedData].
|
|
Exceptions::ThrowArgumentError(instance);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(TransferableTypedData_factory, 0, 2) {
|
|
ASSERT(
|
|
TypeArguments::CheckedHandle(zone, arguments->NativeArgAt(0)).IsNull());
|
|
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Instance, array_instance,
|
|
arguments->NativeArgAt(1));
|
|
|
|
Array& array = Array::Handle();
|
|
intptr_t array_length;
|
|
if (array_instance.IsGrowableObjectArray()) {
|
|
const auto& growable_array = GrowableObjectArray::Cast(array_instance);
|
|
array ^= growable_array.data();
|
|
array_length = growable_array.Length();
|
|
} else if (array_instance.IsArray()) {
|
|
array ^= Array::Cast(array_instance).raw();
|
|
array_length = array.Length();
|
|
} else {
|
|
Exceptions::ThrowArgumentError(array_instance);
|
|
UNREACHABLE();
|
|
}
|
|
Instance& instance = Instance::Handle();
|
|
uint64_t total_bytes = 0;
|
|
const uint64_t kMaxBytes = TypedData::MaxElements(kTypedDataUint8ArrayCid);
|
|
for (intptr_t i = 0; i < array_length; i++) {
|
|
instance ^= array.At(i);
|
|
total_bytes += static_cast<uintptr_t>(GetTypedDataSizeOrThrow(instance));
|
|
if (total_bytes > kMaxBytes) {
|
|
const Array& error_args = Array::Handle(Array::New(3));
|
|
error_args.SetAt(0, array);
|
|
error_args.SetAt(1, String::Handle(String::New("data")));
|
|
error_args.SetAt(
|
|
2, String::Handle(String::NewFormatted(
|
|
"Aggregated list exceeds max size %" Pu64 "", kMaxBytes)));
|
|
Exceptions::ThrowByType(Exceptions::kArgumentValue, error_args);
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
uint8_t* data = reinterpret_cast<uint8_t*>(malloc(total_bytes));
|
|
if (data == nullptr) {
|
|
const Instance& exception =
|
|
Instance::Handle(thread->isolate()->object_store()->out_of_memory());
|
|
Exceptions::Throw(thread, exception);
|
|
UNREACHABLE();
|
|
}
|
|
intptr_t offset = 0;
|
|
for (intptr_t i = 0; i < array_length; i++) {
|
|
instance ^= array.At(i);
|
|
|
|
{
|
|
NoSafepointScope no_safepoint;
|
|
const auto& typed_data = TypedDataBase::Cast(instance);
|
|
const intptr_t length_in_bytes = typed_data.LengthInBytes();
|
|
|
|
void* source = typed_data.DataAddr(0);
|
|
// The memory does not overlap.
|
|
memcpy(data + offset, source, length_in_bytes); // NOLINT
|
|
offset += length_in_bytes;
|
|
}
|
|
}
|
|
ASSERT(static_cast<uintptr_t>(offset) == total_bytes);
|
|
return TransferableTypedData::New(data, total_bytes);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(TransferableTypedData_materialize, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(TransferableTypedData, t,
|
|
arguments->NativeArgAt(0));
|
|
|
|
void* peer;
|
|
{
|
|
NoSafepointScope no_safepoint;
|
|
peer = thread->heap()->GetPeer(t.raw());
|
|
// Assume that object's Peer is only used to track transferrability state.
|
|
ASSERT(peer != nullptr);
|
|
}
|
|
|
|
TransferableTypedDataPeer* tpeer =
|
|
reinterpret_cast<TransferableTypedDataPeer*>(peer);
|
|
const intptr_t length = tpeer->length();
|
|
uint8_t* data = tpeer->data();
|
|
if (data == nullptr) {
|
|
const auto& error = String::Handle(String::New(
|
|
"Attempt to materialize object that was transferred already."));
|
|
Exceptions::ThrowArgumentError(error);
|
|
UNREACHABLE();
|
|
}
|
|
tpeer->ClearData();
|
|
|
|
const ExternalTypedData& typed_data = ExternalTypedData::Handle(
|
|
ExternalTypedData::New(kExternalTypedDataUint8ArrayCid, data, length,
|
|
thread->heap()->SpaceForExternal(length)));
|
|
FinalizablePersistentHandle::New(thread->isolate(), typed_data,
|
|
/* peer= */ data,
|
|
&ExternalTypedDataFinalizer, length,
|
|
/*auto_delete=*/true);
|
|
return typed_data.raw();
|
|
}
|
|
|
|
} // namespace dart
|