flutter_flutter/engine/src/flutter/runtime/isolate_configuration.cc
Dan Field 874ceb4863 Avoid reloading the kernel snapshot when spawning an isolate in the same group (flutter/engine#48478)
Found by @mraleph while looking at flutter_tester related things with me.

I took some suggestions from him and added a test. This should eventually help speed up running large multi-file test suites.

The test is a little bit wonky because we don't have great inspection points (e.g. something to mock or examine from a test). It should break if we change the way we load kernel assets in the test harness, and is verifying that we only ask for the test asset once where we used to ask for it twice.
2023-12-01 00:32:31 +00:00

301 lines
9.7 KiB
C++

// 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 "flutter/runtime/isolate_configuration.h"
#include "flutter/fml/make_copyable.h"
#include "flutter/runtime/dart_vm.h"
namespace flutter {
IsolateConfiguration::IsolateConfiguration() = default;
IsolateConfiguration::~IsolateConfiguration() = default;
bool IsolateConfiguration::PrepareIsolate(DartIsolate& isolate) {
if (isolate.GetPhase() != DartIsolate::Phase::LibrariesSetup) {
FML_DLOG(ERROR)
<< "Isolate was in incorrect phase to be prepared for running.";
return false;
}
return DoPrepareIsolate(isolate);
}
class AppSnapshotIsolateConfiguration final : public IsolateConfiguration {
public:
AppSnapshotIsolateConfiguration() = default;
// |IsolateConfiguration|
bool DoPrepareIsolate(DartIsolate& isolate) override {
return isolate.PrepareForRunningFromPrecompiledCode();
}
// |IsolateConfiguration|
bool IsNullSafetyEnabled(const DartSnapshot& snapshot) override {
return snapshot.IsNullSafetyEnabled(nullptr);
}
private:
FML_DISALLOW_COPY_AND_ASSIGN(AppSnapshotIsolateConfiguration);
};
class KernelIsolateConfiguration : public IsolateConfiguration {
public:
// The kernel mapping may be nullptr if reusing the group's loaded kernel.
explicit KernelIsolateConfiguration(
std::unique_ptr<const fml::Mapping> kernel)
: kernel_(std::move(kernel)) {}
// |IsolateConfiguration|
bool DoPrepareIsolate(DartIsolate& isolate) override {
if (DartVM::IsRunningPrecompiledCode()) {
return false;
}
return isolate.PrepareForRunningFromKernel(std::move(kernel_),
/*child_isolate=*/false,
/*last_piece=*/true);
}
// |IsolateConfiguration|
bool IsNullSafetyEnabled(const DartSnapshot& snapshot) override {
return snapshot.IsNullSafetyEnabled(kernel_.get());
}
private:
std::unique_ptr<const fml::Mapping> kernel_;
FML_DISALLOW_COPY_AND_ASSIGN(KernelIsolateConfiguration);
};
class KernelListIsolateConfiguration final : public IsolateConfiguration {
public:
explicit KernelListIsolateConfiguration(
std::vector<std::future<std::unique_ptr<const fml::Mapping>>>
kernel_pieces)
: kernel_piece_futures_(std::move(kernel_pieces)) {
if (kernel_piece_futures_.empty()) {
FML_LOG(ERROR) << "Attempted to create kernel list configuration without "
"any kernel blobs.";
}
}
// |IsolateConfiguration|
bool DoPrepareIsolate(DartIsolate& isolate) override {
if (DartVM::IsRunningPrecompiledCode()) {
return false;
}
ResolveKernelPiecesIfNecessary();
if (resolved_kernel_pieces_.empty()) {
FML_DLOG(ERROR) << "No kernel pieces provided to prepare this isolate.";
return false;
}
for (size_t i = 0; i < resolved_kernel_pieces_.size(); i++) {
if (!resolved_kernel_pieces_[i]) {
FML_DLOG(ERROR) << "This kernel list isolate configuration was already "
"used to prepare an isolate.";
return false;
}
const bool last_piece = i + 1 == resolved_kernel_pieces_.size();
if (!isolate.PrepareForRunningFromKernel(
std::move(resolved_kernel_pieces_[i]), /*child_isolate=*/false,
last_piece)) {
return false;
}
}
return true;
}
// |IsolateConfiguration|
bool IsNullSafetyEnabled(const DartSnapshot& snapshot) override {
ResolveKernelPiecesIfNecessary();
const auto kernel = resolved_kernel_pieces_.empty()
? nullptr
: resolved_kernel_pieces_.front().get();
return snapshot.IsNullSafetyEnabled(kernel);
}
// This must be call as late as possible before accessing any of the kernel
// pieces. This will delay blocking on the futures for as long as possible. So
// far, only Fuchsia depends on this optimization and only on the non-AOT
// configs.
void ResolveKernelPiecesIfNecessary() {
if (resolved_kernel_pieces_.size() == kernel_piece_futures_.size()) {
return;
}
resolved_kernel_pieces_.clear();
for (auto& piece : kernel_piece_futures_) {
// The get() call will xfer the unique pointer out and leave an empty
// future in the original vector.
resolved_kernel_pieces_.emplace_back(piece.get());
}
}
private:
std::vector<std::future<std::unique_ptr<const fml::Mapping>>>
kernel_piece_futures_;
std::vector<std::unique_ptr<const fml::Mapping>> resolved_kernel_pieces_;
FML_DISALLOW_COPY_AND_ASSIGN(KernelListIsolateConfiguration);
};
static std::vector<std::string> ParseKernelListPaths(
std::unique_ptr<fml::Mapping> kernel_list) {
FML_DCHECK(kernel_list);
std::vector<std::string> kernel_pieces_paths;
const char* kernel_list_str =
reinterpret_cast<const char*>(kernel_list->GetMapping());
size_t kernel_list_size = kernel_list->GetSize();
size_t piece_path_start = 0;
while (piece_path_start < kernel_list_size) {
size_t piece_path_end = piece_path_start;
while ((piece_path_end < kernel_list_size) &&
(kernel_list_str[piece_path_end] != '\n')) {
piece_path_end++;
}
std::string piece_path(&kernel_list_str[piece_path_start],
piece_path_end - piece_path_start);
kernel_pieces_paths.emplace_back(std::move(piece_path));
piece_path_start = piece_path_end + 1;
}
return kernel_pieces_paths;
}
static std::vector<std::future<std::unique_ptr<const fml::Mapping>>>
PrepareKernelMappings(const std::vector<std::string>& kernel_pieces_paths,
const std::shared_ptr<AssetManager>& asset_manager,
const fml::RefPtr<fml::TaskRunner>& io_worker) {
FML_DCHECK(asset_manager);
std::vector<std::future<std::unique_ptr<const fml::Mapping>>> fetch_futures;
for (const auto& kernel_pieces_path : kernel_pieces_paths) {
std::promise<std::unique_ptr<const fml::Mapping>> fetch_promise;
fetch_futures.push_back(fetch_promise.get_future());
auto fetch_task =
fml::MakeCopyable([asset_manager, kernel_pieces_path,
fetch_promise = std::move(fetch_promise)]() mutable {
fetch_promise.set_value(
asset_manager->GetAsMapping(kernel_pieces_path));
});
// Fulfill the promise on the worker if one is available or the current
// thread if one is not.
if (io_worker) {
io_worker->PostTask(fetch_task);
} else {
fetch_task();
}
}
return fetch_futures;
}
std::unique_ptr<IsolateConfiguration> IsolateConfiguration::InferFromSettings(
const Settings& settings,
const std::shared_ptr<AssetManager>& asset_manager,
const fml::RefPtr<fml::TaskRunner>& io_worker,
IsolateLaunchType launch_type) {
// Running in AOT mode.
if (DartVM::IsRunningPrecompiledCode()) {
return CreateForAppSnapshot();
}
if (launch_type == IsolateLaunchType::kExistingGroup) {
return CreateForKernel(nullptr);
}
if (settings.application_kernels) {
return CreateForKernelList(settings.application_kernels());
}
if (settings.application_kernel_asset.empty() &&
settings.application_kernel_list_asset.empty()) {
FML_DLOG(ERROR) << "application_kernel_asset or "
"application_kernel_list_asset must be set";
return nullptr;
}
if (!asset_manager) {
FML_DLOG(ERROR) << "No asset manager specified when attempting to create "
"isolate configuration.";
return nullptr;
}
// Running from kernel snapshot. Requires asset manager.
{
std::unique_ptr<fml::Mapping> kernel =
asset_manager->GetAsMapping(settings.application_kernel_asset);
if (kernel) {
return CreateForKernel(std::move(kernel));
}
}
// Running from kernel divided into several pieces (for sharing). Requires
// asset manager and io worker.
if (!io_worker) {
FML_DLOG(ERROR) << "No IO worker specified to load kernel pieces.";
return nullptr;
}
{
std::unique_ptr<fml::Mapping> kernel_list =
asset_manager->GetAsMapping(settings.application_kernel_list_asset);
if (!kernel_list) {
FML_LOG(ERROR) << "Failed to load: "
<< settings.application_kernel_list_asset;
return nullptr;
}
auto kernel_pieces_paths = ParseKernelListPaths(std::move(kernel_list));
auto kernel_mappings =
PrepareKernelMappings(kernel_pieces_paths, asset_manager, io_worker);
return CreateForKernelList(std::move(kernel_mappings));
}
return nullptr;
}
std::unique_ptr<IsolateConfiguration>
IsolateConfiguration::CreateForAppSnapshot() {
return std::make_unique<AppSnapshotIsolateConfiguration>();
}
std::unique_ptr<IsolateConfiguration> IsolateConfiguration::CreateForKernel(
std::unique_ptr<const fml::Mapping> kernel) {
return std::make_unique<KernelIsolateConfiguration>(std::move(kernel));
}
std::unique_ptr<IsolateConfiguration> IsolateConfiguration::CreateForKernelList(
std::vector<std::unique_ptr<const fml::Mapping>> kernel_pieces) {
std::vector<std::future<std::unique_ptr<const fml::Mapping>>> pieces;
for (auto& piece : kernel_pieces) {
if (!piece) {
FML_DLOG(ERROR) << "Invalid kernel piece.";
continue;
}
std::promise<std::unique_ptr<const fml::Mapping>> promise;
pieces.push_back(promise.get_future());
promise.set_value(std::move(piece));
}
return CreateForKernelList(std::move(pieces));
}
std::unique_ptr<IsolateConfiguration> IsolateConfiguration::CreateForKernelList(
std::vector<std::future<std::unique_ptr<const fml::Mapping>>>
kernel_pieces) {
return std::make_unique<KernelListIsolateConfiguration>(
std::move(kernel_pieces));
}
} // namespace flutter