mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
The directory may be invalid when running shell_unittests because some tests call SetCacheDirectoryPath and then delete the directory. Later tests that try to use that cache base path will be unable to open the directory.
389 lines
12 KiB
C++
389 lines
12 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/shell/common/persistent_cache.h"
|
|
|
|
#include <future>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
#include "rapidjson/document.h"
|
|
#include "third_party/skia/include/utils/SkBase64.h"
|
|
|
|
#include "flutter/fml/base32.h"
|
|
#include "flutter/fml/file.h"
|
|
#include "flutter/fml/logging.h"
|
|
#include "flutter/fml/make_copyable.h"
|
|
#include "flutter/fml/mapping.h"
|
|
#include "flutter/fml/paths.h"
|
|
#include "flutter/fml/trace_event.h"
|
|
#include "flutter/shell/version/version.h"
|
|
|
|
namespace flutter {
|
|
|
|
std::string PersistentCache::cache_base_path_;
|
|
|
|
std::shared_ptr<AssetManager> PersistentCache::asset_manager_;
|
|
|
|
std::mutex PersistentCache::instance_mutex_;
|
|
std::unique_ptr<PersistentCache> PersistentCache::gPersistentCache;
|
|
|
|
std::string PersistentCache::SkKeyToFilePath(const SkData& data) {
|
|
if (data.data() == nullptr || data.size() == 0) {
|
|
return "";
|
|
}
|
|
|
|
std::string_view view(reinterpret_cast<const char*>(data.data()),
|
|
data.size());
|
|
|
|
auto encode_result = fml::Base32Encode(view);
|
|
|
|
if (!encode_result.first) {
|
|
return "";
|
|
}
|
|
|
|
return encode_result.second;
|
|
}
|
|
|
|
bool PersistentCache::gIsReadOnly = false;
|
|
|
|
std::atomic<bool> PersistentCache::cache_sksl_ = false;
|
|
std::atomic<bool> PersistentCache::strategy_set_ = false;
|
|
|
|
void PersistentCache::SetCacheSkSL(bool value) {
|
|
if (strategy_set_ && value != cache_sksl_) {
|
|
FML_LOG(ERROR) << "Cache SkSL can only be set before the "
|
|
"GrContextOptions::fShaderCacheStrategy is set.";
|
|
return;
|
|
}
|
|
cache_sksl_ = value;
|
|
}
|
|
|
|
PersistentCache* PersistentCache::GetCacheForProcess() {
|
|
std::scoped_lock lock(instance_mutex_);
|
|
if (gPersistentCache == nullptr) {
|
|
gPersistentCache.reset(new PersistentCache(gIsReadOnly));
|
|
}
|
|
return gPersistentCache.get();
|
|
}
|
|
|
|
void PersistentCache::ResetCacheForProcess() {
|
|
std::scoped_lock lock(instance_mutex_);
|
|
gPersistentCache.reset(new PersistentCache(gIsReadOnly));
|
|
strategy_set_ = false;
|
|
}
|
|
|
|
void PersistentCache::SetCacheDirectoryPath(std::string path) {
|
|
cache_base_path_ = path;
|
|
}
|
|
|
|
bool PersistentCache::Purge() {
|
|
// Make sure that this is called after the worker task runner setup so all the
|
|
// file system modifications would happen on that single thread to avoid
|
|
// racing.
|
|
FML_CHECK(GetWorkerTaskRunner());
|
|
|
|
std::promise<bool> removed;
|
|
GetWorkerTaskRunner()->PostTask(
|
|
[&removed, cache_directory = cache_directory_]() {
|
|
if (cache_directory->is_valid()) {
|
|
FML_LOG(INFO) << "Purge persistent cache.";
|
|
removed.set_value(RemoveFilesInDirectory(*cache_directory));
|
|
} else {
|
|
removed.set_value(false);
|
|
}
|
|
});
|
|
return removed.get_future().get();
|
|
}
|
|
|
|
namespace {
|
|
|
|
constexpr char kEngineComponent[] = "flutter_engine";
|
|
|
|
static void FreeOldCacheDirectory(const fml::UniqueFD& cache_base_dir) {
|
|
fml::UniqueFD engine_dir =
|
|
fml::OpenDirectoryReadOnly(cache_base_dir, kEngineComponent);
|
|
if (!engine_dir.is_valid()) {
|
|
return;
|
|
}
|
|
fml::VisitFiles(engine_dir, [](const fml::UniqueFD& directory,
|
|
const std::string& filename) {
|
|
if (filename != GetFlutterEngineVersion()) {
|
|
auto dir = fml::OpenDirectory(directory, filename.c_str(), false,
|
|
fml::FilePermission::kReadWrite);
|
|
if (dir.is_valid()) {
|
|
fml::RemoveDirectoryRecursively(directory, filename.c_str());
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
static std::shared_ptr<fml::UniqueFD> MakeCacheDirectory(
|
|
const std::string& global_cache_base_path,
|
|
bool read_only,
|
|
bool cache_sksl) {
|
|
fml::UniqueFD cache_base_dir;
|
|
if (global_cache_base_path.length()) {
|
|
cache_base_dir = fml::OpenDirectory(global_cache_base_path.c_str(), false,
|
|
fml::FilePermission::kRead);
|
|
} else {
|
|
cache_base_dir = fml::paths::GetCachesDirectory();
|
|
}
|
|
|
|
if (cache_base_dir.is_valid()) {
|
|
FreeOldCacheDirectory(cache_base_dir);
|
|
std::vector<std::string> components = {
|
|
kEngineComponent, GetFlutterEngineVersion(), "skia", GetSkiaVersion()};
|
|
if (cache_sksl) {
|
|
components.push_back(PersistentCache::kSkSLSubdirName);
|
|
}
|
|
return std::make_shared<fml::UniqueFD>(
|
|
CreateDirectory(cache_base_dir, components,
|
|
read_only ? fml::FilePermission::kRead
|
|
: fml::FilePermission::kReadWrite));
|
|
} else {
|
|
return std::make_shared<fml::UniqueFD>();
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
sk_sp<SkData> ParseBase32(const std::string& input) {
|
|
std::pair<bool, std::string> decode_result = fml::Base32Decode(input);
|
|
if (!decode_result.first) {
|
|
FML_LOG(ERROR) << "Base32 can't decode: " << input;
|
|
return nullptr;
|
|
}
|
|
const std::string& data_string = decode_result.second;
|
|
return SkData::MakeWithCopy(data_string.data(), data_string.length());
|
|
}
|
|
|
|
sk_sp<SkData> ParseBase64(const std::string& input) {
|
|
SkBase64 decoder;
|
|
auto error = decoder.decode(input.c_str(), input.length());
|
|
if (error != SkBase64::Error::kNoError) {
|
|
FML_LOG(ERROR) << "Base64 decode error: " << error;
|
|
FML_LOG(ERROR) << "Base64 can't decode: " << input;
|
|
return nullptr;
|
|
}
|
|
return SkData::MakeWithCopy(decoder.getData(), decoder.getDataSize());
|
|
}
|
|
|
|
std::vector<PersistentCache::SkSLCache> PersistentCache::LoadSkSLs() {
|
|
TRACE_EVENT0("flutter", "PersistentCache::LoadSkSLs");
|
|
std::vector<PersistentCache::SkSLCache> result;
|
|
fml::FileVisitor visitor = [&result](const fml::UniqueFD& directory,
|
|
const std::string& filename) {
|
|
sk_sp<SkData> key = ParseBase32(filename);
|
|
sk_sp<SkData> data = LoadFile(directory, filename);
|
|
if (key != nullptr && data != nullptr) {
|
|
result.push_back({key, data});
|
|
} else {
|
|
FML_LOG(ERROR) << "Failed to load: " << filename;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
// Only visit sksl_cache_directory_ if this persistent cache is valid.
|
|
// However, we'd like to continue visit the asset dir even if this persistent
|
|
// cache is invalid.
|
|
if (IsValid()) {
|
|
// In case `rewinddir` doesn't work reliably, load SkSLs from a freshly
|
|
// opened directory (https://github.com/flutter/flutter/issues/65258).
|
|
fml::UniqueFD fresh_dir =
|
|
fml::OpenDirectoryReadOnly(*cache_directory_, kSkSLSubdirName);
|
|
if (fresh_dir.is_valid()) {
|
|
fml::VisitFiles(fresh_dir, visitor);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<fml::Mapping> mapping = nullptr;
|
|
if (asset_manager_ != nullptr) {
|
|
mapping = asset_manager_->GetAsMapping(kAssetFileName);
|
|
}
|
|
if (mapping == nullptr) {
|
|
FML_LOG(INFO) << "No sksl asset found.";
|
|
} else {
|
|
FML_LOG(INFO) << "Found sksl asset. Loading SkSLs from it...";
|
|
rapidjson::Document json_doc;
|
|
rapidjson::ParseResult parse_result =
|
|
json_doc.Parse(reinterpret_cast<const char*>(mapping->GetMapping()),
|
|
mapping->GetSize());
|
|
if (parse_result != rapidjson::ParseErrorCode::kParseErrorNone) {
|
|
FML_LOG(ERROR) << "Failed to parse json file: " << kAssetFileName;
|
|
} else {
|
|
for (auto& item : json_doc["data"].GetObject()) {
|
|
sk_sp<SkData> key = ParseBase32(item.name.GetString());
|
|
sk_sp<SkData> sksl = ParseBase64(item.value.GetString());
|
|
if (key != nullptr && sksl != nullptr) {
|
|
result.push_back({key, sksl});
|
|
} else {
|
|
FML_LOG(ERROR) << "Failed to load: " << item.name.GetString();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
PersistentCache::PersistentCache(bool read_only)
|
|
: is_read_only_(read_only),
|
|
cache_directory_(MakeCacheDirectory(cache_base_path_, read_only, false)),
|
|
sksl_cache_directory_(
|
|
MakeCacheDirectory(cache_base_path_, read_only, true)) {
|
|
if (!IsValid()) {
|
|
FML_LOG(WARNING) << "Could not acquire the persistent cache directory. "
|
|
"Caching of GPU resources on disk is disabled.";
|
|
}
|
|
}
|
|
|
|
PersistentCache::~PersistentCache() = default;
|
|
|
|
bool PersistentCache::IsValid() const {
|
|
return cache_directory_ && cache_directory_->is_valid();
|
|
}
|
|
|
|
sk_sp<SkData> PersistentCache::LoadFile(const fml::UniqueFD& dir,
|
|
const std::string& file_name) {
|
|
auto file = fml::OpenFileReadOnly(dir, file_name.c_str());
|
|
if (!file.is_valid()) {
|
|
return nullptr;
|
|
}
|
|
auto mapping = std::make_unique<fml::FileMapping>(file);
|
|
if (mapping->GetSize() == 0) {
|
|
return nullptr;
|
|
}
|
|
return SkData::MakeWithCopy(mapping->GetMapping(), mapping->GetSize());
|
|
}
|
|
|
|
// |GrContextOptions::PersistentCache|
|
|
sk_sp<SkData> PersistentCache::load(const SkData& key) {
|
|
TRACE_EVENT0("flutter", "PersistentCacheLoad");
|
|
if (!IsValid()) {
|
|
return nullptr;
|
|
}
|
|
auto file_name = SkKeyToFilePath(key);
|
|
if (file_name.size() == 0) {
|
|
return nullptr;
|
|
}
|
|
auto result = PersistentCache::LoadFile(*cache_directory_, file_name);
|
|
if (result != nullptr) {
|
|
TRACE_EVENT0("flutter", "PersistentCacheLoadHit");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void PersistentCacheStore(fml::RefPtr<fml::TaskRunner> worker,
|
|
std::shared_ptr<fml::UniqueFD> cache_directory,
|
|
std::string key,
|
|
std::unique_ptr<fml::Mapping> value) {
|
|
auto task =
|
|
fml::MakeCopyable([cache_directory, //
|
|
file_name = std::move(key), //
|
|
mapping = std::move(value) //
|
|
]() mutable {
|
|
TRACE_EVENT0("flutter", "PersistentCacheStore");
|
|
if (!fml::WriteAtomically(*cache_directory, //
|
|
file_name.c_str(), //
|
|
*mapping) //
|
|
) {
|
|
FML_DLOG(WARNING)
|
|
<< "Could not write cache contents to persistent store.";
|
|
}
|
|
});
|
|
|
|
if (!worker) {
|
|
FML_LOG(WARNING)
|
|
<< "The persistent cache has no available workers. Performing the task "
|
|
"on the current thread. This slow operation is going to occur on a "
|
|
"frame workload.";
|
|
task();
|
|
} else {
|
|
worker->PostTask(std::move(task));
|
|
}
|
|
}
|
|
|
|
// |GrContextOptions::PersistentCache|
|
|
void PersistentCache::store(const SkData& key, const SkData& data) {
|
|
stored_new_shaders_ = true;
|
|
|
|
if (is_read_only_) {
|
|
return;
|
|
}
|
|
|
|
if (!IsValid()) {
|
|
return;
|
|
}
|
|
|
|
auto file_name = SkKeyToFilePath(key);
|
|
|
|
if (file_name.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
auto mapping = std::make_unique<fml::DataMapping>(
|
|
std::vector<uint8_t>{data.bytes(), data.bytes() + data.size()});
|
|
|
|
if (mapping == nullptr || mapping->GetSize() == 0) {
|
|
return;
|
|
}
|
|
|
|
PersistentCacheStore(GetWorkerTaskRunner(),
|
|
cache_sksl_ ? sksl_cache_directory_ : cache_directory_,
|
|
std::move(file_name), std::move(mapping));
|
|
}
|
|
|
|
void PersistentCache::DumpSkp(const SkData& data) {
|
|
if (is_read_only_ || !IsValid()) {
|
|
FML_LOG(ERROR) << "Could not dump SKP from read-only or invalid persistent "
|
|
"cache.";
|
|
return;
|
|
}
|
|
|
|
std::stringstream name_stream;
|
|
auto ticks = fml::TimePoint::Now().ToEpochDelta().ToNanoseconds();
|
|
name_stream << "shader_dump_" << std::to_string(ticks) << ".skp";
|
|
std::string file_name = name_stream.str();
|
|
FML_LOG(INFO) << "Dumping " << file_name;
|
|
auto mapping = std::make_unique<fml::DataMapping>(
|
|
std::vector<uint8_t>{data.bytes(), data.bytes() + data.size()});
|
|
PersistentCacheStore(GetWorkerTaskRunner(), cache_directory_,
|
|
std::move(file_name), std::move(mapping));
|
|
}
|
|
|
|
void PersistentCache::AddWorkerTaskRunner(
|
|
fml::RefPtr<fml::TaskRunner> task_runner) {
|
|
std::scoped_lock lock(worker_task_runners_mutex_);
|
|
worker_task_runners_.insert(task_runner);
|
|
}
|
|
|
|
void PersistentCache::RemoveWorkerTaskRunner(
|
|
fml::RefPtr<fml::TaskRunner> task_runner) {
|
|
std::scoped_lock lock(worker_task_runners_mutex_);
|
|
auto found = worker_task_runners_.find(task_runner);
|
|
if (found != worker_task_runners_.end()) {
|
|
worker_task_runners_.erase(found);
|
|
}
|
|
}
|
|
|
|
fml::RefPtr<fml::TaskRunner> PersistentCache::GetWorkerTaskRunner() const {
|
|
fml::RefPtr<fml::TaskRunner> worker;
|
|
|
|
std::scoped_lock lock(worker_task_runners_mutex_);
|
|
if (!worker_task_runners_.empty()) {
|
|
worker = *worker_task_runners_.begin();
|
|
}
|
|
|
|
return worker;
|
|
}
|
|
|
|
void PersistentCache::SetAssetManager(std::shared_ptr<AssetManager> value) {
|
|
TRACE_EVENT_INSTANT0("flutter", "PersistentCache::SetAssetManager");
|
|
asset_manager_ = value;
|
|
}
|
|
|
|
} // namespace flutter
|