| // 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 <fuchsia/mem/cpp/fidl.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/trace-engine/instrumentation.h> |
| #include <zircon/status.h> |
| #include <zircon/types.h> |
| |
| #include <fcntl.h> |
| #include <stdint.h> |
| #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/sys/cpp/component_context.h" |
| #include "runtime/dart/utils/files.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( |
| 0, icu_data.vmo, 0, static_cast<size_t>(data_size), ZX_VM_PERM_READ, |
| &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(async::Loop* loop) |
| : loop_(loop), context_(sys::ComponentContext::Create()) { |
| #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>()); |
| |
| SetupTraceObserver(); |
| #endif // !defined(DART_PRODUCT) |
| |
| SkGraphics::Init(); |
| |
| SetupICU(); |
| |
| SetProcessName(); |
| |
| SetThreadName("io.flutter.runner.main"); |
| |
| context_->outgoing()->AddPublicService<fuchsia::sys::Runner>( |
| std::bind(&Runner::RegisterApplication, 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>(); |
| |
| #if !defined(DART_PRODUCT) |
| trace_observer_->Stop(); |
| #endif // !defined(DART_PRODUCT) |
| } |
| |
| void Runner::RegisterApplication( |
| fidl::InterfaceRequest<fuchsia::sys::Runner> request) { |
| active_applications_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 Application::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 application termination: Application typically terminate on the |
| // thread on which they were created. This usually means the thread was |
| // specifically created to host the application. But we want to ensure that |
| // access to the active applications collection is made on the same thread. So |
| // we capture the runner in the termination callback. There is no risk of |
| // there being multiple application runner instance in the process at the same |
| // time. So it is safe to use the raw pointer. |
| Application::TerminationCallback termination_callback = |
| [task_runner = loop_->dispatcher(), // |
| application_runner = this // |
| ](const Application* application) { |
| async::PostTask(task_runner, [application_runner, application]() { |
| application_runner->OnApplicationTerminate(application); |
| }); |
| }; |
| |
| auto active_application = Application::Create( |
| std::move(termination_callback), // termination callback |
| std::move(package), // application package |
| std::move(startup_info), // startup info |
| context_->svc(), // runner incoming services |
| std::move(controller) // controller request |
| ); |
| |
| auto key = active_application.application.get(); |
| active_applications_[key] = std::move(active_application); |
| } |
| |
| void Runner::OnApplicationTerminate(const Application* application) { |
| auto app = active_applications_.find(application); |
| if (app == active_applications_.end()) { |
| FML_LOG(INFO) |
| << "The remote end of the application runner tried to terminate an " |
| "application that has already been terminated, possibly because we " |
| "initiated the termination"; |
| return; |
| } |
| auto& active_application = app->second; |
| |
| // Grab the items out of the entry because we will have to rethread the |
| // destruction. |
| auto application_to_destroy = std::move(active_application.application); |
| auto application_thread = std::move(active_application.thread); |
| |
| // Delegate the entry. |
| active_applications_.erase(application); |
| |
| // Post the task to destroy the application and quit its message loop. |
| async::PostTask( |
| application_thread->dispatcher(), |
| fml::MakeCopyable([instance = std::move(application_to_destroy), |
| thread = application_thread.get()]() mutable { |
| instance.reset(); |
| thread->Quit(); |
| })); |
| |
| // This works because just posted the quit task on the hosted thread. |
| application_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() { |
| trace_observer_ = std::make_unique<trace::TraceObserver>(); |
| trace_observer_->Start(loop_->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) { |
| for (auto& it : runner->active_applications_) { |
| fml::AutoResetWaitableEvent latch; |
| async::PostTask(it.second.thread->dispatcher(), [&]() { |
| it.second.application->WriteProfileToTrace(); |
| latch.Signal(); |
| }); |
| latch.Wait(); |
| } |
| Dart_StopProfiling(); |
| trace_release_prolonged_context(runner->prolonged_context_); |
| } |
| }); |
| } |
| #endif // !defined(DART_PRODUCT) |
| |
| } // namespace flutter_runner |