blob: 3b1a0078d6b7699890f8f76f52f71d8a3ac6eb12 [file] [log] [blame]
// Copyright 2019 The Chromium 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 "components/optimization_guide/hint_cache_store.h"
#include "base/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "components/leveldb_proto/public/proto_database.h"
#include "components/leveldb_proto/public/proto_database_provider.h"
#include "components/leveldb_proto/public/shared_proto_database_client_list.h"
#include "components/optimization_guide/optimization_guide_prefs.h"
#include "components/optimization_guide/proto/hint_cache.pb.h"
namespace optimization_guide {
namespace {
// Enforce that StoreEntryType enum is synced with the StoreEntryType proto
// (components/previews/content/proto/hint_cache.proto)
static_assert(proto::StoreEntryType_MAX ==
static_cast<int>(HintCacheStore::StoreEntryType::kMaxValue),
"mismatched StoreEntryType enums");
// The folder where the data will be stored on disk.
constexpr char kHintCacheStoreFolder[] = "previews_hint_cache_store";
// The amount of data to build up in memory before converting to a sorted on-
// disk file.
constexpr size_t kDatabaseWriteBufferSizeBytes = 128 * 1024;
// Delimiter that appears between the sections of a store entry key.
// Examples:
// "[StoreEntryType::kMetadata]_[MetadataType]"
// "[StoreEntryType::kComponentHint]_[component_version]_[host]"
constexpr char kKeySectionDelimiter = '_';
// Realistic minimum length of a host suffix.
const int kMinHostSuffix = 6; // eg., abc.tv
// Enumerates the possible outcomes of loading metadata. Used in UMA histograms,
// so the order of enumerators should not be changed.
//
// Keep in sync with OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult
// in tools/metrics/histograms/enums.xml.
enum class OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult {
kSuccess = 0,
kLoadMetadataFailed = 1,
kSchemaMetadataMissing = 2,
kSchemaMetadataWrongVersion = 3,
kComponentMetadataMissing = 4,
kFetchedMetadataMissing = 5,
kComponentAndFetchedMetadataMissing = 6,
kMaxValue = kComponentAndFetchedMetadataMissing,
};
// Util class for recording the result of loading the metadata. The result is
// recorded when it goes out of scope and its destructor is called.
class ScopedLoadMetadataResultRecorder {
public:
ScopedLoadMetadataResultRecorder()
: result_(OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult::
kSuccess) {}
~ScopedLoadMetadataResultRecorder() {
UMA_HISTOGRAM_ENUMERATION(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult", result_);
}
void set_result(
OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult result) {
result_ = result;
}
private:
OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult result_;
};
void RecordStatusChange(HintCacheStore::Status status) {
UMA_HISTOGRAM_ENUMERATION("OptimizationGuide.HintCacheLevelDBStore.Status",
status);
}
// Returns true if |key_prefix| is a prefix of |key|.
bool DatabasePrefixFilter(const std::string& key_prefix,
const std::string& key) {
return base::StartsWith(key, key_prefix, base::CompareCase::SENSITIVE);
}
} // namespace
HintCacheStore::HintCacheStore(
leveldb_proto::ProtoDatabaseProvider* database_provider,
const base::FilePath& database_dir,
scoped_refptr<base::SequencedTaskRunner> store_task_runner) {
base::FilePath hint_store_dir =
database_dir.AppendASCII(kHintCacheStoreFolder);
database_ = database_provider->GetDB<proto::StoreEntry>(
leveldb_proto::ProtoDbType::HINT_CACHE_STORE, hint_store_dir,
store_task_runner);
RecordStatusChange(status_);
}
HintCacheStore::HintCacheStore(
std::unique_ptr<leveldb_proto::ProtoDatabase<proto::StoreEntry>> database)
: database_(std::move(database)) {
RecordStatusChange(status_);
}
HintCacheStore::~HintCacheStore() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void HintCacheStore::Initialize(bool purge_existing_data,
base::OnceClosure callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
UpdateStatus(Status::kInitializing);
// Asynchronously initialize the store and run the callback once
// initialization completes. Initialization consists of the following steps:
// 1. Initialize the database.
// 2. If |purge_existing_data| is set to true, unconditionally purge
// database and skip to step 6.
// 3. Otherwise, retrieve the metadata entries (e.g. Schema and Component).
// 4. If schema is the wrong version, purge database and skip to step 6.
// 5. Otherwise, load all hint entry keys.
// 6. Run callback after purging database or retrieving hint entry keys.
leveldb_env::Options options = leveldb_proto::CreateSimpleOptions();
options.write_buffer_size = kDatabaseWriteBufferSizeBytes;
database_->Init(options,
base::BindOnce(&HintCacheStore::OnDatabaseInitialized,
weak_ptr_factory_.GetWeakPtr(),
purge_existing_data, std::move(callback)));
}
std::unique_ptr<HintUpdateData>
HintCacheStore::MaybeCreateUpdateDataForComponentHints(
const base::Version& version) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(version.IsValid());
if (!IsAvailable()) {
return nullptr;
}
// Component updates are only permitted when the update version is newer than
// the store's current one.
if (component_version_.has_value() && version <= component_version_) {
return nullptr;
}
// Create and return a HintUpdateData object. This object has
// hints from the component moved into it and organizes them in a format
// usable by the store; the object will returned to the store in
// StoreComponentHints().
return HintUpdateData::CreateComponentHintUpdateData(version);
}
std::unique_ptr<HintUpdateData> HintCacheStore::CreateUpdateDataForFetchedHints(
base::Time update_time,
base::Time expiry_time) const {
// Create and returns a HintUpdateData object. This object has has hints
// from the GetHintsResponse moved into and organizes them in a format
// usable by the store. The object will be store with UpdateFetchedData().
return HintUpdateData::CreateFetchedHintUpdateData(update_time, expiry_time);
}
void HintCacheStore::UpdateComponentHints(
std::unique_ptr<HintUpdateData> component_data,
base::OnceClosure callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(component_data);
DCHECK(!data_update_in_flight_);
DCHECK(component_data->component_version());
if (!IsAvailable()) {
std::move(callback).Run();
return;
}
// If this isn't a newer component version than the store's current one, then
// the simply return. There's nothing to update.
if (component_version_.has_value() &&
component_data->component_version() <= component_version_) {
std::move(callback).Run();
return;
}
// Mark that there's now a component data update in-flight. While this is
// true, keys and hints will not be returned by the store.
data_update_in_flight_ = true;
// Set the component version prior to requesting the update. This ensures that
// a second update request for the same component version won't be allowed. In
// the case where the update fails, the store will become unavailable, so it's
// safe to treat component version in the update as the new one.
SetComponentVersion(*component_data->component_version());
// The current keys are about to be removed, so clear out the keys available
// within the store. The keys will be populated after the component data
// update completes.
hint_entry_keys_.reset();
// Purge any component hints that are missing the new component version
// prefix.
EntryKeyPrefix retain_prefix =
GetComponentHintEntryKeyPrefix(component_version_.value());
EntryKeyPrefix filter_prefix = GetComponentHintEntryKeyPrefixWithoutVersion();
// Add the new component data and purge any old component hints from the db.
// After processing finishes, OnUpdateHints() is called, which loads
// the updated hint entry keys from the database.
database_->UpdateEntriesWithRemoveFilter(
component_data->TakeUpdateEntries(),
base::BindRepeating(
[](const EntryKeyPrefix& retain_prefix,
const EntryKeyPrefix& filter_prefix, const std::string& key) {
return key.compare(0, retain_prefix.length(), retain_prefix) != 0 &&
key.compare(0, filter_prefix.length(), filter_prefix) == 0;
},
retain_prefix, filter_prefix),
base::BindOnce(&HintCacheStore::OnUpdateHints,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void HintCacheStore::UpdateFetchedHints(
std::unique_ptr<HintUpdateData> fetched_hints_data,
base::OnceClosure callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(fetched_hints_data);
DCHECK(!data_update_in_flight_);
DCHECK(fetched_hints_data->fetch_update_time());
if (!IsAvailable()) {
std::move(callback).Run();
return;
}
fetched_update_time_ = *fetched_hints_data->fetch_update_time();
data_update_in_flight_ = true;
hint_entry_keys_.reset();
// This will remove the fetched metadata entry and insert all the entries
// currently in |leveldb_fetched_hints_data|.
database_->UpdateEntriesWithRemoveFilter(
fetched_hints_data->TakeUpdateEntries(),
base::BindRepeating(&DatabasePrefixFilter,
GetMetadataTypeEntryKey(MetadataType::kFetched)),
base::BindOnce(&HintCacheStore::OnUpdateHints,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void HintCacheStore::PurgeExpiredFetchedHints() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!IsAvailable()) {
return;
}
// Load all the fetched hints to check their expiry times.
database_->LoadKeysAndEntriesWithFilter(
base::BindRepeating(&DatabasePrefixFilter,
GetFetchedHintEntryKeyPrefix()),
base::BindOnce(&HintCacheStore::OnLoadFetchedHintsToPurgeExpired,
weak_ptr_factory_.GetWeakPtr()));
}
void HintCacheStore::OnLoadFetchedHintsToPurgeExpired(
bool success,
std::unique_ptr<EntryMap> fetched_entries) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!success) {
return;
}
auto keys_to_remove = std::make_unique<EntryKeySet>();
int64_t now_since_epoch =
base::Time::Now().ToDeltaSinceWindowsEpoch().InSeconds();
for (const auto& entry : *fetched_entries) {
if (entry.second.expiry_time_secs() <= now_since_epoch) {
keys_to_remove->insert(entry.first);
}
}
// TODO(mcrouse): Record the number of hints that will be expired from the
// store.
data_update_in_flight_ = true;
hint_entry_keys_.reset();
auto empty_entries = std::make_unique<EntryVector>();
database_->UpdateEntriesWithRemoveFilter(
std::move(empty_entries),
base::BindRepeating(
[](EntryKeySet* keys_to_remove, const std::string& key) {
return keys_to_remove->find(key) != keys_to_remove->end();
},
keys_to_remove.get()),
base::BindOnce(&HintCacheStore::OnUpdateHints,
weak_ptr_factory_.GetWeakPtr(), base::DoNothing::Once()));
}
bool HintCacheStore::FindHintEntryKey(const std::string& host,
EntryKey* out_hint_entry_key) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Search for kFetched hint entry keys first, fetched hints should be
// fresher and preferred.
if (FindHintEntryKeyForHostWithPrefix(host, out_hint_entry_key,
GetFetchedHintEntryKeyPrefix())) {
return true;
}
// Search for kComponent hint entry keys next.
DCHECK(!component_version_.has_value() ||
component_hint_entry_key_prefix_ ==
GetComponentHintEntryKeyPrefix(component_version_.value()));
if (FindHintEntryKeyForHostWithPrefix(host, out_hint_entry_key,
component_hint_entry_key_prefix_))
return true;
return false;
}
bool HintCacheStore::FindHintEntryKeyForHostWithPrefix(
const std::string& host,
EntryKey* out_hint_entry_key,
const EntryKeyPrefix& hint_entry_key_prefix) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(out_hint_entry_key);
// Look for longest host name suffix that has a hint. No need to continue
// lookups and substring work once get to a root domain like ".com" or
// ".co.in" (MinHostSuffix length check is a heuristic for that).
std::string host_suffix(host);
while (host_suffix.length() >= kMinHostSuffix) {
// Attempt to find a hint entry key associated with the current host suffix.
*out_hint_entry_key = hint_entry_key_prefix + host_suffix;
if (hint_entry_keys_ && hint_entry_keys_->find(*out_hint_entry_key) !=
hint_entry_keys_->end()) {
return true;
}
size_t pos = host_suffix.find_first_of('.');
if (pos == std::string::npos) {
break;
}
host_suffix = host_suffix.substr(pos + 1);
}
return false;
}
void HintCacheStore::LoadHint(const EntryKey& hint_entry_key,
HintLoadedCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!IsAvailable()) {
std::move(callback).Run(hint_entry_key, nullptr);
return;
}
database_->GetEntry(hint_entry_key,
base::BindOnce(&HintCacheStore::OnLoadHint,
weak_ptr_factory_.GetWeakPtr(),
hint_entry_key, std::move(callback)));
}
base::Time HintCacheStore::FetchedHintsUpdateTime() const {
// If the store is not available, the metadata entries have not been loaded
// so there are no fetched hints.
if (!IsAvailable())
return base::Time();
return fetched_update_time_;
}
// static
const char HintCacheStore::kStoreSchemaVersion[] = "1";
// static
HintCacheStore::EntryKeyPrefix HintCacheStore::GetMetadataEntryKeyPrefix() {
return base::NumberToString(
static_cast<int>(HintCacheStore::StoreEntryType::kMetadata)) +
kKeySectionDelimiter;
}
// static
HintCacheStore::EntryKey HintCacheStore::GetMetadataTypeEntryKey(
MetadataType metadata_type) {
return GetMetadataEntryKeyPrefix() +
base::NumberToString(static_cast<int>(metadata_type));
}
// static
HintCacheStore::EntryKeyPrefix
HintCacheStore::GetComponentHintEntryKeyPrefixWithoutVersion() {
return base::NumberToString(
static_cast<int>(HintCacheStore::StoreEntryType::kComponentHint)) +
kKeySectionDelimiter;
}
// static
HintCacheStore::EntryKeyPrefix HintCacheStore::GetComponentHintEntryKeyPrefix(
const base::Version& component_version) {
return GetComponentHintEntryKeyPrefixWithoutVersion() +
component_version.GetString() + kKeySectionDelimiter;
}
// static
HintCacheStore::EntryKeyPrefix HintCacheStore::GetFetchedHintEntryKeyPrefix() {
return base::NumberToString(
static_cast<int>(HintCacheStore::StoreEntryType::kFetchedHint)) +
kKeySectionDelimiter;
}
void HintCacheStore::UpdateStatus(Status new_status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Status::kUninitialized can only transition to Status::kInitializing.
DCHECK(status_ != Status::kUninitialized ||
new_status == Status::kInitializing);
// Status::kInitializing can only transition to Status::kAvailable or
// Status::kFailed.
DCHECK(status_ != Status::kInitializing || new_status == Status::kAvailable ||
new_status == Status::kFailed);
// Status::kAvailable can only transition to kStatus::Failed.
DCHECK(status_ != Status::kAvailable || new_status == Status::kFailed);
// The status can never transition from Status::kFailed.
DCHECK(status_ != Status::kFailed || new_status == Status::kFailed);
// If the status is not changing, simply return; the remaining logic handles
// status changes.
if (status_ == new_status) {
return;
}
status_ = new_status;
RecordStatusChange(status_);
if (status_ == Status::kFailed) {
database_->Destroy(base::BindOnce(&HintCacheStore::OnDatabaseDestroyed,
weak_ptr_factory_.GetWeakPtr()));
ClearComponentVersion();
hint_entry_keys_.reset();
}
}
bool HintCacheStore::IsAvailable() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return status_ == Status::kAvailable;
}
void HintCacheStore::PurgeDatabase(base::OnceClosure callback) {
// When purging the database, update the schema version to the current one.
EntryKey schema_entry_key = GetMetadataTypeEntryKey(MetadataType::kSchema);
proto::StoreEntry schema_entry;
schema_entry.set_version(kStoreSchemaVersion);
auto entries_to_save = std::make_unique<EntryVector>();
entries_to_save->emplace_back(schema_entry_key, schema_entry);
database_->UpdateEntriesWithRemoveFilter(
std::move(entries_to_save),
base::BindRepeating(
[](const std::string& schema_entry_key, const std::string& key) {
return key.compare(0, schema_entry_key.length(),
schema_entry_key) != 0;
},
schema_entry_key),
base::BindOnce(&HintCacheStore::OnPurgeDatabase,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void HintCacheStore::SetComponentVersion(
const base::Version& component_version) {
DCHECK(component_version.IsValid());
component_version_ = component_version;
component_hint_entry_key_prefix_ =
GetComponentHintEntryKeyPrefix(component_version_.value());
}
void HintCacheStore::ClearComponentVersion() {
component_version_.reset();
component_hint_entry_key_prefix_.clear();
}
void HintCacheStore::ClearFetchedHintsFromDatabase() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!data_update_in_flight_);
if (!IsAvailable()) {
return;
}
data_update_in_flight_ = true;
auto entries_to_save = std::make_unique<EntryVector>();
// TODO(mcrouse): Add histogram to record the number of hints being removed.
hint_entry_keys_.reset();
// Removes all |kFetchedHint| store entries. OnUpdateHints will handle
// updating status and re-filling hint_entry_keys with the hints still in the
// store.
database_->UpdateEntriesWithRemoveFilter(
std::move(entries_to_save), // this should be empty.
base::BindRepeating(&DatabasePrefixFilter,
GetFetchedHintEntryKeyPrefix()),
base::BindOnce(&HintCacheStore::OnUpdateHints,
weak_ptr_factory_.GetWeakPtr(), base::DoNothing::Once()));
}
void HintCacheStore::MaybeLoadHintEntryKeys(base::OnceClosure callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// If the database is unavailable or if there's an in-flight component data
// update, then don't load the hint keys. Simply run the callback.
if (!IsAvailable() || data_update_in_flight_) {
std::move(callback).Run();
return;
}
// Create a new KeySet object. This is populated by the store's keys as the
// filter is run with each key on the DB's background thread. The filter
// itself always returns false, ensuring that no entries are ever actually
// loaded by the DB. Ownership of the KeySet is passed into the
// LoadKeysAndEntriesCallback callback, guaranteeing that the KeySet has a
// lifespan longer than the filter calls.
std::unique_ptr<EntryKeySet> hint_entry_keys(std::make_unique<EntryKeySet>());
EntryKeySet* raw_hint_entry_keys_pointer = hint_entry_keys.get();
database_->LoadKeysAndEntriesWithFilter(
base::BindRepeating(
[](EntryKeySet* hint_entry_keys, const std::string& filter_prefix,
const std::string& entry_key) {
if (entry_key.compare(0, filter_prefix.length(), filter_prefix) !=
0) {
hint_entry_keys->insert(entry_key);
}
return false;
},
raw_hint_entry_keys_pointer, GetMetadataEntryKeyPrefix()),
base::BindOnce(&HintCacheStore::OnLoadHintEntryKeys,
weak_ptr_factory_.GetWeakPtr(), std::move(hint_entry_keys),
std::move(callback)));
}
size_t HintCacheStore::GetHintEntryKeyCount() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return hint_entry_keys_ ? hint_entry_keys_->size() : 0;
}
void HintCacheStore::OnDatabaseInitialized(
bool purge_existing_data,
base::OnceClosure callback,
leveldb_proto::Enums::InitStatus status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (status != leveldb_proto::Enums::InitStatus::kOK) {
UpdateStatus(Status::kFailed);
std::move(callback).Run();
return;
}
// If initialization is set to purge all existing data, then simply trigger
// the purge and return. There's no need to load metadata entries that'll
// immediately be purged.
if (purge_existing_data) {
PurgeDatabase(std::move(callback));
return;
}
// Load all entries from the DB with the metadata key prefix.
database_->LoadKeysAndEntriesWithFilter(
leveldb_proto::KeyFilter(), leveldb::ReadOptions(),
GetMetadataEntryKeyPrefix(),
base::BindOnce(&HintCacheStore::OnLoadMetadata,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void HintCacheStore::OnDatabaseDestroyed(bool /*success*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void HintCacheStore::OnLoadMetadata(
base::OnceClosure callback,
bool success,
std::unique_ptr<EntryMap> metadata_entries) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(metadata_entries);
// Create a scoped load metadata result recorder. It records the result when
// its destructor is called.
ScopedLoadMetadataResultRecorder result_recorder;
if (!success) {
result_recorder.set_result(
OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult::
kLoadMetadataFailed);
UpdateStatus(Status::kFailed);
std::move(callback).Run();
return;
}
// If the schema version in the DB is not the current version, then purge
// the database.
auto schema_entry =
metadata_entries->find(GetMetadataTypeEntryKey(MetadataType::kSchema));
if (schema_entry == metadata_entries->end() ||
!schema_entry->second.has_version() ||
schema_entry->second.version() != kStoreSchemaVersion) {
if (schema_entry == metadata_entries->end()) {
result_recorder.set_result(
OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult::
kSchemaMetadataMissing);
} else {
result_recorder.set_result(
OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult::
kSchemaMetadataWrongVersion);
}
PurgeDatabase(std::move(callback));
return;
}
// If the component metadata entry exists, then use it to set the component
// version.
bool component_metadata_missing = false;
auto component_entry =
metadata_entries->find(GetMetadataTypeEntryKey(MetadataType::kComponent));
if (component_entry != metadata_entries->end()) {
DCHECK(component_entry->second.has_version());
SetComponentVersion(base::Version(component_entry->second.version()));
} else {
result_recorder.set_result(
OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult::
kComponentMetadataMissing);
component_metadata_missing = true;
}
auto fetched_entry =
metadata_entries->find(GetMetadataTypeEntryKey(MetadataType::kFetched));
if (fetched_entry != metadata_entries->end()) {
DCHECK(fetched_entry->second.has_update_time_secs());
fetched_update_time_ = base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromSeconds(fetched_entry->second.update_time_secs()));
} else {
if (component_metadata_missing) {
result_recorder.set_result(
OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult::
kComponentAndFetchedMetadataMissing);
} else {
result_recorder.set_result(
OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult::
kFetchedMetadataMissing);
}
fetched_update_time_ = base::Time();
}
UpdateStatus(Status::kAvailable);
MaybeLoadHintEntryKeys(std::move(callback));
}
void HintCacheStore::OnPurgeDatabase(base::OnceClosure callback, bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// The database can only be purged during initialization.
DCHECK_EQ(status_, Status::kInitializing);
UpdateStatus(success ? Status::kAvailable : Status::kFailed);
std::move(callback).Run();
}
void HintCacheStore::OnUpdateHints(base::OnceClosure callback, bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(data_update_in_flight_);
data_update_in_flight_ = false;
if (!success) {
UpdateStatus(Status::kFailed);
std::move(callback).Run();
return;
}
MaybeLoadHintEntryKeys(std::move(callback));
}
void HintCacheStore::OnLoadHintEntryKeys(
std::unique_ptr<EntryKeySet> hint_entry_keys,
base::OnceClosure callback,
bool success,
std::unique_ptr<EntryMap> /*unused*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!hint_entry_keys_);
if (!success) {
UpdateStatus(Status::kFailed);
std::move(callback).Run();
return;
}
// If the store was set to unavailable after the request was started, or if
// there's an in-flight component data update, which means the keys are
// about to be invalidated, then the loaded keys should not be considered
// valid. Reset the keys so that they are cleared.
if (!IsAvailable() || data_update_in_flight_) {
hint_entry_keys.reset();
}
hint_entry_keys_ = std::move(hint_entry_keys);
std::move(callback).Run();
}
void HintCacheStore::OnLoadHint(const std::string& entry_key,
HintLoadedCallback callback,
bool success,
std::unique_ptr<proto::StoreEntry> entry) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// If either the request failed, the store was set to unavailable after the
// request was started, or there's an in-flight component data update, which
// means the entry is about to be invalidated, then the loaded hint should
// not be considered valid. Reset the entry so that no hint is returned to
// the requester.
if (!success || !IsAvailable() || data_update_in_flight_) {
entry.reset();
}
if (!entry || !entry->has_hint()) {
std::unique_ptr<proto::Hint> loaded_hint(nullptr);
std::move(callback).Run(entry_key, std::move(loaded_hint));
return;
}
if (entry->has_expiry_time_secs() &&
entry->expiry_time_secs() <=
base::Time::Now().ToDeltaSinceWindowsEpoch().InSeconds()) {
// An expired hint should be loaded rarely if the user is regularly fetching
// and storing fresh hints. Expired fetched hints are removed each time
// fresh hints are fetched and placed into the store. In the future, the
// expired hints could be asynchronously removed if necessary.
// An empty hint is returned instead of the expired one.
UMA_HISTOGRAM_BOOLEAN(
"OptimizationGuide.HintCacheStore.OnLoadHint.FetchedHintExpired", true);
std::unique_ptr<proto::Hint> loaded_hint(nullptr);
std::move(callback).Run(entry_key, std::move(loaded_hint));
return;
}
// Release the Hint into a Hint unique_ptr. This eliminates the need for any
// copies of the entry's hint.
std::unique_ptr<proto::Hint> loaded_hint(entry->release_hint());
StoreEntryType store_entry_type =
static_cast<StoreEntryType>(entry->entry_type());
UMA_HISTOGRAM_ENUMERATION("OptimizationGuide.HintCache.HintType.Loaded",
store_entry_type);
if (store_entry_type == StoreEntryType::kFetchedHint) {
UMA_HISTOGRAM_CUSTOM_TIMES(
"OptimizationGuide.HintCache.FetchedHint.TimeToExpiration",
base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromSeconds(entry->expiry_time_secs())) -
base::Time::Now(),
base::TimeDelta::FromHours(1), base::TimeDelta::FromDays(15), 50);
}
std::move(callback).Run(entry_key, std::move(loaded_hint));
}
} // namespace optimization_guide