blob: dd708e44cf2de170219e796ece5f72ed87eab7ee [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.h"
#include <algorithm>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/hash/hash.h"
#include "base/memory/scoped_refptr.h"
#include "base/no_destructor.h"
#include "base/state_transitions.h"
#include "base/task/sequenced_task_runner.h"
#include "net/base/features.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"
#include "net/disk_cache/sql/sql_shared_cache_handle.h"
#include "net/disk_cache/sql/sql_shared_cache_isolated_database.h"
#include "net/http/http_cache.h"
#include "net/http/http_response_info.h"
namespace disk_cache {
SqlSharedCache::SqlSharedCache(
std::string nik_string,
SqlPersistentStore& store,
const base::FilePath& directory,
base::RepeatingCallback<void(SqlSharedCache&)> on_unreferenced_callback,
scoped_refptr<base::SequencedTaskRunner> db_task_runner,
scoped_refptr<SqlReadCacheMemoryMonitor> read_cache_memory_monitor,
scoped_refptr<BackendCleanupTracker> cleanup_tracker)
: nik_string_(std::move(nik_string)),
store_(store),
directory_(directory),
on_unreferenced_callback_(std::move(on_unreferenced_callback)),
db_task_runner_(std::move(db_task_runner)),
read_cache_memory_monitor_(std::move(read_cache_memory_monitor)),
cleanup_tracker_(std::move(cleanup_tracker)) {}
SqlSharedCache::~SqlSharedCache() {
isolated_database_.Reset();
if (cleanup_tracker_) {
CHECK(db_task_runner_);
db_task_runner_->PostTaskAndReply(
FROM_HERE, base::DoNothing(),
base::DoNothingWithBoundArgs(std::move(cleanup_tracker_)));
}
}
void SqlSharedCache::Cleanup(base::OnceClosure callback) {
if (!isolated_database_) {
std::move(callback).Run();
return;
}
isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::Cleanup)
.Then(std::move(callback));
isolated_database_.Reset();
}
void SqlSharedCache::InitIsolatedDatabase(
SqlSharedCacheDbId shared_cache_db_id,
base::OnceCallback<void(bool)> callback) {
CHECK(!shared_cache_db_id_);
shared_cache_db_id_ = shared_cache_db_id;
isolated_database_ = SqlTrackedSequenceBound<SqlSharedCacheIsolatedDatabase>(
db_task_runner_, store_->GetAsyncTaskManager(), nik_string_, directory_,
shared_cache_db_id, db_task_runner_, read_cache_memory_monitor_);
isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::Init)
.Then(base::BindOnce(
[](base::OnceCallback<void(bool)> callback,
base::expected<void, SqlSharedCacheIsolatedDatabase::Error>
result) { std::move(callback).Run(result.has_value()); },
std::move(callback)));
isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::GetAllUrlHashes)
.Then(base::BindOnce(&SqlSharedCache::OnHashesLoaded,
weak_factory_.GetWeakPtr()));
for (ClientsMap::iterator it(&clients_); !it.IsAtEnd(); it.Advance()) {
isolated_database_
.AsyncCall(&SqlSharedCacheIsolatedDatabase::GetSharedReadOnlyConnection)
.Then(base::BindOnce(&SqlSharedCache::OnPendingFileSetForClient,
weak_factory_.GetWeakPtr(), it.GetCurrentKey()));
}
}
void SqlSharedCache::RegisterClient(
std::unique_ptr<SharedCacheClientRemote> client) {
CHECK(client);
auto* client_ptr = client.get();
auto client_id = clients_.Add(std::move(client));
client_ptr->SetDisconnectHandler(
base::BindOnce(&SqlSharedCache::OnClientDisconnected,
weak_factory_.GetWeakPtr(), client_id, CreateHandle()));
if (cached_hashes_.has_value()) {
std::vector<uint32_t> hashes(cached_hashes_->begin(),
cached_hashes_->end());
client_ptr->OnResourcesAdded(hashes);
}
if (isolated_database_) {
isolated_database_
.AsyncCall(&SqlSharedCacheIsolatedDatabase::GetSharedReadOnlyConnection)
.Then(base::BindOnce(&SqlSharedCache::OnPendingFileSetForClient,
weak_factory_.GetWeakPtr(), client_id));
}
}
void SqlSharedCache::OnPendingFileSetForClient(
ClientId client_id,
base::expected<sqlite_vfs::PendingFileSet,
SqlSharedCacheIsolatedDatabase::Error> pending_file_set) {
if (!pending_file_set.has_value()) {
return;
}
auto* client = clients_.Lookup(client_id);
if (!client) {
return;
}
client->Initialize(std::move(*pending_file_set));
}
void SqlSharedCache::OnHashesLoaded(
base::expected<std::vector<uint32_t>, SqlSharedCacheIsolatedDatabase::Error>
hashes) {
if (!cached_hashes_.has_value()) {
cached_hashes_ = absl::flat_hash_set<uint32_t>();
}
if (!hashes.has_value()) {
return;
}
for (auto hash : *hashes) {
cached_hashes_->insert(hash);
}
if (!hashes->empty()) {
for (ClientsMap::iterator it(&clients_); !it.IsAtEnd(); it.Advance()) {
it.GetCurrentValue()->OnResourcesAdded(*hashes);
}
}
}
void SqlSharedCache::OnClientDisconnected(
ClientId client_id,
scoped_refptr<SqlSharedCacheHandle> handle) {
clients_.Remove(client_id);
}
scoped_refptr<SqlSharedCacheHandle> SqlSharedCache::CreateHandle() {
return base::MakeRefCounted<SqlSharedCacheHandle>(
weak_factory_.GetWeakPtr(), base::PassKey<SqlSharedCache>());
}
void SqlSharedCache::IncrementHandleCount(base::PassKey<SqlSharedCacheHandle>) {
handle_count_++;
}
void SqlSharedCache::DecrementHandleCount(base::PassKey<SqlSharedCacheHandle>) {
handle_count_--;
if (!IsReferenced()) {
on_unreferenced_callback_.Run(*this);
}
}
void SqlSharedCache::CopyEntries(
base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries,
scoped_refptr<base::RefCountedData<std::atomic_bool>> abort_flag,
base::OnceCallback<void(
base::queue<SqlPersistentStore::SharedCacheEligibleEntry>)> callback,
base::RepeatingCallback<void(const CacheEntryKey&)>
on_entry_copied_callback) {
CHECK(pending_copy_entries_.empty());
CHECK(!copy_callback_);
CHECK(!current_copy_row_id_);
CHECK(!on_entry_copied_callback_);
CHECK(!entries.empty());
CHECK(shared_cache_db_id_);
CHECK(isolated_database_);
pending_copy_entries_ = std::move(entries);
copy_abort_flag_ = std::move(abort_flag);
copy_callback_ = std::move(callback);
on_entry_copied_callback_ = std::move(on_entry_copied_callback);
CopyNextEntry();
}
void SqlSharedCache::CopyNextEntry() {
if (pending_copy_entries_.empty() ||
(copy_abort_flag_ &&
copy_abort_flag_->data.load(std::memory_order_relaxed))) {
FinishCopy();
return;
}
auto entry = std::move(pending_copy_entries_.front());
pending_copy_entries_.pop();
current_entry_hash_ = base::PersistentHash(entry.url.spec());
const auto key = entry.key;
store_->OpenEntry(
key, base::BindOnce(&SqlSharedCache::OnEntryOpenedForSharedCache,
weak_factory_.GetWeakPtr(), std::move(entry)));
}
void SqlSharedCache::OnEntryOpenedForSharedCache(
SqlPersistentStore::SharedCacheEligibleEntry entry,
SqlPersistentStore::EntryInfoOrError result) {
if (!result.has_value() || !result->head) {
OnCopyEntryFailed();
return;
}
// If the entry already has a `shared_cache_resource_id`, its blob data in the
// persistent store has already been moved to the shared cache. Attempting to
// copy it again would read zero-filled body data and create an all-zero
// duplicate entry in the isolated database.
if (result->shared_cache_resource_id) {
OnCopyEntryFailed();
return;
}
auto info = std::move(*result);
if (info.body_end >
net::features::kSqlDiskCacheMaxSharedCacheCopyEntrySize.Get()) {
OnCopyEntryFailed();
return;
}
net::HttpResponseInfo disk_response_info;
bool response_truncated = false;
if (!net::HttpCache::ParseResponseInfo(base::as_bytes(info.head->span()),
&disk_response_info,
&response_truncated) ||
response_truncated ||
disk_response_info.response_time != entry.response_info->response_time) {
OnCopyEntryFailed();
return;
}
auto pickled_buffer = base::MakeRefCounted<net::PickledIOBuffer>(
entry.response_info->MakePickle(/*skip_transient_headers=*/true,
/*response_truncated=*/false));
if (info.body_end > 0) {
int64_t chunk_size = std::min(
static_cast<int64_t>(
net::features::kSqlDiskCacheSharedCacheReadBufferSize.Get()),
info.body_end);
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(chunk_size);
const auto key = entry.key;
const auto res_id = info.res_id;
const auto body_end = info.body_end;
store_->ReadEntryData(
key, res_id, 0, buffer, chunk_size, body_end,
/*sparse_reading=*/false,
base::BindOnce(&SqlSharedCache::OnEntryDataReadForInsert,
weak_factory_.GetWeakPtr(), std::move(entry), res_id,
std::move(pickled_buffer), body_end, buffer));
return;
}
isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::Insert)
.WithArgs(entry.key, std::move(pickled_buffer), info.body_end, nullptr)
.Then(base::BindOnce(&SqlSharedCache::OnIsolatedDatabaseInserted,
weak_factory_.GetWeakPtr(), entry.key, info.res_id,
info.body_end, 0));
}
void SqlSharedCache::OnEntryDataReadForInsert(
SqlPersistentStore::SharedCacheEligibleEntry entry,
SqlPersistentStore::ResId res_id,
scoped_refptr<net::PickledIOBuffer> headers,
int64_t body_end,
scoped_refptr<net::IOBuffer> buffer,
base::expected<SqlPersistentStore::ReadResult, SqlPersistentStore::Error>
result) {
// For `sparse_reading = false`, `SqlPersistentStoreBackend::ReadEntryData`
// zero-fills gaps and returns `read_bytes` equal to the requested size
// as long as the read is within bounds.
if (!result.has_value() ||
result.value().read_bytes != static_cast<int>(buffer->size())) {
OnCopyEntryFailed();
return;
}
isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::Insert)
.WithArgs(entry.key, std::move(headers), body_end, buffer)
.Then(base::BindOnce(&SqlSharedCache::OnIsolatedDatabaseInserted,
weak_factory_.GetWeakPtr(), entry.key, res_id,
body_end, buffer->size()));
}
void SqlSharedCache::OnIsolatedDatabaseInserted(
CacheEntryKey key,
SqlPersistentStore::ResId res_id,
int64_t body_end,
int64_t offset,
base::expected<SqlSharedCacheRowId, SqlSharedCacheIsolatedDatabase::Error>
result) {
if (!result.has_value()) {
OnCopyEntryFailed();
return;
}
CHECK(!current_copy_row_id_);
current_copy_row_id_ = result.value();
ReadNextChunk(std::move(key), res_id, body_end, offset, result.value());
}
void SqlSharedCache::ReadNextChunk(CacheEntryKey key,
SqlPersistentStore::ResId res_id,
int64_t body_end,
int64_t offset,
SqlSharedCacheRowId shared_cache_row_id) {
CHECK_LE(offset, body_end);
if (offset == body_end) {
MoveBlobsToSharedCache(key, res_id, shared_cache_row_id);
return;
}
int64_t chunk_size =
std::min(static_cast<int64_t>(
net::features::kSqlDiskCacheSharedCacheReadBufferSize.Get()),
body_end - offset);
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(chunk_size);
store_->ReadEntryData(
key, res_id, offset, buffer, chunk_size, body_end,
/*sparse_reading=*/false,
base::BindOnce(&SqlSharedCache::OnEntryDataRead,
weak_factory_.GetWeakPtr(), key, res_id, body_end, offset,
shared_cache_row_id, buffer));
}
void SqlSharedCache::OnEntryDataRead(
CacheEntryKey key,
SqlPersistentStore::ResId res_id,
int64_t body_end,
int64_t offset,
SqlSharedCacheRowId shared_cache_row_id,
scoped_refptr<net::IOBuffer> buffer,
base::expected<SqlPersistentStore::ReadResult, SqlPersistentStore::Error>
result) {
// For `sparse_reading = false`, `SqlPersistentStoreBackend::ReadEntryData`
// zero-fills gaps and returns `read_bytes` equal to the requested size
// as long as the read is within bounds.
if (!result.has_value() || result.value().read_bytes != buffer->size()) {
OnCopyEntryFailed();
return;
}
bool set_ready = (offset + buffer->size() == body_end);
isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::WriteBody)
.WithArgs(key, shared_cache_row_id, offset, buffer, set_ready)
.Then(base::BindOnce(&SqlSharedCache::OnIsolatedDatabaseWritten,
weak_factory_.GetWeakPtr(), key, res_id, body_end,
offset + buffer->size(), shared_cache_row_id));
}
void SqlSharedCache::OnIsolatedDatabaseWritten(
CacheEntryKey key,
SqlPersistentStore::ResId res_id,
int64_t body_end,
int64_t next_offset,
SqlSharedCacheRowId shared_cache_row_id,
base::expected<void, SqlSharedCacheIsolatedDatabase::Error> result) {
if (!result.has_value()) {
OnCopyEntryFailed();
return;
}
ReadNextChunk(std::move(key), res_id, body_end, next_offset,
shared_cache_row_id);
}
void SqlSharedCache::MoveBlobsToSharedCache(
CacheEntryKey key,
SqlPersistentStore::ResId res_id,
SqlSharedCacheRowId shared_cache_row_id) {
store_->MoveBlobsToSharedCache(
key, res_id, {*shared_cache_db_id_, shared_cache_row_id},
base::BindOnce(
[](base::WeakPtr<SqlSharedCache> self, CacheEntryKey key,
SqlPersistentStore::Error error) {
if (self) {
if (error == SqlPersistentStore::Error::kOk) {
self->OnCopyEntryComplete(key);
} else {
self->OnCopyEntryFailed();
}
}
},
weak_factory_.GetWeakPtr(), key));
}
void SqlSharedCache::OnCopyEntryComplete(const CacheEntryKey& key) {
CHECK(current_entry_hash_);
// `cached_hashes_` is guaranteed to be populated by `OnHashesLoaded` which
// was scheduled on the database task runner during `InitIsolatedDatabase`
// prior to any entry copy operations on the same sequence.
CHECK(cached_hashes_.has_value());
if (cached_hashes_->insert(*current_entry_hash_).second) {
copy_new_hashes_.push_back(*current_entry_hash_);
}
current_entry_hash_.reset();
current_copy_row_id_ = std::nullopt;
if (on_entry_copied_callback_) {
on_entry_copied_callback_.Run(key);
}
CopyNextEntry();
}
void SqlSharedCache::OnCopyEntryFailed() {
if (current_copy_row_id_.has_value()) {
isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::DeleteEntry)
.WithArgs(*current_copy_row_id_);
current_copy_row_id_ = std::nullopt;
}
current_entry_hash_.reset();
CopyNextEntry();
}
void SqlSharedCache::FinishCopy() {
if (!copy_new_hashes_.empty()) {
for (ClientsMap::iterator it(&clients_); !it.IsAtEnd(); it.Advance()) {
it.GetCurrentValue()->OnResourcesAdded(copy_new_hashes_);
}
copy_new_hashes_.clear();
}
CHECK(copy_callback_);
CHECK(!current_copy_row_id_);
on_entry_copied_callback_.Reset();
auto unprocessed_entries = std::move(pending_copy_entries_);
CHECK(pending_copy_entries_.empty());
copy_abort_flag_ = nullptr;
auto callback = std::move(copy_callback_);
CHECK(!copy_callback_);
std::move(callback).Run(std::move(unprocessed_entries));
}
void SqlSharedCache::DeleteEntries(
const std::vector<SqlSharedCacheRowId>& shared_cache_row_ids,
base::OnceCallback<
void(base::expected<void, SqlSharedCacheIsolatedDatabase::Error>)>
callback) {
if (!isolated_database_) {
std::move(callback).Run(base::unexpected(
SqlSharedCacheIsolatedDatabase::Error::kIsolatedDatabaseNotAvailable));
return;
}
isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::DeleteEntries)
.WithArgs(shared_cache_row_ids)
.Then(std::move(callback));
}
void SqlSharedCache::Read(
const CacheEntryKey& entry_key,
SqlSharedCacheRowId shared_cache_row_id,
int body_size,
int64_t offset,
scoped_refptr<net::IOBuffer> buffer,
SqlPersistentStore::ReadResultOrErrorCallback callback) {
if (!isolated_database_) {
std::move(callback).Run(
base::unexpected(SqlPersistentStore::Error::kNotFound));
return;
}
if (offset > std::numeric_limits<int>::max()) {
std::move(callback).Run(
base::unexpected(SqlPersistentStore::Error::kFailedToExecute));
return;
}
int offset_int = static_cast<int>(offset);
isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::Read)
.WithArgs(entry_key, shared_cache_row_id, body_size, offset_int, buffer)
.Then(base::BindOnce(&SqlSharedCache::OnIsolatedDatabaseRead,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void SqlSharedCache::OnIsolatedDatabaseRead(
SqlPersistentStore::ReadResultOrErrorCallback callback,
SqlSharedCacheIsolatedDatabase::ReadResultOrError result) {
if (result.has_value()) {
std::move(callback).Run(result.value());
} else {
SqlPersistentStore::Error store_error;
switch (result.error()) {
case SqlSharedCacheIsolatedDatabase::Error::kEntryNotFound:
store_error = SqlPersistentStore::Error::kNotFound;
break;
default:
store_error = SqlPersistentStore::Error::kFailedToExecute;
break;
}
std::move(callback).Run(base::unexpected(store_error));
}
}
void SqlSharedCache::GetBlobHandle(
const CacheEntryKey& entry_key,
SqlSharedCacheRowId shared_cache_row_id,
int body_size,
base::OnceCallback<
void(base::expected<scoped_refptr<SqlSharedCacheBlobHandle>,
SqlSharedCacheIsolatedDatabase::Error>)> callback) {
if (!isolated_database_) {
std::move(callback).Run(base::unexpected(
SqlSharedCacheIsolatedDatabase::Error::kIsolatedDatabaseNotAvailable));
return;
}
isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::GetBlobHandle)
.WithArgs(entry_key, shared_cache_row_id, body_size)
.Then(std::move(callback));
}
} // namespace disk_cache