blob: c30dc4493091b99fd2aaca3aab1071c3c680f3ef [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/shared_dictionary/shared_dictionary_manager_on_disk.h"
#include "base/containers/contains.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/token.h"
#include "base/unguessable_token.h"
#include "net/base/net_errors.h"
#include "net/disk_cache/disk_cache.h"
#include "services/network/shared_dictionary/shared_dictionary_storage_on_disk.h"
namespace network {
namespace {
absl::optional<base::UnguessableToken> DeserializeToUnguessableToken(
const std::string& token_string) {
absl::optional<base::Token> token = base::Token::FromString(token_string);
if (!token) {
return absl::nullopt;
}
return base::UnguessableToken::Deserialize(token->high(), token->low());
}
} // namespace
class SharedDictionaryManagerOnDisk::ClearDataTask
: public SharedDictionaryManagerOnDisk::SerializedTask {
public:
ClearDataTask(raw_ptr<SharedDictionaryManagerOnDisk> manager,
base::Time start_time,
base::Time end_time,
base::RepeatingCallback<bool(const GURL&)> url_matcher,
base::OnceClosure callback)
: manager_(manager),
start_time_(start_time),
end_time_(end_time),
url_matcher_(std::move(url_matcher)),
callback_(std::move(callback)) {}
~ClearDataTask() override = default;
ClearDataTask(const ClearDataTask&) = delete;
ClearDataTask& operator=(const ClearDataTask&) = delete;
void Start() override {
manager_->metadata_store().ClearDictionaries(
start_time_, end_time_, std::move(url_matcher_),
base::BindOnce(&ClearDataTask::OnClearDictionariesFinished,
weak_factory_.GetWeakPtr()));
}
private:
void OnClearDictionariesFinished(
net::SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError
result) {
if (result.has_value()) {
manager_->OnDictionaryDeleted(result.value(),
/*need_to_doom_disk_cache_entries=*/true);
}
std::move(callback_).Run();
manager_->OnFinishSerializedTask();
}
raw_ptr<SharedDictionaryManagerOnDisk> manager_;
const base::Time start_time_;
const base::Time end_time_;
base::RepeatingCallback<bool(const GURL&)> url_matcher_;
base::OnceClosure callback_;
base::WeakPtrFactory<ClearDataTask> weak_factory_{this};
};
class SharedDictionaryManagerOnDisk::ClearDataTaskInfo
: public SharedDictionaryManagerOnDisk::SerializedTaskInfo {
public:
ClearDataTaskInfo(base::Time start_time,
base::Time end_time,
base::RepeatingCallback<bool(const GURL&)> url_matcher,
base::OnceClosure callback)
: start_time_(start_time),
end_time_(end_time),
url_matcher_(std::move(url_matcher)),
callback_(std::move(callback)) {}
~ClearDataTaskInfo() override = default;
ClearDataTaskInfo(const ClearDataTaskInfo&) = delete;
ClearDataTaskInfo& operator=(const ClearDataTaskInfo&) = delete;
std::unique_ptr<SerializedTask> CreateTask(
SharedDictionaryManagerOnDisk* manager) override {
return std::make_unique<ClearDataTask>(manager, start_time_, end_time_,
std::move(url_matcher_),
std::move(callback_));
}
private:
const base::Time start_time_;
const base::Time end_time_;
base::RepeatingCallback<bool(const GURL&)> url_matcher_;
base::OnceClosure callback_;
};
class SharedDictionaryManagerOnDisk::MismatchingEntryDeletionTask
: public SharedDictionaryManagerOnDisk::SerializedTask {
public:
explicit MismatchingEntryDeletionTask(
raw_ptr<SharedDictionaryManagerOnDisk> manager)
: manager_(manager) {}
~MismatchingEntryDeletionTask() override = default;
MismatchingEntryDeletionTask(const MismatchingEntryDeletionTask&) = delete;
MismatchingEntryDeletionTask& operator=(const MismatchingEntryDeletionTask&) =
delete;
void Start() override {
// 1) Get the disk cache key tokens currently being written by the manager.
writing_disk_cache_key_tokens_ = manager_->writing_disk_cache_key_tokens();
// 2) Get the all disk cache key tokens in the metadata store.
manager_->metadata_store().GetAllDiskCacheKeyTokens(BindOnce(
&MismatchingEntryDeletionTask::OnAllDiskCacheKeyTokensInDatabase,
weak_factory_.GetWeakPtr()));
}
private:
void OnAllDiskCacheKeyTokensInDatabase(
net::SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError
result) {
if (!result.has_value()) {
manager_->OnFinishSerializedTask();
return;
}
disk_cache_key_tokens_ = std::move(result.value());
// 3) Get the iterator of the disk cache.
manager_->disk_cache().CreateIterator(
BindOnce(&MismatchingEntryDeletionTask::OnDiskCacheIterator,
weak_factory_.GetWeakPtr()));
}
void OnDiskCacheIterator(
std::unique_ptr<disk_cache::Backend::Iterator> disk_cache_iterator) {
if (!disk_cache_iterator) {
manager_->OnFinishSerializedTask();
return;
}
disk_cache_iterator_ = std::move(disk_cache_iterator);
OpenNextEntry();
}
void OpenNextEntry() {
auto split_callback = base::SplitOnceCallback(
base::BindOnce(&MismatchingEntryDeletionTask::OnDiskCacheEntry,
weak_factory_.GetWeakPtr()));
// 4) For each disk cache entry, opens the entry.
disk_cache::EntryResult result =
disk_cache_iterator_->OpenNextEntry(std::move(split_callback.first));
if (result.net_error() != net::ERR_IO_PENDING) {
std::move(split_callback.second).Run(std::move(result));
}
}
void OnDiskCacheEntry(disk_cache::EntryResult result) {
if (result.net_error() == net::ERR_FAILED) {
// 8) The iteration is complete.
CleanupDatabase();
return;
}
if (result.net_error() < 0) {
manager_->OnFinishSerializedTask();
return;
}
disk_cache::ScopedEntryPtr entry(result.ReleaseEntry());
// 5) Get the disk cache key token of the entry.
absl::optional<base::UnguessableToken> token =
DeserializeToUnguessableToken(entry->GetKey());
if (!token) {
// 6) If the disk cache entry key is not a valid token, deletes the entry.
entry->Doom();
++invalid_disk_cache_entry_count_;
} else if (disk_cache_key_tokens_.erase(*token) != 1) {
if (!base::Contains(writing_disk_cache_key_tokens_, *token)) {
// 7) If the disk cache key token is not in the metadata, and is not in
// the set of tokens currently being written by the manager, deletes
// the entry.
entry->Doom();
++metadata_missing_dictionary_count_;
}
}
OpenNextEntry();
}
void CleanupDatabase() {
base::UmaHistogramCounts100(
"Net.SharedDictionaryManagerOnDisk.InvalidDiskCacheEntryCount",
invalid_disk_cache_entry_count_);
base::UmaHistogramCounts100(
"Net.SharedDictionaryManagerOnDisk.MetadataMissingDictionaryCount",
metadata_missing_dictionary_count_);
base::UmaHistogramCounts100(
"Net.SharedDictionaryManagerOnDisk."
"DiskCacheEntryMissingDictionaryCount",
disk_cache_key_tokens_.size());
if (disk_cache_key_tokens_.empty()) {
manager_->OnFinishSerializedTask();
return;
}
// 9) `disk_cache_key_tokens_` contains the tokens which were in the
// metadata store, but not in the disk cache.
// 10) Let the manager know such dictionaries are unavailable.
manager_->OnDictionaryDeleted(disk_cache_key_tokens_,
/*need_to_doom_disk_cache_entries=*/false);
// 11) Deletes such dictionaries from the metadata store.
manager_->metadata_store().DeleteDictionariesByDiskCacheKeyTokens(
std::move(disk_cache_key_tokens_),
base::BindOnce(&MismatchingEntryDeletionTask::CleanupDatabaseDone,
weak_factory_.GetWeakPtr()));
}
void CleanupDatabaseDone(
net::SQLitePersistentSharedDictionaryStore::Error error) {
manager_->OnFinishSerializedTask();
}
raw_ptr<SharedDictionaryManagerOnDisk> manager_;
base::OnceCallback<void(int)> callback_;
std::set<base::UnguessableToken> disk_cache_key_tokens_;
std::set<base::UnguessableToken> writing_disk_cache_key_tokens_;
std::unique_ptr<disk_cache::Backend::Iterator> disk_cache_iterator_;
uint32_t invalid_disk_cache_entry_count_ = 0;
uint32_t metadata_missing_dictionary_count_ = 0;
base::WeakPtrFactory<MismatchingEntryDeletionTask> weak_factory_{this};
};
class SharedDictionaryManagerOnDisk::MismatchingEntryDeletionTaskInfo
: public SharedDictionaryManagerOnDisk::SerializedTaskInfo {
public:
MismatchingEntryDeletionTaskInfo() = default;
~MismatchingEntryDeletionTaskInfo() override = default;
MismatchingEntryDeletionTaskInfo(const MismatchingEntryDeletionTaskInfo&) =
delete;
MismatchingEntryDeletionTaskInfo& operator=(
const MismatchingEntryDeletionTaskInfo&) = delete;
std::unique_ptr<SerializedTask> CreateTask(
SharedDictionaryManagerOnDisk* manager) override {
return std::make_unique<MismatchingEntryDeletionTask>(manager);
}
};
class SharedDictionaryManagerOnDisk::CacheEvictionTask
: public SharedDictionaryManagerOnDisk::SerializedTask {
public:
CacheEvictionTask(raw_ptr<SharedDictionaryManagerOnDisk> manager,
uint64_t cache_max_size)
: manager_(manager), cache_max_size_(cache_max_size) {}
~CacheEvictionTask() override = default;
CacheEvictionTask(const CacheEvictionTask&) = delete;
CacheEvictionTask& operator=(const CacheEvictionTask&) = delete;
void Start() override {
manager_->metadata_store().ProcessEviction(
cache_max_size_, cache_max_size_ * 0.9,
base::BindOnce(&CacheEvictionTask::OnProcessEvictionFinished,
weak_factory_.GetWeakPtr()));
}
private:
void OnProcessEvictionFinished(
net::SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError
result) {
if (result.has_value()) {
manager_->OnDictionaryDeleted(result.value(),
/*need_to_doom_disk_cache_entries=*/true);
}
manager_->OnFinishSerializedTask();
}
raw_ptr<SharedDictionaryManagerOnDisk> manager_;
const uint64_t cache_max_size_;
base::WeakPtrFactory<CacheEvictionTask> weak_factory_{this};
};
class SharedDictionaryManagerOnDisk::CacheEvictionTaskInfo
: public SharedDictionaryManagerOnDisk::SerializedTaskInfo {
public:
explicit CacheEvictionTaskInfo(base::OnceClosure task_created_callback)
: task_created_callback_(std::move(task_created_callback)) {}
~CacheEvictionTaskInfo() override = default;
CacheEvictionTaskInfo(const CacheEvictionTaskInfo&) = delete;
CacheEvictionTaskInfo& operator=(const CacheEvictionTaskInfo&) = delete;
std::unique_ptr<SerializedTask> CreateTask(
SharedDictionaryManagerOnDisk* manager) override {
std::move(task_created_callback_).Run();
return std::make_unique<CacheEvictionTask>(manager,
manager->cache_max_size());
}
private:
base::OnceClosure task_created_callback_;
};
class SharedDictionaryManagerOnDisk::ExpiredDictionaryDeletionTask
: public SharedDictionaryManagerOnDisk::SerializedTask {
public:
explicit ExpiredDictionaryDeletionTask(
raw_ptr<SharedDictionaryManagerOnDisk> manager)
: manager_(manager) {}
~ExpiredDictionaryDeletionTask() override = default;
ExpiredDictionaryDeletionTask(const ExpiredDictionaryDeletionTask&) = delete;
ExpiredDictionaryDeletionTask& operator=(
const ExpiredDictionaryDeletionTask&) = delete;
void Start() override {
manager_->metadata_store().DeleteExpiredDictionaries(
base::Time::Now(),
base::BindOnce(
&ExpiredDictionaryDeletionTask::OnDeleteExpiredDictionariesFinished,
weak_factory_.GetWeakPtr()));
}
private:
void OnDeleteExpiredDictionariesFinished(
net::SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError
result) {
if (result.has_value()) {
manager_->OnDictionaryDeleted(result.value(),
/*need_to_doom_disk_cache_entries=*/true);
}
manager_->OnFinishSerializedTask();
}
raw_ptr<SharedDictionaryManagerOnDisk> manager_;
base::WeakPtrFactory<ExpiredDictionaryDeletionTask> weak_factory_{this};
};
class SharedDictionaryManagerOnDisk::ExpiredDictionaryDeletionTaskInfo
: public SharedDictionaryManagerOnDisk::SerializedTaskInfo {
public:
explicit ExpiredDictionaryDeletionTaskInfo(
base::OnceClosure task_created_callback)
: task_created_callback_(std::move(task_created_callback)) {}
~ExpiredDictionaryDeletionTaskInfo() override = default;
ExpiredDictionaryDeletionTaskInfo(const ExpiredDictionaryDeletionTaskInfo&) =
delete;
ExpiredDictionaryDeletionTaskInfo& operator=(
const ExpiredDictionaryDeletionTaskInfo&) = delete;
std::unique_ptr<SerializedTask> CreateTask(
SharedDictionaryManagerOnDisk* manager) override {
std::move(task_created_callback_).Run();
return std::make_unique<ExpiredDictionaryDeletionTask>(manager);
}
private:
base::OnceClosure task_created_callback_;
};
SharedDictionaryManagerOnDisk::SharedDictionaryManagerOnDisk(
const base::FilePath& database_path,
const base::FilePath& cache_directory_path,
uint64_t cache_max_size,
#if BUILDFLAG(IS_ANDROID)
base::android::ApplicationStatusListener* app_status_listener,
#endif // BUILDFLAG(IS_ANDROID)
scoped_refptr<disk_cache::BackendFileOperationsFactory>
file_operations_factory)
: cache_max_size_(cache_max_size),
metadata_store_(database_path,
/*client_task_runner=*/
base::SingleThreadTaskRunner::GetCurrentDefault(),
/*background_task_runner=*/
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_BLOCKING,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN})) {
disk_cache_.Initialize(cache_directory_path,
#if BUILDFLAG(IS_ANDROID)
app_status_listener,
#endif // BUILDFLAG(IS_ANDROID)
std::move(file_operations_factory));
MaybePostExpiredDictionaryDeletionTask();
MaybePostCacheEvictionTask();
}
SharedDictionaryManagerOnDisk::~SharedDictionaryManagerOnDisk() = default;
scoped_refptr<SharedDictionaryStorage>
SharedDictionaryManagerOnDisk::CreateStorage(
const net::SharedDictionaryStorageIsolationKey& isolation_key) {
return base::MakeRefCounted<SharedDictionaryStorageOnDisk>(
weak_factory_.GetWeakPtr(), isolation_key,
base::ScopedClosureRunner(
base::BindOnce(&SharedDictionaryManager::OnStorageDeleted,
GetWeakPtr(), isolation_key)));
}
void SharedDictionaryManagerOnDisk::SetCacheMaxSize(uint64_t cache_max_size) {
cache_max_size_ = cache_max_size;
MaybePostExpiredDictionaryDeletionTask();
MaybePostCacheEvictionTask();
}
scoped_refptr<SharedDictionaryWriter>
SharedDictionaryManagerOnDisk::CreateWriter(
const net::SharedDictionaryStorageIsolationKey& isolation_key,
const GURL& url,
base::Time response_time,
base::TimeDelta expiration,
const std::string& match,
base::OnceCallback<void(net::SharedDictionaryInfo)> callback) {
const base::UnguessableToken disk_cache_key_token =
base::UnguessableToken::Create();
CHECK(writing_disk_cache_key_tokens_.insert(disk_cache_key_token).second);
auto writer = base::MakeRefCounted<SharedDictionaryWriterOnDisk>(
disk_cache_key_token,
base::BindOnce(
&SharedDictionaryManagerOnDisk::OnDictionaryWrittenInDiskCache,
weak_factory_.GetWeakPtr(), isolation_key, url, response_time,
expiration, match, disk_cache_key_token, std::move(callback)),
disk_cache_.GetWeakPtr());
writer->Initialize();
return writer;
}
void SharedDictionaryManagerOnDisk::OnDictionaryWrittenInDiskCache(
const net::SharedDictionaryStorageIsolationKey& isolation_key,
const GURL& url,
base::Time response_time,
base::TimeDelta expiration,
const std::string& match,
const base::UnguessableToken& disk_cache_key_token,
base::OnceCallback<void(net::SharedDictionaryInfo)> callback,
SharedDictionaryWriterOnDisk::Result result,
size_t size,
const net::SHA256HashValue& hash) {
if (result != SharedDictionaryWriterOnDisk::Result::kSuccess) {
CHECK(writing_disk_cache_key_tokens_.erase(disk_cache_key_token) == 1);
return;
}
base::Time last_used_time = base::Time::Now();
net::SharedDictionaryInfo info(url, response_time, expiration, match,
last_used_time, size, hash,
disk_cache_key_token,
/*primary_key_in_database=*/absl::nullopt);
metadata_store_.RegisterDictionary(
isolation_key, info,
base::BindOnce(
&SharedDictionaryManagerOnDisk::OnDictionaryWrittenInDatabase,
weak_factory_.GetWeakPtr(), info, std::move(callback)));
}
void SharedDictionaryManagerOnDisk::OnDictionaryWrittenInDatabase(
net::SharedDictionaryInfo info,
base::OnceCallback<void(net::SharedDictionaryInfo)> callback,
net::SQLitePersistentSharedDictionaryStore::RegisterDictionaryResultOrError
result) {
CHECK(writing_disk_cache_key_tokens_.erase(info.disk_cache_key_token()) == 1);
if (!result.has_value()) {
disk_cache_.DoomEntry(info.disk_cache_key_token().ToString(),
base::DoNothing());
return;
}
base::UmaHistogramCustomCounts(
"Net.SharedDictionaryManagerOnDisk.DictionarySize", info.size(), 1,
100000000, 50);
base::UmaHistogramCustomCounts(
"Net.SharedDictionaryManagerOnDisk.TotalDictionarySizeWhenAdded",
result.value().total_dictionary_size, 1, 200000000, 50);
info.set_primary_key_in_database(result.value().primary_key_in_database);
if (result.value().disk_cache_key_token_to_be_removed) {
disk_cache_.DoomEntry(
result.value().disk_cache_key_token_to_be_removed->ToString(),
base::DoNothing());
}
std::move(callback).Run(std::move(info));
MaybePostExpiredDictionaryDeletionTask();
if (cache_max_size_ > 0 &&
result.value().total_dictionary_size > cache_max_size_) {
MaybePostCacheEvictionTask();
}
}
void SharedDictionaryManagerOnDisk::UpdateDictionaryLastUsedTime(
net::SharedDictionaryInfo& info) {
info.set_last_used_time(base::Time::Now());
CHECK(info.primary_key_in_database());
metadata_store_.UpdateDictionaryLastUsedTime(*info.primary_key_in_database(),
info.last_used_time());
}
void SharedDictionaryManagerOnDisk::ClearData(
base::Time start_time,
base::Time end_time,
base::RepeatingCallback<bool(const GURL&)> url_matcher,
base::OnceClosure callback) {
PostSerializedTask(std::make_unique<ClearDataTaskInfo>(
start_time, end_time, std::move(url_matcher), std::move(callback)));
MaybePostExpiredDictionaryDeletionTask();
MaybePostCacheEvictionTask();
MaybePostMismatchingEntryDeletionTask();
}
void SharedDictionaryManagerOnDisk::PostSerializedTask(
std::unique_ptr<SerializedTaskInfo> task_info) {
pending_serialized_task_info_.push_back(std::move(task_info));
MaybeStartSerializedTask();
}
void SharedDictionaryManagerOnDisk::OnFinishSerializedTask() {
CHECK(running_serialized_task_);
running_serialized_task_.reset();
MaybeStartSerializedTask();
}
void SharedDictionaryManagerOnDisk::MaybeStartSerializedTask() {
if (running_serialized_task_ || pending_serialized_task_info_.empty()) {
return;
}
std::unique_ptr<SerializedTaskInfo> serialized_task_info =
std::move(*pending_serialized_task_info_.begin());
pending_serialized_task_info_.pop_front();
running_serialized_task_ = serialized_task_info->CreateTask(this);
running_serialized_task_->Start();
}
void SharedDictionaryManagerOnDisk::OnDictionaryDeleted(
const std::set<base::UnguessableToken>& disk_cache_key_tokens,
bool need_to_doom_disk_cache_entries) {
if (need_to_doom_disk_cache_entries) {
for (const base::UnguessableToken& token : disk_cache_key_tokens) {
disk_cache().DoomEntry(token.ToString(), base::DoNothing());
}
}
for (auto& it : storages()) {
reinterpret_cast<SharedDictionaryStorageOnDisk*>(it.second.get())
->OnDictionaryDeleted(disk_cache_key_tokens);
}
}
void SharedDictionaryManagerOnDisk::MaybePostMismatchingEntryDeletionTask() {
// MismatchingEntryDeletionTask is intended to resolve the mismatch between
// the disk cache and metadata database. We run MismatchingEntryDeletionTask
// when ClearData() is called for the first time.
if (mismatching_entry_deletion_task_posted_) {
return;
}
mismatching_entry_deletion_task_posted_ = true;
PostSerializedTask(std::make_unique<MismatchingEntryDeletionTaskInfo>());
}
void SharedDictionaryManagerOnDisk::MaybePostCacheEvictionTask() {
if (cache_eviction_task_queued_ || cache_max_size_ == 0) {
return;
}
cache_eviction_task_queued_ = true;
PostSerializedTask(std::make_unique<CacheEvictionTaskInfo>(base::BindOnce(
[](base::WeakPtr<SharedDictionaryManagerOnDisk> weak_ptr) {
if (weak_ptr) {
CHECK(weak_ptr->cache_eviction_task_queued_);
weak_ptr->cache_eviction_task_queued_ = false;
}
},
weak_factory_.GetWeakPtr())));
}
void SharedDictionaryManagerOnDisk::MaybePostExpiredDictionaryDeletionTask() {
if (expired_entry_deletion_task_queued_) {
return;
}
expired_entry_deletion_task_queued_ = true;
PostSerializedTask(
std::make_unique<ExpiredDictionaryDeletionTaskInfo>(base::BindOnce(
[](base::WeakPtr<SharedDictionaryManagerOnDisk> weak_ptr) {
if (weak_ptr) {
CHECK(weak_ptr->expired_entry_deletion_task_queued_);
weak_ptr->expired_entry_deletion_task_queued_ = false;
}
},
weak_factory_.GetWeakPtr())));
}
} // namespace network