blob: 2da9a087e4c4d8207d2581f264c77f6f919c0086 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/disk_cache/sql/sql_shared_cache_manager.h"
#include <utility>
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/task/thread_pool.h"
#include "net/disk_cache/backend_cleanup_tracker.h"
#include "net/disk_cache/sql/shared_cache_client_remote.h"
#include "net/disk_cache/sql/sql_persistent_store.h"
namespace disk_cache {
SqlSharedCacheManager::SqlSharedCacheManager(
SqlPersistentStore& store,
const base::FilePath& path,
scoped_refptr<SqlReadCacheMemoryMonitor> read_cache_memory_monitor,
scoped_refptr<BackendCleanupTracker> cleanup_tracker)
: store_(store),
directory_(path),
db_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_BLOCKING,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN})),
index_database_(db_task_runner_, store_->GetAsyncTaskManager(), path),
read_cache_memory_monitor_(std::move(read_cache_memory_monitor)),
cleanup_tracker_(cleanup_tracker) {}
SqlSharedCacheManager::~SqlSharedCacheManager() {
index_database_.Reset();
if (cleanup_tracker_) {
CHECK(db_task_runner_);
db_task_runner_->PostTaskAndReply(
FROM_HERE, base::DoNothing(),
base::DoNothingWithBoundArgs(std::move(cleanup_tracker_)));
}
}
void SqlSharedCacheManager::Init(InitCallback callback) {
index_database_.AsyncCall(&SqlSharedCacheIndexDatabase::Initialize)
.Then(std::move(callback).Then(
base::BindOnce(&SqlSharedCacheManager::FinishDbOperation,
weak_factory_.GetWeakPtr())));
}
void SqlSharedCacheManager::PostDbOperation(
base::OnceCallback<void(DbOperationHandle)> operation) {
pending_db_operations_.push(std::move(operation));
MaybeRunNextDbOperation();
}
void SqlSharedCacheManager::MaybeRunNextDbOperation() {
if (db_operation_in_progress_ || pending_db_operations_.empty()) {
return;
}
db_operation_in_progress_ = true;
auto op = std::move(pending_db_operations_.front());
pending_db_operations_.pop();
DbOperationHandle db_operation_handle(base::BindOnce(
&SqlSharedCacheManager::FinishDbOperation, weak_factory_.GetWeakPtr()));
std::move(op).Run(std::move(db_operation_handle));
}
void SqlSharedCacheManager::FinishDbOperation() {
CHECK(db_operation_in_progress_);
db_operation_in_progress_ = false;
MaybeRunNextDbOperation();
}
void SqlSharedCacheManager::GetCacheByNik(
const net::NetworkIsolationKey& nik,
bool require_shared_cache_db_id,
base::OnceCallback<void(scoped_refptr<SqlSharedCacheHandle>)> callback) {
CHECK(!nik.IsTransient());
DoGetCacheByNik(nik, require_shared_cache_db_id, std::move(callback),
DbOperationHandle());
}
void SqlSharedCacheManager::DoGetCacheByNik(
net::NetworkIsolationKey nik,
bool require_shared_cache_db_id,
base::OnceCallback<void(scoped_refptr<SqlSharedCacheHandle>)> callback,
DbOperationHandle db_operation_handle) {
if (auto it = shared_caches_by_nik_string_.find(*nik.ToCacheKeyString());
it != shared_caches_by_nik_string_.end()) {
if (!require_shared_cache_db_id || it->second->shared_cache_db_id()) {
std::move(callback).Run(it->second->CreateHandle());
return;
}
}
if (!db_operation_handle) {
PostDbOperation(base::BindOnce(
&SqlSharedCacheManager::DoGetCacheByNik, weak_factory_.GetWeakPtr(),
nik, require_shared_cache_db_id, std::move(callback)));
return;
}
index_database_
.AsyncCall(&SqlSharedCacheIndexDatabase::GetDbIdByNetworkIsolationKey)
.WithArgs(nik, require_shared_cache_db_id)
.Then(base::BindOnce(&SqlSharedCacheManager::OnGetSharedDbIdForNik,
weak_factory_.GetWeakPtr(), nik, std::move(callback),
std::move(db_operation_handle)));
}
void SqlSharedCacheManager::OnGetSharedDbIdForNik(
net::NetworkIsolationKey nik,
base::OnceCallback<void(scoped_refptr<SqlSharedCacheHandle>)> callback,
DbOperationHandle db_operation_handle,
base::expected<SqlSharedCacheDbId, SqlSharedCacheIndexDatabase::Error>
result) {
std::optional<SqlSharedCacheDbId> shared_cache_db_id;
if (result.has_value()) {
shared_cache_db_id = result.value();
} else if (result.error() != SqlSharedCacheIndexDatabase::Error::kNotFound) {
std::move(callback).Run(scoped_refptr<SqlSharedCacheHandle>());
return;
}
const auto nik_str = *nik.ToCacheKeyString();
if (auto it = shared_caches_by_nik_string_.find(nik_str);
it != shared_caches_by_nik_string_.end()) {
auto& cache_ptr = it->second;
if (shared_cache_db_id && !cache_ptr->shared_cache_db_id()) {
CHECK(shared_caches_by_shared_cache_db_id_
.emplace(*shared_cache_db_id, cache_ptr)
.second);
cache_ptr->InitIsolatedDatabase(
*shared_cache_db_id,
base::BindOnce(
[](DbOperationHandle db_operation_handle, bool result) {},
std::move(db_operation_handle)));
}
std::move(callback).Run(cache_ptr->CreateHandle());
return;
}
std::move(callback).Run(RegisterNewSqlSharedCache(
nik_str, shared_cache_db_id, std::move(db_operation_handle)));
}
scoped_refptr<SqlSharedCacheHandle>
SqlSharedCacheManager::RegisterNewSqlSharedCache(
const std::string& nik_str,
std::optional<SqlSharedCacheDbId> shared_cache_db_id,
DbOperationHandle db_operation_handle) {
scoped_refptr<base::SequencedTaskRunner> db_task_runner =
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_BLOCKING,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
auto cache = std::make_unique<SqlSharedCache>(
nik_str, *store_, directory_,
base::BindRepeating(&SqlSharedCacheManager::OnSqlSharedCacheUnreferenced,
weak_factory_.GetWeakPtr()),
db_task_runner, read_cache_memory_monitor_, cleanup_tracker_);
SqlSharedCache* cache_ptr = cache.get();
shared_caches_.insert(std::move(cache));
CHECK(shared_caches_by_nik_string_.emplace(nik_str, cache_ptr).second);
if (shared_cache_db_id) {
CHECK(shared_caches_by_shared_cache_db_id_
.emplace(*shared_cache_db_id, cache_ptr)
.second);
cache_ptr->InitIsolatedDatabase(
*shared_cache_db_id,
base::BindOnce(
[](DbOperationHandle db_operation_handle, bool result) {},
std::move(db_operation_handle)));
}
return cache_ptr->CreateHandle();
}
void SqlSharedCacheManager::GetCacheByDbId(
SqlSharedCacheDbId shared_cache_db_id,
base::OnceCallback<void(scoped_refptr<SqlSharedCacheHandle>)> callback) {
DoGetCacheByDbId(shared_cache_db_id, std::move(callback),
DbOperationHandle());
}
void SqlSharedCacheManager::DoGetCacheByDbId(
SqlSharedCacheDbId shared_cache_db_id,
base::OnceCallback<void(scoped_refptr<SqlSharedCacheHandle>)> callback,
DbOperationHandle db_operation_handle) {
if (auto it = shared_caches_by_shared_cache_db_id_.find(shared_cache_db_id);
it != shared_caches_by_shared_cache_db_id_.end()) {
std::move(callback).Run(it->second->CreateHandle());
return;
}
if (!db_operation_handle) {
PostDbOperation(base::BindOnce(&SqlSharedCacheManager::DoGetCacheByDbId,
weak_factory_.GetWeakPtr(),
shared_cache_db_id, std::move(callback)));
return;
}
index_database_
.AsyncCall(&SqlSharedCacheIndexDatabase::GetIsolationKeyStringByDbId)
.WithArgs(shared_cache_db_id)
.Then(base::BindOnce(&SqlSharedCacheManager::OnGetNikStringForDbId,
weak_factory_.GetWeakPtr(), shared_cache_db_id,
std::move(callback),
std::move(db_operation_handle)));
}
void SqlSharedCacheManager::OnGetNikStringForDbId(
SqlSharedCacheDbId shared_cache_db_id,
base::OnceCallback<void(scoped_refptr<SqlSharedCacheHandle>)> callback,
DbOperationHandle db_operation_handle,
base::expected<std::string, SqlSharedCacheIndexDatabase::Error> result) {
if (!result.has_value()) {
std::move(callback).Run(scoped_refptr<SqlSharedCacheHandle>());
return;
}
std::move(callback).Run(RegisterNewSqlSharedCache(
result.value(), shared_cache_db_id, std::move(db_operation_handle)));
}
void SqlSharedCacheManager::DeleteResources(
std::vector<SqlSharedCacheResourceId> resources,
base::OnceClosure callback) {
absl::flat_hash_map<SqlSharedCacheDbId, std::vector<SqlSharedCacheRowId>>
grouped_resources;
for (const auto& resource : resources) {
grouped_resources[resource.db_id].push_back(resource.row_id);
}
PostDbOperation(
base::BindOnce(&SqlSharedCacheManager::DeleteNextResourceGroup,
weak_factory_.GetWeakPtr(), std::move(grouped_resources),
std::move(callback)));
}
void SqlSharedCacheManager::DeleteNextResourceGroup(
absl::flat_hash_map<SqlSharedCacheDbId, std::vector<SqlSharedCacheRowId>>
grouped_resources,
base::OnceClosure callback,
DbOperationHandle db_operation_handle) {
if (grouped_resources.empty()) {
if (callback) {
std::move(callback).Run();
}
return;
}
auto it = grouped_resources.begin();
const SqlSharedCacheDbId db_id = it->first;
std::vector<SqlSharedCacheRowId> row_ids = std::move(it->second);
grouped_resources.erase(it);
auto next_task =
base::BindOnce(&SqlSharedCacheManager::DeleteNextResourceGroup,
weak_factory_.GetWeakPtr(), std::move(grouped_resources),
std::move(callback), std::move(db_operation_handle));
DoGetCacheByDbId(
db_id,
base::BindOnce(
[](std::vector<SqlSharedCacheRowId> row_ids,
base::OnceClosure next_task,
scoped_refptr<SqlSharedCacheHandle> handle) {
if (handle && *handle) {
auto* cache = handle->get();
cache->DeleteEntries(
row_ids,
base::BindOnce(
[](base::OnceClosure next_task,
base::expected<void,
SqlSharedCacheIsolatedDatabase::Error>
result) { std::move(next_task).Run(); },
std::move(next_task)));
} else {
std::move(next_task).Run();
}
},
std::move(row_ids), std::move(next_task)),
DbOperationHandle(base::DoNothing()));
}
void SqlSharedCacheManager::OnSqlSharedCacheUnreferenced(
SqlSharedCache& cache) {
PostDbOperation(
base::BindOnce(&SqlSharedCacheManager::DoDeleteUnreferencedSqlSharedCache,
weak_factory_.GetWeakPtr(), cache.nik_string()));
}
void SqlSharedCacheManager::DoDeleteUnreferencedSqlSharedCache(
const std::string& nik_string,
DbOperationHandle db_operation_handle) {
auto it = shared_caches_by_nik_string_.find(nik_string);
if (it == shared_caches_by_nik_string_.end()) {
return;
}
SqlSharedCache* cache = it->second;
if (cache->IsReferenced()) {
return;
}
shared_caches_by_nik_string_.erase(it);
if (cache->shared_cache_db_id()) {
shared_caches_by_shared_cache_db_id_.erase(*cache->shared_cache_db_id());
}
auto cache_it = shared_caches_.find(cache);
CHECK(cache_it != shared_caches_.end());
auto cache_to_delete =
std::move(const_cast<std::unique_ptr<SqlSharedCache>&>(*cache_it));
shared_caches_.erase(cache_it);
cache_to_delete->Cleanup(
base::DoNothingWithBoundArgs(std::move(db_operation_handle)));
}
void SqlSharedCacheManager::ProcessSharedCacheEligibleEntries(
std::map<net::NetworkIsolationKey,
base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> entries,
scoped_refptr<base::RefCountedData<std::atomic_bool>> abort_flag,
base::OnceCallback<void(
std::vector<SqlPersistentStore::SharedCacheEligibleEntry>)> callback,
base::RepeatingCallback<void(const CacheEntryKey&)>
on_entry_copied_callback) {
CHECK(abort_flag);
PostDbOperation(base::BindOnce(
&SqlSharedCacheManager::DoProcessSharedCacheEligibleEntries,
weak_factory_.GetWeakPtr(), std::move(entries), abort_flag,
std::move(callback), std::move(on_entry_copied_callback)));
}
void SqlSharedCacheManager::DoProcessSharedCacheEligibleEntries(
std::map<net::NetworkIsolationKey,
base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> entries,
scoped_refptr<base::RefCountedData<std::atomic_bool>> abort_flag,
base::OnceCallback<void(
std::vector<SqlPersistentStore::SharedCacheEligibleEntry>)> callback,
base::RepeatingCallback<void(const CacheEntryKey&)>
on_entry_copied_callback,
DbOperationHandle db_operation_handle) {
base::queue<base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> groups;
for (auto& [nik, entry_queue] : entries) {
groups.push(std::move(entry_queue));
}
ProcessNextNikGroup(
std::move(groups), abort_flag, {},
std::move(callback).Then(base::OnceClosure(
base::DoNothingWithBoundArgs(std::move(db_operation_handle)))),
std::move(on_entry_copied_callback));
}
void SqlSharedCacheManager::ProcessNextNikGroup(
base::queue<base::queue<SqlPersistentStore::SharedCacheEligibleEntry>>
groups,
scoped_refptr<base::RefCountedData<std::atomic_bool>> abort_flag,
std::vector<SqlPersistentStore::SharedCacheEligibleEntry> all_unprocessed,
base::OnceCallback<void(
std::vector<SqlPersistentStore::SharedCacheEligibleEntry>)> callback,
base::RepeatingCallback<void(const CacheEntryKey&)>
on_entry_copied_callback) {
CHECK(abort_flag);
if (groups.empty() || abort_flag->data.load(std::memory_order_relaxed)) {
while (!groups.empty()) {
auto& group = groups.front();
while (!group.empty()) {
all_unprocessed.push_back(std::move(group.front()));
group.pop();
}
groups.pop();
}
std::move(callback).Run(std::move(all_unprocessed));
return;
}
CHECK(!groups.front().empty());
const net::NetworkIsolationKey next_nik = groups.front().front().nik;
DoGetCacheByNik(
next_nik, /*require_shared_cache_db_id=*/true,
base::BindOnce(&SqlSharedCacheManager::OnGetSharedCacheForProcess,
weak_factory_.GetWeakPtr(), next_nik, std::move(groups),
std::move(abort_flag), std::move(all_unprocessed),
std::move(callback), std::move(on_entry_copied_callback)),
DbOperationHandle(base::DoNothing()));
}
void SqlSharedCacheManager::OnGetSharedCacheForProcess(
net::NetworkIsolationKey current_nik,
base::queue<base::queue<SqlPersistentStore::SharedCacheEligibleEntry>>
groups,
scoped_refptr<base::RefCountedData<std::atomic_bool>> abort_flag,
std::vector<SqlPersistentStore::SharedCacheEligibleEntry> all_unprocessed,
base::OnceCallback<void(
std::vector<SqlPersistentStore::SharedCacheEligibleEntry>)> callback,
base::RepeatingCallback<void(const CacheEntryKey&)>
on_entry_copied_callback,
scoped_refptr<SqlSharedCacheHandle> handle) {
// `groups` is a `base::queue` of entry queues (each inner queue contains
// entries sharing the same NetworkIsolationKey).
CHECK(!groups.empty());
CHECK(!groups.front().empty());
CHECK(groups.front().front().nik == current_nik);
auto entry_queue = std::move(groups.front());
groups.pop();
if (!handle) {
ProcessNextNikGroup(std::move(groups), abort_flag,
std::move(all_unprocessed), std::move(callback),
std::move(on_entry_copied_callback));
return;
}
(*handle)->CopyEntries(
std::move(entry_queue), abort_flag,
base::BindOnce(&SqlSharedCacheManager::OnProcessEntryCompleted,
weak_factory_.GetWeakPtr(), handle, std::move(groups),
abort_flag, std::move(all_unprocessed),
std::move(callback), on_entry_copied_callback),
std::move(on_entry_copied_callback));
}
void SqlSharedCacheManager::OnProcessEntryCompleted(
scoped_refptr<SqlSharedCacheHandle> handle,
base::queue<base::queue<SqlPersistentStore::SharedCacheEligibleEntry>>
groups,
scoped_refptr<base::RefCountedData<std::atomic_bool>> abort_flag,
std::vector<SqlPersistentStore::SharedCacheEligibleEntry> all_unprocessed,
base::OnceCallback<void(
std::vector<SqlPersistentStore::SharedCacheEligibleEntry>)> callback,
base::RepeatingCallback<void(const CacheEntryKey&)>
on_entry_copied_callback,
base::queue<SqlPersistentStore::SharedCacheEligibleEntry> results) {
while (!results.empty()) {
all_unprocessed.push_back(std::move(results.front()));
results.pop();
}
ProcessNextNikGroup(std::move(groups), abort_flag, std::move(all_unprocessed),
std::move(callback), std::move(on_entry_copied_callback));
}
void SqlSharedCacheManager::RegisterClient(
const net::NetworkIsolationKey& network_isolation_key,
std::unique_ptr<SharedCacheClientRemote> client) {
GetCacheByNik(network_isolation_key, /*require_shared_cache_db_id=*/false,
base::BindOnce(
[](std::unique_ptr<SharedCacheClientRemote> client,
scoped_refptr<SqlSharedCacheHandle> handle) {
if (handle) {
(*handle)->RegisterClient(std::move(client));
}
},
std::move(client)));
}
void SqlSharedCacheManager::SetSimulateDbFailureForTesting(bool fail) {
if (index_database_) {
index_database_
.AsyncCall(&SqlSharedCacheIndexDatabase::SetSimulateDbFailureForTesting)
.WithArgs(fail);
}
}
} // namespace disk_cache