1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "flutter/shell/common/persistent_cache.h"
6
7#include <future>
8#include <memory>
9#include <string>
10#include <string_view>
11
12#include "rapidjson/document.h"
13#include "third_party/skia/include/utils/SkBase64.h"
14
15#include "flutter/fml/base32.h"
16#include "flutter/fml/file.h"
17#include "flutter/fml/logging.h"
18#include "flutter/fml/make_copyable.h"
19#include "flutter/fml/mapping.h"
20#include "flutter/fml/paths.h"
21#include "flutter/fml/trace_event.h"
22#include "flutter/shell/version/version.h"
23
24namespace flutter {
25
26std::string PersistentCache::cache_base_path_;
27
28std::shared_ptr<AssetManager> PersistentCache::asset_manager_;
29
30std::mutex PersistentCache::instance_mutex_;
31std::unique_ptr<PersistentCache> PersistentCache::gPersistentCache;
32
33std::string PersistentCache::SkKeyToFilePath(const SkData& data) {
34 if (data.data() == nullptr || data.size() == 0) {
35 return "";
36 }
37
38 std::string_view view(reinterpret_cast<const char*>(data.data()),
39 data.size());
40
41 auto encode_result = fml::Base32Encode(view);
42
43 if (!encode_result.first) {
44 return "";
45 }
46
47 return encode_result.second;
48}
49
50bool PersistentCache::gIsReadOnly = false;
51
52std::atomic<bool> PersistentCache::cache_sksl_ = false;
53std::atomic<bool> PersistentCache::strategy_set_ = false;
54
55void PersistentCache::SetCacheSkSL(bool value) {
56 if (strategy_set_ && value != cache_sksl_) {
57 FML_LOG(ERROR) << "Cache SkSL can only be set before the "
58 "GrContextOptions::fShaderCacheStrategy is set.";
59 return;
60 }
61 cache_sksl_ = value;
62}
63
64PersistentCache* PersistentCache::GetCacheForProcess() {
65 std::scoped_lock lock(instance_mutex_);
66 if (gPersistentCache == nullptr) {
67 gPersistentCache.reset(new PersistentCache(gIsReadOnly));
68 }
69 return gPersistentCache.get();
70}
71
72void PersistentCache::ResetCacheForProcess() {
73 std::scoped_lock lock(instance_mutex_);
74 gPersistentCache.reset(new PersistentCache(gIsReadOnly));
75 strategy_set_ = false;
76}
77
78void PersistentCache::SetCacheDirectoryPath(std::string path) {
79 cache_base_path_ = path;
80}
81
82bool PersistentCache::Purge() {
83 // Make sure that this is called after the worker task runner setup so all the
84 // file system modifications would happen on that single thread to avoid
85 // racing.
86 FML_CHECK(GetWorkerTaskRunner());
87
88 std::promise<bool> removed;
89 GetWorkerTaskRunner()->PostTask(
90 [&removed, cache_directory = cache_directory_]() {
91 if (cache_directory->is_valid()) {
92 FML_LOG(INFO) << "Purge persistent cache.";
93 removed.set_value(RemoveFilesInDirectory(*cache_directory));
94 } else {
95 removed.set_value(false);
96 }
97 });
98 return removed.get_future().get();
99}
100
101namespace {
102
103constexpr char kEngineComponent[] = "flutter_engine";
104
105static void FreeOldCacheDirectory(const fml::UniqueFD& cache_base_dir) {
106 fml::UniqueFD engine_dir =
107 fml::OpenDirectoryReadOnly(cache_base_dir, kEngineComponent);
108 if (!engine_dir.is_valid()) {
109 return;
110 }
111 fml::VisitFiles(engine_dir, [](const fml::UniqueFD& directory,
112 const std::string& filename) {
113 if (filename != GetFlutterEngineVersion()) {
114 auto dir = fml::OpenDirectory(directory, filename.c_str(), false,
115 fml::FilePermission::kReadWrite);
116 if (dir.is_valid()) {
117 fml::RemoveDirectoryRecursively(directory, filename.c_str());
118 }
119 }
120 return true;
121 });
122}
123
124static std::shared_ptr<fml::UniqueFD> MakeCacheDirectory(
125 const std::string& global_cache_base_path,
126 bool read_only,
127 bool cache_sksl) {
128 fml::UniqueFD cache_base_dir;
129 if (global_cache_base_path.length()) {
130 cache_base_dir = fml::OpenDirectory(global_cache_base_path.c_str(), false,
131 fml::FilePermission::kRead);
132 } else {
133 cache_base_dir = fml::paths::GetCachesDirectory();
134 }
135
136 if (cache_base_dir.is_valid()) {
137 FreeOldCacheDirectory(cache_base_dir);
138 std::vector<std::string> components = {
139 kEngineComponent, GetFlutterEngineVersion(), "skia", GetSkiaVersion()};
140 if (cache_sksl) {
141 components.push_back(PersistentCache::kSkSLSubdirName);
142 }
143 return std::make_shared<fml::UniqueFD>(
144 CreateDirectory(cache_base_dir, components,
145 read_only ? fml::FilePermission::kRead
146 : fml::FilePermission::kReadWrite));
147 } else {
148 return std::make_shared<fml::UniqueFD>();
149 }
150}
151} // namespace
152
153sk_sp<SkData> ParseBase32(const std::string& input) {
154 std::pair<bool, std::string> decode_result = fml::Base32Decode(input);
155 if (!decode_result.first) {
156 FML_LOG(ERROR) << "Base32 can't decode: " << input;
157 return nullptr;
158 }
159 const std::string& data_string = decode_result.second;
160 return SkData::MakeWithCopy(data_string.data(), data_string.length());
161}
162
163sk_sp<SkData> ParseBase64(const std::string& input) {
164 SkBase64 decoder;
165 auto error = decoder.decode(input.c_str(), input.length());
166 if (error != SkBase64::Error::kNoError) {
167 FML_LOG(ERROR) << "Base64 decode error: " << error;
168 FML_LOG(ERROR) << "Base64 can't decode: " << input;
169 return nullptr;
170 }
171 return SkData::MakeWithCopy(decoder.getData(), decoder.getDataSize());
172}
173
174std::vector<PersistentCache::SkSLCache> PersistentCache::LoadSkSLs() {
175 TRACE_EVENT0("flutter", "PersistentCache::LoadSkSLs");
176 std::vector<PersistentCache::SkSLCache> result;
177 fml::FileVisitor visitor = [&result](const fml::UniqueFD& directory,
178 const std::string& filename) {
179 sk_sp<SkData> key = ParseBase32(filename);
180 sk_sp<SkData> data = LoadFile(directory, filename);
181 if (key != nullptr && data != nullptr) {
182 result.push_back({key, data});
183 } else {
184 FML_LOG(ERROR) << "Failed to load: " << filename;
185 }
186 return true;
187 };
188
189 // Only visit sksl_cache_directory_ if this persistent cache is valid.
190 // However, we'd like to continue visit the asset dir even if this persistent
191 // cache is invalid.
192 if (IsValid()) {
193 fml::VisitFiles(*sksl_cache_directory_, visitor);
194 }
195
196 std::unique_ptr<fml::Mapping> mapping = nullptr;
197 if (asset_manager_ != nullptr) {
198 mapping = asset_manager_->GetAsMapping(kAssetFileName);
199 }
200 if (mapping == nullptr) {
201 FML_LOG(INFO) << "No sksl asset found.";
202 } else {
203 FML_LOG(INFO) << "Found sksl asset. Loading SkSLs from it...";
204 rapidjson::Document json_doc;
205 rapidjson::ParseResult parse_result =
206 json_doc.Parse(reinterpret_cast<const char*>(mapping->GetMapping()),
207 mapping->GetSize());
208 if (parse_result != rapidjson::ParseErrorCode::kParseErrorNone) {
209 FML_LOG(ERROR) << "Failed to parse json file: " << kAssetFileName;
210 } else {
211 for (auto& item : json_doc["data"].GetObject()) {
212 sk_sp<SkData> key = ParseBase32(item.name.GetString());
213 sk_sp<SkData> sksl = ParseBase64(item.value.GetString());
214 if (key != nullptr && sksl != nullptr) {
215 result.push_back({key, sksl});
216 } else {
217 FML_LOG(ERROR) << "Failed to load: " << item.name.GetString();
218 }
219 }
220 }
221 }
222
223 return result;
224}
225
226PersistentCache::PersistentCache(bool read_only)
227 : is_read_only_(read_only),
228 cache_directory_(MakeCacheDirectory(cache_base_path_, read_only, false)),
229 sksl_cache_directory_(
230 MakeCacheDirectory(cache_base_path_, read_only, true)) {
231 if (!IsValid()) {
232 FML_LOG(WARNING) << "Could not acquire the persistent cache directory. "
233 "Caching of GPU resources on disk is disabled.";
234 }
235}
236
237PersistentCache::~PersistentCache() = default;
238
239bool PersistentCache::IsValid() const {
240 return cache_directory_ && cache_directory_->is_valid();
241}
242
243sk_sp<SkData> PersistentCache::LoadFile(const fml::UniqueFD& dir,
244 const std::string& file_name) {
245 auto file = fml::OpenFileReadOnly(dir, file_name.c_str());
246 if (!file.is_valid()) {
247 return nullptr;
248 }
249 auto mapping = std::make_unique<fml::FileMapping>(file);
250 if (mapping->GetSize() == 0) {
251 return nullptr;
252 }
253 return SkData::MakeWithCopy(mapping->GetMapping(), mapping->GetSize());
254}
255
256// |GrContextOptions::PersistentCache|
257sk_sp<SkData> PersistentCache::load(const SkData& key) {
258 TRACE_EVENT0("flutter", "PersistentCacheLoad");
259 if (!IsValid()) {
260 return nullptr;
261 }
262 auto file_name = SkKeyToFilePath(key);
263 if (file_name.size() == 0) {
264 return nullptr;
265 }
266 auto result = PersistentCache::LoadFile(*cache_directory_, file_name);
267 if (result != nullptr) {
268 TRACE_EVENT0("flutter", "PersistentCacheLoadHit");
269 }
270 return result;
271}
272
273static void PersistentCacheStore(fml::RefPtr<fml::TaskRunner> worker,
274 std::shared_ptr<fml::UniqueFD> cache_directory,
275 std::string key,
276 std::unique_ptr<fml::Mapping> value) {
277 auto task =
278 fml::MakeCopyable([cache_directory, //
279 file_name = std::move(key), //
280 mapping = std::move(value) //
281 ]() mutable {
282 TRACE_EVENT0("flutter", "PersistentCacheStore");
283 if (!fml::WriteAtomically(*cache_directory, //
284 file_name.c_str(), //
285 *mapping) //
286 ) {
287 FML_DLOG(WARNING)
288 << "Could not write cache contents to persistent store.";
289 }
290 });
291
292 if (!worker) {
293 FML_LOG(WARNING)
294 << "The persistent cache has no available workers. Performing the task "
295 "on the current thread. This slow operation is going to occur on a "
296 "frame workload.";
297 task();
298 } else {
299 worker->PostTask(std::move(task));
300 }
301}
302
303// |GrContextOptions::PersistentCache|
304void PersistentCache::store(const SkData& key, const SkData& data) {
305 stored_new_shaders_ = true;
306
307 if (is_read_only_) {
308 return;
309 }
310
311 if (!IsValid()) {
312 return;
313 }
314
315 auto file_name = SkKeyToFilePath(key);
316
317 if (file_name.size() == 0) {
318 return;
319 }
320
321 auto mapping = std::make_unique<fml::DataMapping>(
322 std::vector<uint8_t>{data.bytes(), data.bytes() + data.size()});
323
324 if (mapping == nullptr || mapping->GetSize() == 0) {
325 return;
326 }
327
328 PersistentCacheStore(GetWorkerTaskRunner(),
329 cache_sksl_ ? sksl_cache_directory_ : cache_directory_,
330 std::move(file_name), std::move(mapping));
331}
332
333void PersistentCache::DumpSkp(const SkData& data) {
334 if (is_read_only_ || !IsValid()) {
335 FML_LOG(ERROR) << "Could not dump SKP from read-only or invalid persistent "
336 "cache.";
337 return;
338 }
339
340 std::stringstream name_stream;
341 auto ticks = fml::TimePoint::Now().ToEpochDelta().ToNanoseconds();
342 name_stream << "shader_dump_" << std::to_string(ticks) << ".skp";
343 std::string file_name = name_stream.str();
344 FML_LOG(INFO) << "Dumping " << file_name;
345 auto mapping = std::make_unique<fml::DataMapping>(
346 std::vector<uint8_t>{data.bytes(), data.bytes() + data.size()});
347 PersistentCacheStore(GetWorkerTaskRunner(), cache_directory_,
348 std::move(file_name), std::move(mapping));
349}
350
351void PersistentCache::AddWorkerTaskRunner(
352 fml::RefPtr<fml::TaskRunner> task_runner) {
353 std::scoped_lock lock(worker_task_runners_mutex_);
354 worker_task_runners_.insert(task_runner);
355}
356
357void PersistentCache::RemoveWorkerTaskRunner(
358 fml::RefPtr<fml::TaskRunner> task_runner) {
359 std::scoped_lock lock(worker_task_runners_mutex_);
360 auto found = worker_task_runners_.find(task_runner);
361 if (found != worker_task_runners_.end()) {
362 worker_task_runners_.erase(found);
363 }
364}
365
366fml::RefPtr<fml::TaskRunner> PersistentCache::GetWorkerTaskRunner() const {
367 fml::RefPtr<fml::TaskRunner> worker;
368
369 std::scoped_lock lock(worker_task_runners_mutex_);
370 if (!worker_task_runners_.empty()) {
371 worker = *worker_task_runners_.begin();
372 }
373
374 return worker;
375}
376
377void PersistentCache::SetAssetManager(std::shared_ptr<AssetManager> value) {
378 TRACE_EVENT_INSTANT0("flutter", "PersistentCache::SetAssetManager");
379 asset_manager_ = value;
380}
381
382} // namespace flutter
383