| // Copyright 2013 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "runner.h" | |
| #include <fcntl.h> | |
| #include <fuchsia/mem/cpp/fidl.h> | |
| #include <lib/async/cpp/task.h> | |
| #include <lib/inspect/cpp/inspect.h> | |
| #include <lib/trace-engine/instrumentation.h> | |
| #include <zircon/status.h> | |
| #include <zircon/types.h> | |
| #include <cstdint> | |
| #include <sstream> | |
| #include <utility> | |
| #include "flutter/fml/make_copyable.h" | |
| #include "flutter/lib/ui/text/font_collection.h" | |
| #include "flutter/runtime/dart_vm.h" | |
| #include "lib/async/default.h" | |
| #include "lib/sys/cpp/component_context.h" | |
| #include "runtime/dart/utils/files.h" | |
| #include "runtime/dart/utils/root_inspect_node.h" | |
| #include "runtime/dart/utils/vmo.h" | |
| #include "runtime/dart/utils/vmservice_object.h" | |
| #include "third_party/icu/source/common/unicode/udata.h" | |
| #include "third_party/skia/include/core/SkGraphics.h" | |
| namespace flutter_runner { | |
| namespace { | |
| static constexpr char kIcuDataPath[] = "/pkg/data/icudtl.dat"; | |
| // Environment variable containing the path to the directory containing the | |
| // timezone files. | |
| static constexpr char kICUTZEnv[] = "ICU_TIMEZONE_FILES_DIR"; | |
| // The data directory containing ICU timezone data files. | |
| static constexpr char kICUTZDataDir[] = "/config/data/tzdata/icu/44/le"; | |
| // Map the memory into the process and return a pointer to the memory. | |
| uintptr_t GetICUData(const fuchsia::mem::Buffer& icu_data) { | |
| uint64_t data_size = icu_data.size; | |
| if (data_size > std::numeric_limits<size_t>::max()) | |
| return 0u; | |
| uintptr_t data = 0u; | |
| zx_status_t status = | |
| zx::vmar::root_self()->map(ZX_VM_PERM_READ, 0, icu_data.vmo, 0, | |
| static_cast<size_t>(data_size), &data); | |
| if (status == ZX_OK) { | |
| return data; | |
| } | |
| return 0u; | |
| } | |
| // Initializes the timezone data if available. Timezone data file in Fuchsia | |
| // is at a fixed directory path. Returns true on success. As a side effect | |
| // sets the value of the environment variable "ICU_TIMEZONE_FILES_DIR" to a | |
| // fixed value which is fuchsia-specific. | |
| bool InitializeTZData() { | |
| // We need the ability to change the env variable for testing, so not | |
| // overwriting if set. | |
| setenv(kICUTZEnv, kICUTZDataDir, 0 /* No overwrite */); | |
| const std::string tzdata_dir = getenv(kICUTZEnv); | |
| // Try opening the path to check if present. No need to verify that it is a | |
| // directory since ICU loading will return an error if the TZ data path is | |
| // wrong. | |
| int fd = openat(AT_FDCWD, tzdata_dir.c_str(), O_RDONLY); | |
| if (fd < 0) { | |
| FML_LOG(INFO) << "Could not open: '" << tzdata_dir | |
| << "', proceeding without loading the timezone database: " | |
| << strerror(errno); | |
| return false; | |
| } | |
| if (close(fd)) { | |
| FML_LOG(WARNING) << "Could not close: " << tzdata_dir << ": " | |
| << strerror(errno); | |
| } | |
| return true; | |
| } | |
| // Return value indicates if initialization was successful. | |
| bool InitializeICU() { | |
| const char* data_path = kIcuDataPath; | |
| fuchsia::mem::Buffer icu_data; | |
| if (!dart_utils::VmoFromFilename(data_path, false, &icu_data)) { | |
| return false; | |
| } | |
| uintptr_t data = GetICUData(icu_data); | |
| if (!data) { | |
| return false; | |
| } | |
| // If the loading fails, soldier on. The loading is optional as we don't | |
| // want to crash the engine in transition. | |
| InitializeTZData(); | |
| // Pass the data to ICU. | |
| UErrorCode err = U_ZERO_ERROR; | |
| udata_setCommonData(reinterpret_cast<const char*>(data), &err); | |
| if (err != U_ZERO_ERROR) { | |
| FML_LOG(ERROR) << "error loading ICU data: " << err; | |
| return false; | |
| } | |
| return true; | |
| } | |
| } // namespace | |
| static void SetProcessName() { | |
| std::stringstream stream; | |
| #if defined(DART_PRODUCT) | |
| stream << "io.flutter.product_runner."; | |
| #else | |
| stream << "io.flutter.runner."; | |
| #endif | |
| if (flutter::DartVM::IsRunningPrecompiledCode()) { | |
| stream << "aot"; | |
| } else { | |
| stream << "jit"; | |
| } | |
| const auto name = stream.str(); | |
| zx::process::self()->set_property(ZX_PROP_NAME, name.c_str(), name.size()); | |
| } | |
| static void SetThreadName(const std::string& thread_name) { | |
| zx::thread::self()->set_property(ZX_PROP_NAME, thread_name.c_str(), | |
| thread_name.size()); | |
| } | |
| #if !defined(DART_PRODUCT) | |
| // Register native symbol information for the Dart VM's profiler. | |
| static void RegisterProfilerSymbols(const char* symbols_path, | |
| const char* dso_name) { | |
| std::string* symbols = new std::string(); | |
| if (dart_utils::ReadFileToString(symbols_path, symbols)) { | |
| Dart_AddSymbols(dso_name, symbols->data(), symbols->size()); | |
| } else { | |
| FML_LOG(ERROR) << "Failed to load " << symbols_path; | |
| } | |
| } | |
| #endif // !defined(DART_PRODUCT) | |
| Runner::Runner(fml::RefPtr<fml::TaskRunner> task_runner, | |
| sys::ComponentContext* context) | |
| : task_runner_(task_runner), context_(context) { | |
| #if !defined(DART_PRODUCT) | |
| // The VM service isolate uses the process-wide namespace. It writes the | |
| // vm service protocol port under /tmp. The VMServiceObject exposes that | |
| // port number to The Hub. | |
| context_->outgoing()->debug_dir()->AddEntry( | |
| dart_utils::VMServiceObject::kPortDirName, | |
| std::make_unique<dart_utils::VMServiceObject>()); | |
| inspect::Inspector* inspector = dart_utils::RootInspectNode::GetInspector(); | |
| inspector->GetRoot().CreateLazyValues( | |
| "vmservice_port", | |
| [&]() { | |
| inspect::Inspector inspector; | |
| dart_utils::VMServiceObject::LazyEntryVector out; | |
| dart_utils::VMServiceObject().GetContents(&out); | |
| std::string name = ""; | |
| if (!out.empty()) { | |
| name = out[0].name; | |
| } | |
| inspector.GetRoot().CreateString("vm_service_port", name, &inspector); | |
| return fpromise::make_ok_promise(inspector); | |
| }, | |
| inspector); | |
| SetupTraceObserver(); | |
| #endif // !defined(DART_PRODUCT) | |
| SkGraphics::Init(); | |
| SetupICU(); | |
| SetProcessName(); | |
| SetThreadName("io.flutter.runner.main"); | |
| context_->outgoing()->AddPublicService<fuchsia::sys::Runner>( | |
| std::bind(&Runner::RegisterComponentV1, this, std::placeholders::_1)); | |
| context_->outgoing() | |
| ->AddPublicService<fuchsia::component::runner::ComponentRunner>( | |
| std::bind(&Runner::RegisterComponentV2, this, std::placeholders::_1)); | |
| #if !defined(DART_PRODUCT) | |
| if (Dart_IsPrecompiledRuntime()) { | |
| RegisterProfilerSymbols("pkg/data/flutter_aot_runner.dartprofilersymbols", | |
| ""); | |
| } else { | |
| RegisterProfilerSymbols("pkg/data/flutter_jit_runner.dartprofilersymbols", | |
| ""); | |
| } | |
| #endif // !defined(DART_PRODUCT) | |
| } | |
| Runner::~Runner() { | |
| context_->outgoing()->RemovePublicService<fuchsia::sys::Runner>(); | |
| context_->outgoing() | |
| ->RemovePublicService<fuchsia::component::runner::ComponentRunner>(); | |
| #if !defined(DART_PRODUCT) | |
| trace_observer_->Stop(); | |
| #endif // !defined(DART_PRODUCT) | |
| } | |
| // CF v1 lifecycle methods. | |
| void Runner::RegisterComponentV1( | |
| fidl::InterfaceRequest<fuchsia::sys::Runner> request) { | |
| active_components_v1_bindings_.AddBinding(this, std::move(request)); | |
| } | |
| void Runner::StartComponent( | |
| fuchsia::sys::Package package, | |
| fuchsia::sys::StartupInfo startup_info, | |
| fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller) { | |
| // TRACE_DURATION currently requires that the string data does not change | |
| // in the traced scope. Since |package| gets moved in the Component::Create | |
| // call below, we cannot ensure that |package.resolved_url| does not move or | |
| // change, so we make a copy to pass to TRACE_DURATION. | |
| // TODO(PT-169): Remove this copy when TRACE_DURATION reads string arguments | |
| // eagerly. | |
| std::string url_copy = package.resolved_url; | |
| TRACE_EVENT1("flutter", "StartComponent", "url", url_copy.c_str()); | |
| // Notes on component termination: Components typically terminate on the | |
| // thread on which they were created. This usually means the thread was | |
| // specifically created to host the component. But we want to ensure that | |
| // access to the active components collection is made on the same thread. So | |
| // we capture the runner in the termination callback. There is no risk of | |
| // there being multiple component runner instances in the process at the same | |
| // time. So it is safe to use the raw pointer. | |
| ComponentV1::TerminationCallback termination_callback = | |
| [component_runner = this](const ComponentV1* component) { | |
| component_runner->task_runner_->PostTask( | |
| [component_runner, component]() { | |
| component_runner->OnComponentV1Terminate(component); | |
| }); | |
| }; | |
| ActiveComponentV1 active_component = ComponentV1::Create( | |
| std::move(termination_callback), // termination callback | |
| std::move(package), // component package | |
| std::move(startup_info), // startup info | |
| context_->svc(), // runner incoming services | |
| std::move(controller) // controller request | |
| ); | |
| auto key = active_component.component.get(); | |
| active_components_v1_[key] = std::move(active_component); | |
| } | |
| void Runner::OnComponentV1Terminate(const ComponentV1* component) { | |
| auto app = active_components_v1_.find(component); | |
| if (app == active_components_v1_.end()) { | |
| FML_LOG(INFO) | |
| << "The remote end of the component runner tried to terminate an " | |
| "component that has already been terminated, possibly because we " | |
| "initiated the termination"; | |
| return; | |
| } | |
| ActiveComponentV1& active_component = app->second; | |
| // Grab the items out of the entry because we will have to rethread the | |
| // destruction. | |
| std::unique_ptr<ComponentV1> component_to_destroy = | |
| std::move(active_component.component); | |
| std::unique_ptr<fml::Thread> component_thread = | |
| std::move(active_component.platform_thread); | |
| // Delete the entry. | |
| active_components_v1_.erase(component); | |
| // Post the task to destroy the component and quit its message loop. | |
| component_thread->GetTaskRunner()->PostTask(fml::MakeCopyable( | |
| [instance = std::move(component_to_destroy), | |
| thread = component_thread.get()]() mutable { instance.reset(); })); | |
| // Terminate and join the thread's message loop. | |
| component_thread->Join(); | |
| } | |
| // CF v2 lifecycle methods. | |
| void Runner::RegisterComponentV2( | |
| fidl::InterfaceRequest<fuchsia::component::runner::ComponentRunner> | |
| request) { | |
| active_components_v2_bindings_.AddBinding(this, std::move(request)); | |
| } | |
| void Runner::Start( | |
| fuchsia::component::runner::ComponentStartInfo start_info, | |
| fidl::InterfaceRequest<fuchsia::component::runner::ComponentController> | |
| controller) { | |
| // TRACE_DURATION currently requires that the string data does not change | |
| // in the traced scope. Since |package| gets moved in the ComponentV2::Create | |
| // call below, we cannot ensure that |package.resolved_url| does not move or | |
| // change, so we make a copy to pass to TRACE_DURATION. | |
| // TODO(PT-169): Remove this copy when TRACE_DURATION reads string arguments | |
| // eagerly. | |
| const std::string url_copy = start_info.resolved_url(); | |
| TRACE_EVENT1("flutter", "Start", "url", url_copy.c_str()); | |
| // Notes on component termination: Components typically terminate on the | |
| // thread on which they were created. This usually means the thread was | |
| // specifically created to host the component. But we want to ensure that | |
| // access to the active components collection is made on the same thread. So | |
| // we capture the runner in the termination callback. There is no risk of | |
| // there being multiple component runner instances in the process at the same | |
| // time. So it is safe to use the raw pointer. | |
| ComponentV2::TerminationCallback termination_callback = | |
| [component_runner = this](const ComponentV2* component) { | |
| component_runner->task_runner_->PostTask( | |
| [component_runner, component]() { | |
| component_runner->OnComponentV2Terminate(component); | |
| }); | |
| }; | |
| ActiveComponentV2 active_component = ComponentV2::Create( | |
| std::move(termination_callback), std::move(start_info), | |
| context_->svc() /* runner_incoming_services */, std::move(controller)); | |
| auto key = active_component.component.get(); | |
| active_components_v2_[key] = std::move(active_component); | |
| } | |
| void Runner::OnComponentV2Terminate(const ComponentV2* component) { | |
| auto active_component_it = active_components_v2_.find(component); | |
| if (active_component_it == active_components_v2_.end()) { | |
| FML_LOG(INFO) | |
| << "The remote end of the component runner tried to terminate an " | |
| "component that has already been terminated, possibly because we " | |
| "initiated the termination"; | |
| return; | |
| } | |
| ActiveComponentV2& active_component = active_component_it->second; | |
| // Grab the items out of the entry because we will have to rethread the | |
| // destruction. | |
| std::unique_ptr<ComponentV2> component_to_destroy = | |
| std::move(active_component.component); | |
| std::unique_ptr<fml::Thread> component_thread = | |
| std::move(active_component.platform_thread); | |
| // Delete the entry. | |
| active_components_v2_.erase(component); | |
| // Post the task to destroy the component and quit its message loop. | |
| component_thread->GetTaskRunner()->PostTask(fml::MakeCopyable( | |
| [instance = std::move(component_to_destroy), | |
| thread = component_thread.get()]() mutable { instance.reset(); })); | |
| // Terminate and join the thread's message loop. | |
| component_thread->Join(); | |
| } | |
| void Runner::SetupICU() { | |
| // Exposes the TZ data setup for testing. Failing here is not fatal. | |
| Runner::SetupTZDataInternal(); | |
| if (!Runner::SetupICUInternal()) { | |
| FML_LOG(ERROR) << "Could not initialize ICU data."; | |
| } | |
| } | |
| // static | |
| bool Runner::SetupICUInternal() { | |
| return InitializeICU(); | |
| } | |
| // static | |
| bool Runner::SetupTZDataInternal() { | |
| return InitializeTZData(); | |
| } | |
| #if !defined(DART_PRODUCT) | |
| void Runner::SetupTraceObserver() { | |
| fml::AutoResetWaitableEvent latch; | |
| fml::TaskRunner::RunNowOrPostTask(task_runner_, [&]() { | |
| // Running this initialization code on task_runner_ ensures that the call to | |
| // `async_get_default_dispatcher()` will capture the correct dispatcher. | |
| trace_observer_ = std::make_unique<trace::TraceObserver>(); | |
| trace_observer_->Start(async_get_default_dispatcher(), [runner = this]() { | |
| if (!trace_is_category_enabled("dart:profiler")) { | |
| return; | |
| } | |
| if (trace_state() == TRACE_STARTED) { | |
| runner->prolonged_context_ = trace_acquire_prolonged_context(); | |
| Dart_StartProfiling(); | |
| } else if (trace_state() == TRACE_STOPPING) { | |
| auto write_profile_trace_for_components = [](auto& components) { | |
| for (auto& it : components) { | |
| fml::AutoResetWaitableEvent latch; | |
| fml::TaskRunner::RunNowOrPostTask( | |
| it.second.platform_thread->GetTaskRunner(), [&]() { | |
| it.second.component->WriteProfileToTrace(); | |
| latch.Signal(); | |
| }); | |
| latch.Wait(); | |
| } | |
| }; | |
| write_profile_trace_for_components(runner->active_components_v1_); | |
| write_profile_trace_for_components(runner->active_components_v2_); | |
| Dart_StopProfiling(); | |
| trace_release_prolonged_context(runner->prolonged_context_); | |
| } | |
| }); | |
| latch.Signal(); | |
| }); | |
| latch.Wait(); | |
| } | |
| #endif // !defined(DART_PRODUCT) | |
| } // namespace flutter_runner |