blob: aeb113228abf2195c1a17cb717e9f35da4a8fdf0 [file] [log] [blame]
// Copyright 2022 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/cache/cache.h"
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/files/file_util.h"
#include "base/memory/singleton.h"
#include "base/optional.h"
#include "base/values.h"
#include "cobalt/configuration/configuration.h"
#include "cobalt/extension/javascript_cache.h"
#include "cobalt/persistent_storage/persistent_settings.h"
#include "net/disk_cache/cobalt/cobalt_backend_impl.h"
#include "starboard/common/murmurhash2.h"
#include "starboard/configuration_constants.h"
#include "starboard/system.h"
namespace {
base::Optional<uint32_t> GetMinSizeToCacheInBytes(
disk_cache::ResourceType resource_type) {
switch (resource_type) {
case disk_cache::ResourceType::kCompiledScript:
return 4096u;
default:
return base::nullopt;
}
}
base::Optional<std::string> GetSubdirectory(
disk_cache::ResourceType resource_type) {
switch (resource_type) {
case disk_cache::ResourceType::kCacheApi:
return "cache_api";
case disk_cache::ResourceType::kCompiledScript:
return "compiled_js";
default:
return base::nullopt;
}
}
base::Optional<base::FilePath> GetCacheDirectory(
disk_cache::ResourceType resource_type) {
auto subdirectory = GetSubdirectory(resource_type);
if (!subdirectory) {
return base::nullopt;
}
std::vector<char> path(kSbFileMaxPath, 0);
if (!SbSystemGetPath(kSbSystemPathCacheDirectory, path.data(),
kSbFileMaxPath)) {
return base::nullopt;
}
return base::FilePath(path.data()).Append(subdirectory.value());
}
const CobaltExtensionJavaScriptCacheApi* GetJavaScriptCacheExtension() {
const CobaltExtensionJavaScriptCacheApi* javascript_cache_extension =
static_cast<const CobaltExtensionJavaScriptCacheApi*>(
SbSystemGetExtension(kCobaltExtensionJavaScriptCacheName));
if (javascript_cache_extension &&
strcmp(javascript_cache_extension->name,
kCobaltExtensionJavaScriptCacheName) == 0 &&
javascript_cache_extension->version >= 1) {
return javascript_cache_extension;
}
return nullptr;
}
} // namespace
namespace cobalt {
namespace cache {
// static
Cache* Cache::GetInstance() {
return base::Singleton<Cache, base::LeakySingletonTraits<Cache>>::get();
}
// static
uint32_t Cache::CreateKey(const std::string& s) {
return starboard::MurmurHash2_32(s.c_str(), s.size());
}
bool Cache::Delete(disk_cache::ResourceType resource_type, uint32_t key) {
auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type);
if (memory_capped_directory) {
return memory_capped_directory->Delete(key);
}
return false;
}
void Cache::Delete(disk_cache::ResourceType resource_type) {
auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type);
if (memory_capped_directory) {
memory_capped_directory->DeleteAll();
}
}
void Cache::DeleteAll() {
Delete(disk_cache::ResourceType::kCompiledScript);
Delete(disk_cache::ResourceType::kCacheApi);
}
std::vector<uint32_t> Cache::KeysWithMetadata(
disk_cache::ResourceType resource_type) {
auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type);
if (memory_capped_directory) {
return memory_capped_directory->KeysWithMetadata();
}
return std::vector<uint32_t>();
}
std::unique_ptr<base::Value> Cache::Metadata(
disk_cache::ResourceType resource_type, uint32_t key) {
auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type);
if (memory_capped_directory) {
return memory_capped_directory->Metadata(key);
}
return nullptr;
}
std::unique_ptr<std::vector<uint8_t>> Cache::Retrieve(
disk_cache::ResourceType resource_type, uint32_t key,
std::function<std::pair<std::unique_ptr<std::vector<uint8_t>>,
base::Optional<base::Value>>()>
generate) {
base::ScopedClosureRunner notifier(base::BindOnce(
&Cache::Notify, base::Unretained(this), resource_type, key));
auto* e = GetWaitableEvent(resource_type, key);
if (e) {
e->Wait();
delete e;
}
if (resource_type == disk_cache::ResourceType::kCompiledScript) {
const CobaltExtensionJavaScriptCacheApi* javascript_cache_extension =
GetJavaScriptCacheExtension();
if (javascript_cache_extension) {
const uint8_t* cache_data_buf = nullptr;
int cache_data_size = -1;
if (javascript_cache_extension->GetCachedScript(key, 0, &cache_data_buf,
&cache_data_size)) {
auto data = std::make_unique<std::vector<uint8_t>>(
cache_data_buf, cache_data_buf + cache_data_size);
javascript_cache_extension->ReleaseCachedScriptData(cache_data_buf);
return data;
}
auto data = generate().first;
if (data) {
javascript_cache_extension->StoreCachedScript(
key, data->size(), data->data(), data->size());
}
return std::move(data);
}
}
auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type);
if (memory_capped_directory) {
auto data = memory_capped_directory->Retrieve(key);
if (data) {
return data;
}
}
auto data = generate();
if (data.first) {
Store(resource_type, key, /*data=*/*(data.first), /*metadata=*/data.second);
}
return std::move(data.first);
}
std::unique_ptr<std::vector<uint8_t>> Cache::Retrieve(
disk_cache::ResourceType resource_type, uint32_t key) {
base::ScopedClosureRunner notifier(base::BindOnce(
&Cache::Notify, base::Unretained(this), resource_type, key));
auto* e = GetWaitableEvent(resource_type, key);
if (e) {
e->Wait();
delete e;
}
auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type);
if (memory_capped_directory) {
return memory_capped_directory->Retrieve(key);
}
return nullptr;
}
void Cache::set_enabled(bool enabled) { enabled_ = enabled; }
void Cache::set_persistent_settings(
persistent_storage::PersistentSettings* persistent_settings) {
persistent_settings_ = persistent_settings;
// Guaranteed to be called before any calls to Retrieve()
// since set_persistent_settings() is called from the Application()
// constructor before the NetworkModule is initialized.
set_enabled(persistent_settings_->GetPersistentSettingAsBool(
disk_cache::kCacheEnabledPersistentSettingsKey, true));
}
MemoryCappedDirectory* Cache::GetMemoryCappedDirectory(
disk_cache::ResourceType resource_type) {
base::AutoLock auto_lock(lock_);
auto it = memory_capped_directories_.find(resource_type);
if (it != memory_capped_directories_.end()) {
return it->second.get();
}
// Read in size from persistent storage.
auto metadata = disk_cache::kTypeMetadata[resource_type];
if (persistent_settings_) {
uint32_t bucket_size = static_cast<uint32_t>(
persistent_settings_->GetPersistentSettingAsDouble(
metadata.directory, metadata.max_size_bytes));
disk_cache::kTypeMetadata[resource_type] = {metadata.directory,
bucket_size};
}
auto cache_directory = GetCacheDirectory(resource_type);
auto max_size = GetMaxCacheStorageInBytes(resource_type);
if (!cache_directory || !max_size) {
return nullptr;
}
auto memory_capped_directory =
MemoryCappedDirectory::Create(cache_directory.value(), max_size.value());
memory_capped_directories_[resource_type] =
std::move(memory_capped_directory);
return memory_capped_directories_[resource_type].get();
}
void Cache::Resize(disk_cache::ResourceType resource_type, uint32_t bytes) {
if (resource_type != disk_cache::ResourceType::kCacheApi &&
resource_type != disk_cache::ResourceType::kCompiledScript)
return;
if (bytes == disk_cache::kTypeMetadata[resource_type].max_size_bytes) return;
if (persistent_settings_) {
persistent_settings_->SetPersistentSetting(
disk_cache::kTypeMetadata[resource_type].directory,
std::make_unique<base::Value>(static_cast<double>(bytes)));
}
disk_cache::kTypeMetadata[resource_type].max_size_bytes = bytes;
auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type);
if (memory_capped_directory) {
memory_capped_directory->Resize(bytes);
}
if (bytes == 0) {
Delete(resource_type);
}
}
base::Optional<uint32_t> Cache::GetMaxCacheStorageInBytes(
disk_cache::ResourceType resource_type) {
switch (resource_type) {
case disk_cache::ResourceType::kCacheApi:
case disk_cache::ResourceType::kCompiledScript:
return disk_cache::kTypeMetadata[resource_type].max_size_bytes;
default:
return base::nullopt;
}
}
base::WaitableEvent* Cache::GetWaitableEvent(
disk_cache::ResourceType resource_type, uint32_t key) {
base::AutoLock auto_lock(lock_);
if (pending_.find(resource_type) == pending_.end()) {
pending_[resource_type] =
std::map<uint32_t, std::vector<base::WaitableEvent*>>();
}
if (pending_[resource_type].find(key) == pending_[resource_type].end()) {
pending_[resource_type][key] = std::vector<base::WaitableEvent*>();
return nullptr;
}
auto* e = new base::WaitableEvent;
pending_[resource_type][key].push_back(e);
return e;
}
void Cache::Notify(disk_cache::ResourceType resource_type, uint32_t key) {
base::AutoLock auto_lock(lock_);
if (pending_.find(resource_type) == pending_.end()) {
return;
}
if (pending_[resource_type].find(key) == pending_[resource_type].end()) {
return;
}
for (auto* waitable_event : pending_[resource_type][key]) {
waitable_event->Signal();
}
pending_[resource_type][key].clear();
pending_[resource_type].erase(key);
}
void Cache::Store(disk_cache::ResourceType resource_type, uint32_t key,
const std::vector<uint8_t>& data,
const base::Optional<base::Value>& metadata) {
if (!CanCache(resource_type, data.size())) {
return;
}
auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type);
if (memory_capped_directory) {
memory_capped_directory->Store(key, data, metadata);
}
}
bool Cache::CanCache(disk_cache::ResourceType resource_type,
uint32_t data_size) {
return enabled_ &&
cobalt::configuration::Configuration::GetInstance()
->CobaltCanStoreCompiledJavascript() &&
data_size > 0u &&
data_size >= GetMinSizeToCacheInBytes(resource_type) &&
data_size <= GetMaxCacheStorageInBytes(resource_type);
}
} // namespace cache
} // namespace cobalt