// Copyright (c) 2015, 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 "vm/service_isolate.h" #include "vm/compiler/jit/compiler.h" #include "vm/dart_api_impl.h" #include "vm/dart_api_message.h" #include "vm/dart_entry.h" #include "vm/isolate.h" #include "vm/lockers.h" #include "vm/message.h" #include "vm/message_handler.h" #include "vm/message_snapshot.h" #include "vm/native_arguments.h" #include "vm/native_entry.h" #include "vm/object.h" #include "vm/object_store.h" #include "vm/port.h" #include "vm/service.h" #include "vm/symbols.h" #include "vm/thread_pool.h" #include "vm/timeline.h" #if !defined(PRODUCT) namespace dart { #define Z (T->zone()) DEFINE_FLAG(bool, trace_service, false, "Trace VM service requests."); DEFINE_FLAG(bool, trace_service_pause_events, false, "Trace VM service isolate pause events."); DEFINE_FLAG(bool, trace_service_verbose, false, "Provide extra service tracing information."); // These must be kept in sync with service/constants.dart #define VM_SERVICE_ISOLATE_EXIT_MESSAGE_ID 0 #define VM_SERVICE_ISOLATE_STARTUP_MESSAGE_ID 1 #define VM_SERVICE_ISOLATE_SHUTDOWN_MESSAGE_ID 2 #define VM_SERVICE_WEB_SERVER_CONTROL_MESSAGE_ID 3 #define VM_SERVICE_SERVER_INFO_MESSAGE_ID 4 #define VM_SERVICE_METHOD_CALL_FROM_NATIVE 5 bool ServiceIsolate::SendServiceControlMessage(Thread* thread, Dart_Port port_id, intptr_t code, const char* name) { Dart_CObject ccode; ccode.type = Dart_CObject_kInt32; ccode.value.as_int32 = code; Dart_CObject port_int; port_int.type = Dart_CObject_kInt64; port_int.value.as_int64 = port_id; Dart_CObject send_port; send_port.type = Dart_CObject_kSendPort; send_port.value.as_send_port.id = port_id; send_port.value.as_send_port.origin_id = port_id; Dart_CObject cname; cname.type = Dart_CObject_kString; cname.value.as_string = const_cast(name); Dart_CObject* values[4]; values[0] = &ccode; values[1] = &port_int; values[2] = &send_port; values[3] = &cname; Dart_CObject message; message.type = Dart_CObject_kArray; message.value.as_array.length = 4; message.value.as_array.values = values; return PortMap::PostMessage(WriteApiMessage(thread->zone(), &message, port_, Message::kNormalPriority)); } static ArrayPtr MakeServerControlMessage(const SendPort& sp, intptr_t code, bool enable, const Bool& silenceOutput) { const Array& list = Array::Handle(Array::New(4)); ASSERT(!list.IsNull()); list.SetAt(0, Integer::Handle(Integer::New(code))); list.SetAt(1, sp); list.SetAt(2, Bool::Get(enable)); list.SetAt(3, silenceOutput); return list.ptr(); } const char* ServiceIsolate::kName = DART_VM_SERVICE_ISOLATE_NAME; Dart_IsolateGroupCreateCallback ServiceIsolate::create_group_callback_ = nullptr; Monitor* ServiceIsolate::monitor_ = new Monitor(); ServiceIsolate::State ServiceIsolate::state_ = ServiceIsolate::kStopped; Isolate* ServiceIsolate::isolate_ = nullptr; Dart_Port ServiceIsolate::port_ = ILLEGAL_PORT; Dart_Port ServiceIsolate::origin_ = ILLEGAL_PORT; char* ServiceIsolate::server_address_ = nullptr; char* ServiceIsolate::startup_failure_reason_ = nullptr; void ServiceIsolate::RequestServerInfo(const SendPort& sp) { const Array& message = Array::Handle(MakeServerControlMessage( sp, VM_SERVICE_SERVER_INFO_MESSAGE_ID, false /* ignored */, Bool::Handle() /* ignored */)); ASSERT(!message.IsNull()); PortMap::PostMessage(WriteMessage(/* same_group */ false, message, port_, Message::kNormalPriority)); } void ServiceIsolate::ControlWebServer(const SendPort& sp, bool enable, const Bool& silenceOutput) { const Array& message = Array::Handle(MakeServerControlMessage( sp, VM_SERVICE_WEB_SERVER_CONTROL_MESSAGE_ID, enable, silenceOutput)); ASSERT(!message.IsNull()); PortMap::PostMessage(WriteMessage(/* same_group */ false, message, port_, Message::kNormalPriority)); } void ServiceIsolate::SetServerAddress(const char* address) { if (server_address_ != nullptr) { free(server_address_); server_address_ = nullptr; } if (address == nullptr) { return; } server_address_ = Utils::StrDup(address); } bool ServiceIsolate::Exists() { MonitorLocker ml(monitor_); return isolate_ != nullptr; } bool ServiceIsolate::IsRunning() { MonitorLocker ml(monitor_); return (port_ != ILLEGAL_PORT) && (isolate_ != nullptr); } bool ServiceIsolate::IsServiceIsolateDescendant(Isolate* isolate) { MonitorLocker ml(monitor_); return isolate->group()->id() == origin_; } Dart_Port ServiceIsolate::Port() { MonitorLocker ml(monitor_); return port_; } void ServiceIsolate::WaitForServiceIsolateStartup() { MonitorLocker ml(monitor_); while (state_ == kStarting) { ml.Wait(); } } bool ServiceIsolate::SendServiceRpc(uint8_t* request_json, intptr_t request_json_length, Dart_Port reply_port, char** error) { // Keep in sync with "sdk/lib/vmservice/vmservice.dart:_handleNativeRpcCall". Dart_CObject opcode; opcode.type = Dart_CObject_kInt32; opcode.value.as_int32 = VM_SERVICE_METHOD_CALL_FROM_NATIVE; Dart_CObject message; message.type = Dart_CObject_kTypedData; message.value.as_typed_data.type = Dart_TypedData_kUint8; message.value.as_typed_data.length = request_json_length; message.value.as_typed_data.values = request_json; Dart_CObject send_port; send_port.type = Dart_CObject_kSendPort; send_port.value.as_send_port.id = reply_port; send_port.value.as_send_port.origin_id = ILLEGAL_PORT; Dart_CObject* request_array[] = { &opcode, &message, &send_port, }; Dart_CObject request; request.type = Dart_CObject_kArray; request.value.as_array.values = request_array; request.value.as_array.length = ARRAY_SIZE(request_array); ServiceIsolate::WaitForServiceIsolateStartup(); Dart_Port service_port = ServiceIsolate::Port(); bool success = false; if (service_port != ILLEGAL_PORT) { success = Dart_PostCObject(service_port, &request); if (!success && error != nullptr) { *error = Utils::StrDup("Was unable to post message to service isolate."); } } else { if (error != nullptr) { if (startup_failure_reason_ != nullptr) { *error = OS::SCreate(/*zone=*/nullptr, "Service isolate failed to start up: %s.", startup_failure_reason_); } else { *error = Utils::StrDup("No service isolate port was found."); } } } return success; } bool ServiceIsolate::SendIsolateStartupMessage() { if (!IsRunning()) { return false; } Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); if (isolate->is_vm_isolate()) { return false; } Dart_Port main_port = Dart_GetMainPortId(); if (FLAG_trace_service) { OS::PrintErr(DART_VM_SERVICE_ISOLATE_NAME ": Isolate %s %" Pd64 " registered.\n", isolate->name(), main_port); } bool result = SendServiceControlMessage(thread, main_port, VM_SERVICE_ISOLATE_STARTUP_MESSAGE_ID, isolate->name()); isolate->set_is_service_registered(true); return result; } bool ServiceIsolate::SendIsolateShutdownMessage() { if (!IsRunning()) { return false; } Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); if (isolate->is_vm_isolate()) { return false; } Dart_Port main_port = isolate->main_port(); if (FLAG_trace_service) { OS::PrintErr(DART_VM_SERVICE_ISOLATE_NAME ": Isolate %s %" Pd64 " deregistered.\n", isolate->name(), main_port); } isolate->set_is_service_registered(false); return SendServiceControlMessage(thread, main_port, VM_SERVICE_ISOLATE_SHUTDOWN_MESSAGE_ID, isolate->name()); } void ServiceIsolate::SendServiceExitMessage() { if (!IsRunning()) { return; } if (FLAG_trace_service) { OS::PrintErr(DART_VM_SERVICE_ISOLATE_NAME ": sending service exit message.\n"); } Dart_CObject code; code.type = Dart_CObject_kInt32; code.value.as_int32 = VM_SERVICE_ISOLATE_EXIT_MESSAGE_ID; Dart_CObject* values[1] = {&code}; Dart_CObject message; message.type = Dart_CObject_kArray; message.value.as_array.length = 1; message.value.as_array.values = values; AllocOnlyStackZone zone; PortMap::PostMessage(WriteApiMessage(zone.GetZone(), &message, port_, Message::kNormalPriority)); } void ServiceIsolate::SetServicePort(Dart_Port port) { MonitorLocker ml(monitor_); port_ = port; } void ServiceIsolate::SetServiceIsolate(Isolate* isolate) { MonitorLocker ml(monitor_); isolate_ = isolate; if (isolate_ != nullptr) { ASSERT(isolate->is_service_isolate()); origin_ = isolate_->group()->id(); } } void ServiceIsolate::MaybeMakeServiceIsolate(Isolate* I) { Thread* T = Thread::Current(); ASSERT(I == T->isolate()); ASSERT(I != nullptr); ASSERT(I->name() != nullptr); if (!I->is_service_isolate()) { // Not service isolate. return; } if (Exists()) { // Service isolate already exists. return; } SetServiceIsolate(I); } void ServiceIsolate::FinishedExiting() { MonitorLocker ml(monitor_); ASSERT(state_ == kStarted || state_ == kStopping); state_ = kStopped; port_ = ILLEGAL_PORT; isolate_ = nullptr; ml.NotifyAll(); } void ServiceIsolate::FinishedInitializing() { MonitorLocker ml(monitor_); ASSERT(state_ == kStarting); state_ = kStarted; ml.NotifyAll(); } void ServiceIsolate::InitializingFailed(char* error) { MonitorLocker ml(monitor_); ASSERT(state_ == kStarting); state_ = kStopped; port_ = ILLEGAL_PORT; startup_failure_reason_ = error; ml.NotifyAll(); } class RunServiceTask : public ThreadPool::Task { public: virtual void Run() { ASSERT(Isolate::Current() == nullptr); #if defined(SUPPORT_TIMELINE) TimelineBeginEndScope tbes(Timeline::GetVMStream(), "ServiceIsolateStartup"); #endif // SUPPORT_TIMELINE char* error = nullptr; Isolate* isolate = nullptr; const auto create_group_callback = ServiceIsolate::create_group_callback(); ASSERT(create_group_callback != nullptr); Dart_IsolateFlags api_flags; Isolate::FlagsInitialize(&api_flags); api_flags.is_system_isolate = true; api_flags.is_service_isolate = true; isolate = reinterpret_cast( create_group_callback(ServiceIsolate::kName, ServiceIsolate::kName, nullptr, nullptr, &api_flags, nullptr, &error)); if (isolate == nullptr) { if (FLAG_trace_service) { OS::PrintErr(DART_VM_SERVICE_ISOLATE_NAME ": Isolate creation error: %s\n", error); } char* formatted_error = OS::SCreate( /*zone=*/nullptr, "Invoking the 'create_group' failed with: '%s'", error); free(error); error = nullptr; ServiceIsolate::InitializingFailed(formatted_error); return; } char* main_error = nullptr; { ASSERT(Isolate::Current() == nullptr); StartIsolateScope start_scope(isolate); main_error = RunMain(isolate); } // If we failed to run 'main' of the service isolate then there is // reason to keep it running, it might be in an inconsistent state. // e.g. it could have no port to communicate with it. Declare // initialization failure and shut it down. if (main_error != nullptr) { if (FLAG_trace_service) { OS::PrintErr("vm-service: encountered an error: %s\n", main_error); } ShutdownIsolate(reinterpret_cast(isolate)); ServiceIsolate::InitializingFailed(main_error); return; } ServiceIsolate::FinishedInitializing(); isolate->message_handler()->Run( isolate->group()->thread_pool(), nullptr, [](uword parameter) { ShutdownIsolate(reinterpret_cast(parameter)); ServiceIsolate::FinishedExiting(); }, reinterpret_cast(isolate)); } protected: static void ShutdownIsolate(Dart_Isolate isolate) { if (FLAG_trace_service) { OS::PrintErr("vm-service: ShutdownIsolate\n"); } Dart_EnterIsolate(isolate); { auto T = Thread::Current(); TransitionNativeToVM transition(T); StackZone zone(T); HandleScope handle_scope(T); auto I = T->isolate(); ASSERT(I->is_service_isolate()); // Print the error if there is one. This may execute dart code to // print the exception object, so we need to use a StartIsolateScope. Error& error = Error::Handle(Z); error = T->sticky_error(); if (!error.IsNull() && !error.IsUnwindError()) { OS::PrintErr(DART_VM_SERVICE_ISOLATE_NAME ": Error: %s\n", error.ToErrorCString()); } error = I->sticky_error(); if (!error.IsNull() && !error.IsUnwindError()) { OS::PrintErr(DART_VM_SERVICE_ISOLATE_NAME ": Error: %s\n", error.ToErrorCString()); } } Dart_ShutdownIsolate(); if (FLAG_trace_service) { OS::PrintErr(DART_VM_SERVICE_ISOLATE_NAME ": Shutdown.\n"); } } // Returns an error message if fails. DART_WARN_UNUSED_RESULT char* RunMain(Isolate* I) { Thread* T = Thread::Current(); ASSERT(I == T->isolate()); StackZone zone(T); // Invoke main which will set up the service port. const Library& root_library = Library::Handle(Z, I->group()->object_store()->root_library()); if (root_library.IsNull()) { if (FLAG_trace_service) { OS::PrintErr(DART_VM_SERVICE_ISOLATE_NAME ": Embedder did not install a script.\n"); } // Service isolate is not supported by embedder. return Utils::StrDup("Service isolate is not supported by embedder."); } ASSERT(!root_library.IsNull()); const String& entry_name = Symbols::main(); ASSERT(!entry_name.IsNull()); const Function& entry = Function::Handle( Z, root_library.LookupFunctionAllowPrivate(entry_name)); if (entry.IsNull()) { // Service isolate is not supported by embedder. if (FLAG_trace_service) { OS::PrintErr(DART_VM_SERVICE_ISOLATE_NAME ": Embedder did not provide a main function.\n"); } return Utils::StrDup( "Embedder did not provide main function for service isolate."); } ASSERT(!entry.IsNull()); const Object& result = Object::Handle( Z, DartEntry::InvokeFunction(entry, Object::empty_array())); if (result.IsError()) { // Service isolate did not initialize properly. const char* error_cstr = Error::Cast(result).ToErrorCString(); if (FLAG_trace_service) { OS::PrintErr(DART_VM_SERVICE_ISOLATE_NAME ": Calling main resulted in an error: %s\n", error_cstr); } return OS::SCreate(/*zone=*/nullptr, "Service isolate main resulted in error: %s", error_cstr); } return nullptr; // No error. } }; void ServiceIsolate::Run() { { MonitorLocker ml(monitor_); ASSERT(state_ == kStopped); state_ = kStarting; ml.NotifyAll(); } // Grab the isolate create callback here to avoid race conditions with tests // that change this after Dart_Initialize returns. create_group_callback_ = Isolate::CreateGroupCallback(); if (create_group_callback_ == nullptr) { ServiceIsolate::InitializingFailed( Utils::StrDup("The 'create_group' callback was not provided")); return; } bool task_started = Dart::thread_pool()->Run(); ASSERT(task_started); } void ServiceIsolate::KillServiceIsolate() { { MonitorLocker ml(monitor_); if (state_ == kStopped) { return; } ASSERT(state_ == kStarted); state_ = kStopping; ml.NotifyAll(); } Isolate::KillIfExists(isolate_, Isolate::kInternalKillMsg); { MonitorLocker ml(monitor_); while (state_ == kStopping) { ml.Wait(); } ASSERT(state_ == kStopped); } } void ServiceIsolate::Shutdown() { { MonitorLocker ml(monitor_); while (state_ == kStarting) { ml.Wait(); } } if (IsRunning()) { { MonitorLocker ml(monitor_); ASSERT(state_ == kStarted); state_ = kStopping; ml.NotifyAll(); } SendServiceExitMessage(); { MonitorLocker ml(monitor_); while (state_ == kStopping) { ml.Wait(); } ASSERT(state_ == kStopped); } } else { if (isolate_ != nullptr) { // TODO(johnmccutchan,turnidge) When it is possible to properly create // the VMService object and set up its shutdown handler in the service // isolate's main() function, this case will no longer be possible and // can be removed. KillServiceIsolate(); } } if (server_address_ != nullptr) { free(server_address_); server_address_ = nullptr; } if (startup_failure_reason_ != nullptr) { free(startup_failure_reason_); startup_failure_reason_ = nullptr; } } void ServiceIsolate::BootVmServiceLibrary() { Thread* thread = Thread::Current(); const Library& vmservice_library = Library::Handle(Library::LookupLibrary(thread, Symbols::DartVMService())); ASSERT(!vmservice_library.IsNull()); const String& boot_function_name = String::Handle(String::New("boot")); const Function& boot_function = Function::Handle( vmservice_library.LookupFunctionAllowPrivate(boot_function_name)); ASSERT(!boot_function.IsNull()); const Object& result = Object::Handle( DartEntry::InvokeFunction(boot_function, Object::empty_array())); ASSERT(!result.IsNull()); if (result.IsUnwindError() || result.IsUnhandledException()) { Exceptions::PropagateError(Error::Cast(result)); } Dart_Port port = ILLEGAL_PORT; if (result.IsReceivePort()) { port = ReceivePort::Cast(result).Id(); } ASSERT(port != ILLEGAL_PORT); ServiceIsolate::SetServicePort(port); } void ServiceIsolate::RegisterRunningIsolates( const GrowableArray& isolate_ports, const GrowableArray& isolate_names) { auto thread = Thread::Current(); auto zone = thread->zone(); ASSERT(thread->isolate()->is_service_isolate()); // Obtain "_registerIsolate" function to call. const String& library_url = Symbols::DartVMService(); ASSERT(!library_url.IsNull()); const Library& library = Library::Handle(zone, Library::LookupLibrary(thread, library_url)); ASSERT(!library.IsNull()); const String& function_name = String::Handle(zone, String::New("_registerIsolate")); ASSERT(!function_name.IsNull()); const Function& register_function_ = Function::Handle(zone, library.LookupFunctionAllowPrivate(function_name)); ASSERT(!register_function_.IsNull()); Integer& port_int = Integer::Handle(zone); SendPort& send_port = SendPort::Handle(zone); Array& args = Array::Handle(zone, Array::New(3)); Object& result = Object::Handle(zone); ASSERT(isolate_ports.length() == isolate_names.length()); for (intptr_t i = 0; i < isolate_ports.length(); ++i) { const Dart_Port port_id = isolate_ports[i]; const String& name = *isolate_names[i]; port_int = Integer::New(port_id); send_port = SendPort::New(port_id); args.SetAt(0, port_int); args.SetAt(1, send_port); args.SetAt(2, name); result = DartEntry::InvokeFunction(register_function_, args); if (FLAG_trace_service) { OS::PrintErr("vm-service: Isolate %s %" Pd64 " registered.\n", name.ToCString(), port_id); } ASSERT(!result.IsError()); } } void ServiceIsolate::VisitObjectPointers(ObjectPointerVisitor* visitor) {} } // namespace dart #endif // !defined(PRODUCT)