Flutter Engine
persistent_cache.cc
Go to the documentation of this file.
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/common/graphics/persistent_cache.h"
6 
7 #include <future>
8 #include <memory>
9 #include <string>
10 #include <string_view>
11 
12 #include "flutter/fml/base32.h"
13 #include "flutter/fml/file.h"
14 #include "flutter/fml/hex_codec.h"
15 #include "flutter/fml/logging.h"
16 #include "flutter/fml/make_copyable.h"
17 #include "flutter/fml/mapping.h"
18 #include "flutter/fml/paths.h"
19 #include "flutter/fml/trace_event.h"
20 #include "flutter/shell/version/version.h"
21 #include "openssl/sha.h"
22 #include "rapidjson/document.h"
23 #include "third_party/skia/include/gpu/GrDirectContext.h"
24 #include "third_party/skia/include/utils/SkBase64.h"
25 
26 namespace flutter {
27 
28 std::string PersistentCache::cache_base_path_;
29 
30 std::shared_ptr<AssetManager> PersistentCache::asset_manager_;
31 
32 std::mutex PersistentCache::instance_mutex_;
33 std::unique_ptr<PersistentCache> PersistentCache::gPersistentCache;
34 
35 std::string PersistentCache::SkKeyToFilePath(const SkData& key) {
36  if (key.data() == nullptr || key.size() == 0) {
37  return "";
38  }
39 
40  uint8_t sha_digest[SHA_DIGEST_LENGTH];
41  SHA1(static_cast<const uint8_t*>(key.data()), key.size(), sha_digest);
42 
43  std::string_view view(reinterpret_cast<const char*>(sha_digest),
44  SHA_DIGEST_LENGTH);
45  return fml::HexEncode(view);
46 }
47 
48 bool PersistentCache::gIsReadOnly = false;
49 
50 std::atomic<bool> PersistentCache::cache_sksl_ = false;
51 std::atomic<bool> PersistentCache::strategy_set_ = false;
52 
54  if (strategy_set_ && value != cache_sksl_) {
55  FML_LOG(ERROR) << "Cache SkSL can only be set before the "
56  "GrContextOptions::fShaderCacheStrategy is set.";
57  return;
58  }
59  cache_sksl_ = value;
60 }
61 
63  std::scoped_lock lock(instance_mutex_);
64  if (gPersistentCache == nullptr) {
65  gPersistentCache.reset(new PersistentCache(gIsReadOnly));
66  }
67  return gPersistentCache.get();
68 }
69 
71  std::scoped_lock lock(instance_mutex_);
72  gPersistentCache.reset(new PersistentCache(gIsReadOnly));
73  strategy_set_ = false;
74 }
75 
77  cache_base_path_ = path;
78 }
79 
81  // Make sure that this is called after the worker task runner setup so all the
82  // file system modifications would happen on that single thread to avoid
83  // racing.
84  FML_CHECK(GetWorkerTaskRunner());
85 
86  std::promise<bool> removed;
87  GetWorkerTaskRunner()->PostTask([&removed,
88  cache_directory = cache_directory_]() {
89  if (cache_directory->is_valid()) {
90  // Only remove files but not directories.
91  FML_LOG(INFO) << "Purge persistent cache.";
92  fml::FileVisitor delete_file = [](const fml::UniqueFD& directory,
93  const std::string& filename) {
94  // Do not delete directories. Return true to continue with other files.
95  if (fml::IsDirectory(directory, filename.c_str())) {
96  return true;
97  }
98  return fml::UnlinkFile(directory, filename.c_str());
99  };
100  removed.set_value(VisitFilesRecursively(*cache_directory, delete_file));
101  } else {
102  removed.set_value(false);
103  }
104  });
105  return removed.get_future().get();
106 }
107 
108 namespace {
109 
110 constexpr char kEngineComponent[] = "flutter_engine";
111 
112 static void FreeOldCacheDirectory(const fml::UniqueFD& cache_base_dir) {
113  fml::UniqueFD engine_dir =
114  fml::OpenDirectoryReadOnly(cache_base_dir, kEngineComponent);
115  if (!engine_dir.is_valid()) {
116  return;
117  }
118  fml::VisitFiles(engine_dir, [](const fml::UniqueFD& directory,
119  const std::string& filename) {
120  if (filename != GetFlutterEngineVersion()) {
121  auto dir = fml::OpenDirectory(directory, filename.c_str(), false,
123  if (dir.is_valid()) {
124  fml::RemoveDirectoryRecursively(directory, filename.c_str());
125  }
126  }
127  return true;
128  });
129 }
130 
131 static std::shared_ptr<fml::UniqueFD> MakeCacheDirectory(
132  const std::string& global_cache_base_path,
133  bool read_only,
134  bool cache_sksl) {
135  fml::UniqueFD cache_base_dir;
136  if (global_cache_base_path.length()) {
137  cache_base_dir = fml::OpenDirectory(global_cache_base_path.c_str(), false,
139  } else {
140  cache_base_dir = fml::paths::GetCachesDirectory();
141  }
142 
143  if (cache_base_dir.is_valid()) {
144  FreeOldCacheDirectory(cache_base_dir);
145  std::vector<std::string> components = {
146  kEngineComponent, GetFlutterEngineVersion(), "skia", GetSkiaVersion()};
147  if (cache_sksl) {
148  components.push_back(PersistentCache::kSkSLSubdirName);
149  }
150  return std::make_shared<fml::UniqueFD>(
151  CreateDirectory(cache_base_dir, components,
152  read_only ? fml::FilePermission::kRead
154  } else {
155  return std::make_shared<fml::UniqueFD>();
156  }
157 }
158 } // namespace
159 
160 sk_sp<SkData> ParseBase32(const std::string& input) {
161  std::pair<bool, std::string> decode_result = fml::Base32Decode(input);
162  if (!decode_result.first) {
163  FML_LOG(ERROR) << "Base32 can't decode: " << input;
164  return nullptr;
165  }
166  const std::string& data_string = decode_result.second;
167  return SkData::MakeWithCopy(data_string.data(), data_string.length());
168 }
169 
170 sk_sp<SkData> ParseBase64(const std::string& input) {
171  SkBase64::Error error;
172 
173  size_t output_len;
174  error = SkBase64::Decode(input.c_str(), input.length(), nullptr, &output_len);
175  if (error != SkBase64::Error::kNoError) {
176  FML_LOG(ERROR) << "Base64 decode error: " << error;
177  FML_LOG(ERROR) << "Base64 can't decode: " << input;
178  return nullptr;
179  }
180 
181  sk_sp<SkData> data = SkData::MakeUninitialized(output_len);
182  void* output = data->writable_data();
183  error = SkBase64::Decode(input.c_str(), input.length(), output, &output_len);
184  if (error != SkBase64::Error::kNoError) {
185  FML_LOG(ERROR) << "Base64 decode error: " << error;
186  FML_LOG(ERROR) << "Base64 can't decode: " << input;
187  return nullptr;
188  }
189 
190  return data;
191 }
192 
193 size_t PersistentCache::PrecompileKnownSkSLs(GrDirectContext* context) const {
194  auto known_sksls = LoadSkSLs();
195  // A trace must be present even if no precompilations have been completed.
196  FML_TRACE_EVENT("flutter", "PersistentCache::PrecompileKnownSkSLs", "count",
197  known_sksls.size());
198 
199  if (context == nullptr) {
200  return 0;
201  }
202 
203  size_t precompiled_count = 0;
204  for (const auto& sksl : known_sksls) {
205  TRACE_EVENT0("flutter", "PrecompilingSkSL");
206  if (context->precompileShader(*sksl.key, *sksl.value)) {
207  precompiled_count++;
208  }
209  }
210 
211  FML_TRACE_COUNTER("flutter", "PersistentCache::PrecompiledSkSLs",
212  reinterpret_cast<int64_t>(this), // Trace Counter ID
213  "Successful", precompiled_count);
214  return precompiled_count;
215 }
216 
217 std::vector<PersistentCache::SkSLCache> PersistentCache::LoadSkSLs() const {
218  TRACE_EVENT0("flutter", "PersistentCache::LoadSkSLs");
219  std::vector<PersistentCache::SkSLCache> result;
220  fml::FileVisitor visitor = [&result](const fml::UniqueFD& directory,
221  const std::string& filename) {
222  SkSLCache cache = LoadFile(directory, filename, true);
223  if (cache.key != nullptr && cache.value != nullptr) {
224  result.push_back(cache);
225  } else {
226  FML_LOG(ERROR) << "Failed to load: " << filename;
227  }
228  return true;
229  };
230 
231  // Only visit sksl_cache_directory_ if this persistent cache is valid.
232  // However, we'd like to continue visit the asset dir even if this persistent
233  // cache is invalid.
234  if (IsValid()) {
235  // In case `rewinddir` doesn't work reliably, load SkSLs from a freshly
236  // opened directory (https://github.com/flutter/flutter/issues/65258).
237  fml::UniqueFD fresh_dir =
238  fml::OpenDirectoryReadOnly(*cache_directory_, kSkSLSubdirName);
239  if (fresh_dir.is_valid()) {
240  fml::VisitFiles(fresh_dir, visitor);
241  }
242  }
243 
244  std::unique_ptr<fml::Mapping> mapping = nullptr;
245  if (asset_manager_ != nullptr) {
246  mapping = asset_manager_->GetAsMapping(kAssetFileName);
247  }
248  if (mapping == nullptr) {
249  FML_LOG(INFO) << "No sksl asset found.";
250  } else {
251  FML_LOG(INFO) << "Found sksl asset. Loading SkSLs from it...";
252  rapidjson::Document json_doc;
253  rapidjson::ParseResult parse_result =
254  json_doc.Parse(reinterpret_cast<const char*>(mapping->GetMapping()),
255  mapping->GetSize());
256  if (parse_result != rapidjson::ParseErrorCode::kParseErrorNone) {
257  FML_LOG(ERROR) << "Failed to parse json file: " << kAssetFileName;
258  } else {
259  for (auto& item : json_doc["data"].GetObject()) {
260  sk_sp<SkData> key = ParseBase32(item.name.GetString());
261  sk_sp<SkData> sksl = ParseBase64(item.value.GetString());
262  if (key != nullptr && sksl != nullptr) {
263  result.push_back({key, sksl});
264  } else {
265  FML_LOG(ERROR) << "Failed to load: " << item.name.GetString();
266  }
267  }
268  }
269  }
270 
271  return result;
272 }
273 
274 PersistentCache::PersistentCache(bool read_only)
275  : is_read_only_(read_only),
276  cache_directory_(MakeCacheDirectory(cache_base_path_, read_only, false)),
277  sksl_cache_directory_(
278  MakeCacheDirectory(cache_base_path_, read_only, true)) {
279  if (!IsValid()) {
280  FML_LOG(WARNING) << "Could not acquire the persistent cache directory. "
281  "Caching of GPU resources on disk is disabled.";
282  }
283 }
284 
286 
287 bool PersistentCache::IsValid() const {
288  return cache_directory_ && cache_directory_->is_valid();
289 }
290 
291 PersistentCache::SkSLCache PersistentCache::LoadFile(
292  const fml::UniqueFD& dir,
293  const std::string& file_name,
294  bool need_key) {
296  auto file = fml::OpenFileReadOnly(dir, file_name.c_str());
297  if (!file.is_valid()) {
298  return result;
299  }
300  auto mapping = std::make_unique<fml::FileMapping>(file);
301  if (mapping->GetSize() < sizeof(CacheObjectHeader)) {
302  return result;
303  }
304  const CacheObjectHeader* header =
305  reinterpret_cast<const CacheObjectHeader*>(mapping->GetMapping());
306  if (header->signature != CacheObjectHeader::kSignature ||
308  FML_LOG(INFO) << "Persistent cache header is corrupt: " << file_name;
309  return result;
310  }
311  if (mapping->GetSize() < sizeof(CacheObjectHeader) + header->key_size) {
312  FML_LOG(INFO) << "Persistent cache size is corrupt: " << file_name;
313  return result;
314  }
315  if (need_key) {
316  result.key = SkData::MakeWithCopy(
317  mapping->GetMapping() + sizeof(CacheObjectHeader), header->key_size);
318  }
319  size_t value_offset = sizeof(CacheObjectHeader) + header->key_size;
320  result.value = SkData::MakeWithCopy(mapping->GetMapping() + value_offset,
321  mapping->GetSize() - value_offset);
322  return result;
323 }
324 
325 // |GrContextOptions::PersistentCache|
326 sk_sp<SkData> PersistentCache::load(const SkData& key) {
327  TRACE_EVENT0("flutter", "PersistentCacheLoad");
328  if (!IsValid()) {
329  return nullptr;
330  }
331  auto file_name = SkKeyToFilePath(key);
332  if (file_name.size() == 0) {
333  return nullptr;
334  }
335  auto result =
336  PersistentCache::LoadFile(*cache_directory_, file_name, false).value;
337  if (result != nullptr) {
338  TRACE_EVENT0("flutter", "PersistentCacheLoadHit");
339  }
340  return result;
341 }
342 
344  std::shared_ptr<fml::UniqueFD> cache_directory,
345  std::string key,
346  std::unique_ptr<fml::Mapping> value) {
347  auto task = fml::MakeCopyable([cache_directory, //
348  file_name = std::move(key), //
349  mapping = std::move(value) //
350  ]() mutable {
351  TRACE_EVENT0("flutter", "PersistentCacheStore");
352  if (!fml::WriteAtomically(*cache_directory, //
353  file_name.c_str(), //
354  *mapping) //
355  ) {
356  FML_LOG(WARNING) << "Could not write cache contents to persistent store.";
357  }
358  });
359 
360  if (!worker) {
361  FML_LOG(WARNING)
362  << "The persistent cache has no available workers. Performing the task "
363  "on the current thread. This slow operation is going to occur on a "
364  "frame workload.";
365  task();
366  } else {
367  worker->PostTask(std::move(task));
368  }
369 }
370 
371 std::unique_ptr<fml::MallocMapping> PersistentCache::BuildCacheObject(
372  const SkData& key,
373  const SkData& data) {
374  size_t total_size = sizeof(CacheObjectHeader) + key.size() + data.size();
375  uint8_t* mapping_buf = reinterpret_cast<uint8_t*>(malloc(total_size));
376  if (!mapping_buf) {
377  return nullptr;
378  }
379  auto mapping = std::make_unique<fml::MallocMapping>(mapping_buf, total_size);
380 
381  CacheObjectHeader header(key.size());
382  memcpy(mapping_buf, &header, sizeof(CacheObjectHeader));
383  mapping_buf += sizeof(CacheObjectHeader);
384  memcpy(mapping_buf, key.data(), key.size());
385  mapping_buf += key.size();
386  memcpy(mapping_buf, data.data(), data.size());
387 
388  return mapping;
389 }
390 
391 // |GrContextOptions::PersistentCache|
392 void PersistentCache::store(const SkData& key, const SkData& data) {
393  stored_new_shaders_ = true;
394 
395  if (is_read_only_) {
396  return;
397  }
398 
399  if (!IsValid()) {
400  return;
401  }
402 
403  auto file_name = SkKeyToFilePath(key);
404 
405  if (file_name.size() == 0) {
406  return;
407  }
408 
409  std::unique_ptr<fml::MallocMapping> mapping = BuildCacheObject(key, data);
410  if (!mapping) {
411  return;
412  }
413 
414  PersistentCacheStore(GetWorkerTaskRunner(),
415  cache_sksl_ ? sksl_cache_directory_ : cache_directory_,
416  std::move(file_name), std::move(mapping));
417 }
418 
419 void PersistentCache::DumpSkp(const SkData& data) {
420  if (is_read_only_ || !IsValid()) {
421  FML_LOG(ERROR) << "Could not dump SKP from read-only or invalid persistent "
422  "cache.";
423  return;
424  }
425 
426  std::stringstream name_stream;
428  name_stream << "shader_dump_" << std::to_string(ticks) << ".skp";
429  std::string file_name = name_stream.str();
430  FML_LOG(INFO) << "Dumping " << file_name;
431  auto mapping = std::make_unique<fml::DataMapping>(
432  std::vector<uint8_t>{data.bytes(), data.bytes() + data.size()});
433  PersistentCacheStore(GetWorkerTaskRunner(), cache_directory_,
434  std::move(file_name), std::move(mapping));
435 }
436 
438  fml::RefPtr<fml::TaskRunner> task_runner) {
439  std::scoped_lock lock(worker_task_runners_mutex_);
440  worker_task_runners_.insert(task_runner);
441 }
442 
444  fml::RefPtr<fml::TaskRunner> task_runner) {
445  std::scoped_lock lock(worker_task_runners_mutex_);
446  auto found = worker_task_runners_.find(task_runner);
447  if (found != worker_task_runners_.end()) {
448  worker_task_runners_.erase(found);
449  }
450 }
451 
452 fml::RefPtr<fml::TaskRunner> PersistentCache::GetWorkerTaskRunner() const {
454 
455  std::scoped_lock lock(worker_task_runners_mutex_);
456  if (!worker_task_runners_.empty()) {
457  worker = *worker_task_runners_.begin();
458  }
459 
460  return worker;
461 }
462 
463 void PersistentCache::SetAssetManager(std::shared_ptr<AssetManager> value) {
464  TRACE_EVENT_INSTANT0("flutter", "PersistentCache::SetAssetManager");
465  asset_manager_ = value;
466 }
467 
468 std::vector<std::unique_ptr<fml::Mapping>>
470  if (!asset_manager_) {
471  FML_LOG(ERROR)
472  << "PersistentCache::GetSkpsFromAssetManager: Asset manager not set!";
473  return std::vector<std::unique_ptr<fml::Mapping>>();
474  }
475  return asset_manager_->GetAsMappings(".*\\.skp$", "shaders");
476 }
477 
478 } // namespace flutter
const char * GetFlutterEngineVersion()
Definition: version.cc:11
DEF_SWITCHES_START snapshot asset path
Definition: switches.h:32
static std::unique_ptr< fml::MallocMapping > BuildCacheObject(const SkData &key, const SkData &data)
sk_sp< SkData > ParseBase64(const std::string &input)
const uint8_t uint32_t uint32_t GError ** error
#define TRACE_EVENT0(category_group, name)
Definition: trace_event.h:90
void DumpSkp(const SkData &data)
#define FML_TRACE_COUNTER(category_group, name, counter_id, arg1,...)
Definition: trace_event.h:69
size_t PrecompileKnownSkSLs(GrDirectContext *context) const
Precompile SkSLs packaged with the application and gathered during previous runs in the given context...
#define TRACE_EVENT_INSTANT0(category_group, name)
Definition: trace_event.h:119
std::function< bool(const fml::UniqueFD &directory, const std::string &filename)> FileVisitor
Definition: file.h:98
fml::UniqueFD GetCachesDirectory()
GAsyncResult * result
TimeDelta ToEpochDelta() const
Definition: time_point.h:47
sk_sp< SkData > ParseBase32(const std::string &input)
#define FML_LOG(severity)
Definition: logging.h:65
static void SetCacheSkSL(bool value)
#define CreateDirectory
static constexpr char kAssetFileName[]
void AddWorkerTaskRunner(fml::RefPtr< fml::TaskRunner > task_runner)
bool is_valid() const
Definition: unique_object.h:89
fml::UniqueFD OpenDirectory(const char *path, bool create_if_necessary, FilePermission permission)
Definition: file_posix.cc:96
static constexpr char kSkSLSubdirName[]
internal::CopyableLambda< T > MakeCopyable(T lambda)
Definition: make_copyable.h:57
bool VisitFiles(const fml::UniqueFD &directory, const FileVisitor &visitor)
Definition: file_posix.cc:235
static void SetAssetManager(std::shared_ptr< AssetManager > value)
uint8_t value
static void PersistentCacheStore(fml::RefPtr< fml::TaskRunner > worker, std::shared_ptr< fml::UniqueFD > cache_directory, std::string key, std::unique_ptr< fml::Mapping > value)
static void SetCacheDirectoryPath(std::string path)
void RemoveWorkerTaskRunner(fml::RefPtr< fml::TaskRunner > task_runner)
bool IsDirectory(const fml::UniqueFD &directory)
Definition: file_posix.cc:126
std::pair< bool, std::string > Base32Decode(const std::string &input)
Definition: base32.cc:54
std::string HexEncode(std::string_view input)
Definition: hex_codec.cc:13
constexpr int64_t ToNanoseconds() const
Definition: time_delta.h:61
sk_sp< SkData > load(const SkData &key) override
const char * GetSkiaVersion()
Definition: version.cc:15
fml::UniqueFD OpenDirectoryReadOnly(const fml::UniqueFD &base_directory, const char *path)
Definition: file.cc:97
static std::string SkKeyToFilePath(const SkData &key)
bool VisitFilesRecursively(const fml::UniqueFD &directory, const FileVisitor &visitor)
Definition: file.cc:71
bool UnlinkFile(const char *path)
Definition: file_posix.cc:170
static void ResetCacheForProcess()
std::vector< std::unique_ptr< fml::Mapping > > GetSkpsFromAssetManager() const
static PersistentCache * GetCacheForProcess()
#define FML_CHECK(condition)
Definition: logging.h:68
FlView * view
std::vector< SkSLCache > LoadSkSLs() const
Load all the SkSL shader caches in the right directory.
#define FML_TRACE_EVENT(category_group, name,...)
Definition: trace_event.h:86
bool WriteAtomically(const fml::UniqueFD &base_directory, const char *file_name, const Mapping &mapping)
Definition: file_posix.cc:190
virtual void PostTask(const fml::closure &task) override
Definition: task_runner.cc:24
bool RemoveDirectoryRecursively(const fml::UniqueFD &parent, const char *directory_name)
Definition: file.cc:120
static TimePoint Now()
Definition: time_point.cc:39
fml::UniqueFD OpenFileReadOnly(const fml::UniqueFD &base_directory, const char *path)
Definition: file.cc:92