| // 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_isolated_database.h" | |
| #include <algorithm> | |
| #include <limits> | |
| #include <utility> | |
| #include "base/check.h" | |
| #include "base/check_op.h" | |
| #include "base/functional/bind.h" | |
| #include "base/functional/callback.h" | |
| #include "base/location.h" | |
| #include "base/memory/ref_counted_memory.h" | |
| #include "base/no_destructor.h" | |
| #include "base/numerics/safe_conversions.h" | |
| #include "base/strings/strcat.h" | |
| #include "base/strings/string_number_conversions.h" | |
| #include "base/task/sequenced_task_runner.h" | |
| #include "base/types/expected_macros.h" | |
| #include "components/sqlite_vfs/client.h" | |
| #include "components/sqlite_vfs/sqlite_sandboxed_vfs.h" | |
| #include "components/sqlite_vfs/vfs_utils.h" | |
| #include "net/base/features.h" | |
| #include "net/disk_cache/sql/sql_backend_constants.h" | |
| #include "net/disk_cache/sql/sql_read_cache_memory_monitor.h" | |
| #include "net/disk_cache/sql/sql_shared_cache_isolated_database_queries.h" | |
| #include "sql/database.h" | |
| #include "sql/meta_table.h" | |
| #include "sql/statement.h" | |
| #include "sql/streaming_blob_handle.h" | |
| #include "sql/transaction.h" | |
| using disk_cache_sql_queries::GetSharedCacheIsolatedDatabaseQuery; | |
| using disk_cache_sql_queries::SharedCacheIsolatedDatabaseQuery; | |
| namespace disk_cache { | |
| namespace { | |
| constexpr int kSharedCacheIsolatedDatabaseCurrentVersion = 1; | |
| constexpr int kSharedCacheIsolatedDatabaseCompatibleVersion = 1; | |
| } // namespace | |
| // static | |
| std::unique_ptr<SqlSharedCacheIsolatedDatabase::DatabaseAssets> | |
| SqlSharedCacheIsolatedDatabase::DatabaseAssets::MaybeCreate( | |
| const base::FilePath& directory, | |
| SqlSharedCacheDbId shared_cache_db_id) { | |
| ASSIGN_OR_RETURN( | |
| auto pending_file_set, | |
| sqlite_vfs::MakePendingFileSet( | |
| sqlite_vfs::Client::kSharedCacheIsolated, directory, | |
| base::FilePath::FromASCII( | |
| base::StrCat({kSqlBackendSharedCacheIsolatedFileNamePrefix, | |
| base::NumberToString(*shared_cache_db_id)})), | |
| /*single_connection=*/false, | |
| net::features::kRendererAccessibleHttpCacheWalMode.Get()), | |
| [](auto) { return nullptr; }); | |
| ASSIGN_OR_RETURN(auto vfs_file_set, | |
| sqlite_vfs::SqliteVfsFileSet::Bind( | |
| sqlite_vfs::Client::kSharedCacheIsolated, | |
| std::move(pending_file_set)), | |
| [] { return nullptr; }); | |
| return std::make_unique<DatabaseAssets>(directory, shared_cache_db_id, | |
| std::move(vfs_file_set), | |
| base::PassKey<DatabaseAssets>()); | |
| } | |
| SqlSharedCacheIsolatedDatabase::DatabaseAssets::DatabaseAssets( | |
| const base::FilePath& directory, | |
| SqlSharedCacheDbId shared_cache_db_id, | |
| sqlite_vfs::SqliteVfsFileSet vfs_file_set, | |
| base::PassKey<DatabaseAssets>) | |
| : directory_(directory), | |
| shared_cache_db_id_(shared_cache_db_id), | |
| vfs_file_set_(std::move(vfs_file_set)), | |
| unregister_runner_(sqlite_vfs::SqliteSandboxedVfsDelegate::GetInstance() | |
| ->RegisterSandboxedFiles(vfs_file_set_)), | |
| db_(sql::DatabaseOptions() | |
| .set_read_only(vfs_file_set_.read_only()) | |
| .set_exclusive_locking(vfs_file_set_.is_single_connection()) | |
| .set_wal_mode(vfs_file_set_.wal_mode()) | |
| .set_vfs_name_discouraged( | |
| sqlite_vfs::SqliteSandboxedVfsDelegate::kSqliteVfsName) | |
| .set_mmap_enabled(false), | |
| sql::Database::Tag("SharedCacheIsolated")) {} | |
| SqlSharedCacheIsolatedDatabase::DatabaseAssets::~DatabaseAssets() = default; | |
| base::FilePath | |
| SqlSharedCacheIsolatedDatabase::DatabaseAssets::GetDbVirtualFilePath() const { | |
| return vfs_file_set_.GetDbVirtualFilePath(); | |
| } | |
| base::expected<sqlite_vfs::PendingFileSet, sqlite_vfs::FileSetError> | |
| SqlSharedCacheIsolatedDatabase::DatabaseAssets::ShareConnection() { | |
| return sqlite_vfs::ShareConnection( | |
| directory_, | |
| base::FilePath::FromASCII( | |
| base::StrCat({kSqlBackendSharedCacheIsolatedFileNamePrefix, | |
| base::NumberToString(*shared_cache_db_id_)})), | |
| vfs_file_set_, /*read_write=*/false); | |
| } | |
| void SqlSharedCacheIsolatedDatabase::DatabaseAssets::AbandonAndDeleteFiles() { | |
| db_.Close(); | |
| vfs_file_set_.Abandon(); | |
| base::FilePath base_name = base::FilePath::FromASCII( | |
| base::StrCat({kSqlBackendSharedCacheIsolatedFileNamePrefix, | |
| base::NumberToString(shared_cache_db_id_.value())})); | |
| sqlite_vfs::DeleteFiles(sqlite_vfs::Client::kSharedCacheIsolated, directory_, | |
| base_name); | |
| } | |
| SqlSharedCacheIsolatedDatabase::SqlSharedCacheIsolatedDatabase( | |
| std::string nik_string, | |
| const base::FilePath& directory, | |
| SqlSharedCacheDbId shared_cache_db_id, | |
| scoped_refptr<base::SequencedTaskRunner> task_runner, | |
| scoped_refptr<SqlReadCacheMemoryMonitor> read_cache_memory_monitor) | |
| : nik_string_(std::move(nik_string)), | |
| db_assets_(DatabaseAssets::MaybeCreate(directory, shared_cache_db_id)), | |
| task_runner_(std::move(task_runner)), | |
| read_cache_memory_monitor_(std::move(read_cache_memory_monitor)) { | |
| CHECK(task_runner_); | |
| } | |
| SqlSharedCacheIsolatedDatabase::~SqlSharedCacheIsolatedDatabase() = default; | |
| base::expected<std::vector<uint32_t>, SqlSharedCacheIsolatedDatabase::Error> | |
| SqlSharedCacheIsolatedDatabase::GetAllUrlHashes() { | |
| if (ShouldSimulateFailure(OperationForTesting::kRead)) { | |
| return base::unexpected(Error::kFailedForTesting); | |
| } | |
| if (!db_assets_) { | |
| return base::unexpected(Error::kFailedToOpenVfsFileSet); | |
| } | |
| if (!db_assets_->db().is_open()) { | |
| return base::unexpected(Error::kDatabaseNotOpen); | |
| } | |
| sql::Statement statement(db_assets_->db().GetCachedStatement( | |
| SQL_FROM_HERE, GetSharedCacheIsolatedDatabaseQuery( | |
| SharedCacheIsolatedDatabaseQuery::kSelectHashes))); | |
| std::vector<uint32_t> hashes; | |
| while (statement.Step()) { | |
| hashes.push_back(statement.ColumnInt(0)); | |
| } | |
| if (!statement.Succeeded()) { | |
| return base::unexpected(Error::kFailedToExecuteStatement); | |
| } | |
| return hashes; | |
| } | |
| base::expected<sqlite_vfs::PendingFileSet, | |
| SqlSharedCacheIsolatedDatabase::Error> | |
| SqlSharedCacheIsolatedDatabase::GetSharedReadOnlyConnection() { | |
| if (!db_assets_) { | |
| return base::unexpected(Error::kFailedToOpenVfsFileSet); | |
| } | |
| auto pending_file_set = db_assets_->ShareConnection(); | |
| if (!pending_file_set.has_value()) { | |
| return base::unexpected(Error::kFailedToShareConnection); | |
| } | |
| return std::move(*pending_file_set); | |
| } | |
| namespace { | |
| SqlSharedCacheIsolatedDatabase::SimFailedCallback* | |
| g_global_simulate_db_failure_callback = nullptr; | |
| } // namespace | |
| // static | |
| void SqlSharedCacheIsolatedDatabase:: | |
| SetGlobalSimulateDbFailureCallbackForTesting( // IN-TEST | |
| SimFailedCallback callback) { | |
| if (callback) { | |
| static base::NoDestructor<SimFailedCallback> s_callback; | |
| *s_callback = std::move(callback); | |
| g_global_simulate_db_failure_callback = s_callback.get(); | |
| } else { | |
| g_global_simulate_db_failure_callback = nullptr; | |
| } | |
| } | |
| void SqlSharedCacheIsolatedDatabase:: | |
| SetSimulateDbFailureCallbackForTesting( // IN-TEST | |
| SimFailedCallback callback) { | |
| simulate_db_failure_callback_ = std::move(callback); | |
| } | |
| bool SqlSharedCacheIsolatedDatabase::ShouldSimulateFailure( | |
| OperationForTesting op) const { | |
| if (g_global_simulate_db_failure_callback && | |
| g_global_simulate_db_failure_callback->Run(op)) { | |
| return true; | |
| } | |
| return simulate_db_failure_callback_ && simulate_db_failure_callback_.Run(op); | |
| } | |
| base::expected<void, SqlSharedCacheIsolatedDatabase::Error> | |
| SqlSharedCacheIsolatedDatabase::Init() { | |
| if (ShouldSimulateFailure(OperationForTesting::kInit)) { | |
| return base::unexpected(Error::kFailedForTesting); | |
| } | |
| if (!db_assets_) { | |
| return base::unexpected(Error::kFailedToOpenVfsFileSet); | |
| } | |
| sql::Database& db = db_assets_->db(); | |
| if (db.is_open()) { | |
| return base::ok(); | |
| } | |
| if (!db.Open(db_assets_->GetDbVirtualFilePath())) { | |
| return base::unexpected(Error::kFailedToOpenDatabase); | |
| } | |
| if (sql::MetaTable::RazeIfIncompatible( | |
| &db, kSharedCacheIsolatedDatabaseCompatibleVersion, | |
| kSharedCacheIsolatedDatabaseCurrentVersion) == | |
| sql::RazeIfIncompatibleResult::kFailed) { | |
| return base::unexpected(Error::kFailedToRazeIncompatibleVersionDatabase); | |
| } | |
| sql::MetaTable meta_table; | |
| if (sql::MetaTable::DoesTableExist(&db)) { | |
| std::string saved_nik; | |
| if (!meta_table.Init(&db, kSharedCacheIsolatedDatabaseCurrentVersion, | |
| kSharedCacheIsolatedDatabaseCompatibleVersion) || | |
| !meta_table.GetValue(kSqlBackendSharedCacheIsolatedMetaTableKeyNik, | |
| &saved_nik) || | |
| saved_nik != nik_string_) { | |
| if (!db.Raze()) { | |
| return base::unexpected(Error::kFailedToRazeIncompatibleNikDatabase); | |
| } | |
| meta_table.Reset(); | |
| } else { | |
| return base::ok(); | |
| } | |
| } | |
| sql::Transaction transaction(&db); | |
| if (!transaction.Begin()) { | |
| return base::unexpected(Error::kFailedToStartTransaction); | |
| } | |
| if (!db.Execute(GetSharedCacheIsolatedDatabaseQuery( | |
| SharedCacheIsolatedDatabaseQuery::kCreateResourcesTable))) { | |
| return base::unexpected(Error::kFailedToCreateResourcesTable); | |
| } | |
| if (!db.Execute(GetSharedCacheIsolatedDatabaseQuery( | |
| SharedCacheIsolatedDatabaseQuery::kCreateResourcesTableIndex))) { | |
| return base::unexpected(Error::kFailedToCreateResourcesTableIndex); | |
| } | |
| if (!meta_table.Init(&db, kSharedCacheIsolatedDatabaseCurrentVersion, | |
| kSharedCacheIsolatedDatabaseCompatibleVersion)) { | |
| return base::unexpected(Error::kFailedToInitMetaTable); | |
| } | |
| if (!meta_table.SetValue(kSqlBackendSharedCacheIsolatedMetaTableKeyNik, | |
| nik_string_)) { | |
| return base::unexpected(Error::kFailedToSetNikInMetaTable); | |
| } | |
| if (!transaction.Commit()) { | |
| return base::unexpected(Error::kFailedToCommitTransaction); | |
| } | |
| return base::ok(); | |
| } | |
| base::expected<SqlSharedCacheRowId, SqlSharedCacheIsolatedDatabase::Error> | |
| SqlSharedCacheIsolatedDatabase::Insert(const CacheEntryKey& entry_key, | |
| scoped_refptr<net::IOBuffer> headers, | |
| uint32_t total_body_size, | |
| scoped_refptr<net::IOBuffer> body) { | |
| if (ShouldSimulateFailure(OperationForTesting::kInsert)) { | |
| return base::unexpected(Error::kFailedForTesting); | |
| } | |
| if (!db_assets_ || !db_assets_->db().is_open()) { | |
| return base::unexpected(Error::kDatabaseNotOpen); | |
| } | |
| if (total_body_size > | |
| static_cast<uint32_t>(std::numeric_limits<int32_t>::max())) { | |
| return base::unexpected(Error::kBodyTooLarge); | |
| } | |
| sql::Transaction transaction(&db_assets_->db()); | |
| if (!transaction.Begin()) { | |
| return base::unexpected(Error::kFailedToStartTransaction); | |
| } | |
| SqlSharedCacheRowId shared_cache_row_id; | |
| const bool is_ready = | |
| (total_body_size == 0) || | |
| (body && total_body_size == static_cast<uint32_t>(body->size())); | |
| constexpr base::span<uint8_t, 0> kEmptySpan; | |
| { | |
| sql::Statement statement(db_assets_->db().GetCachedStatement( | |
| SQL_FROM_HERE, GetSharedCacheIsolatedDatabaseQuery( | |
| SharedCacheIsolatedDatabaseQuery::kInsertResource))); | |
| statement.BindInt64(0, entry_key.resource_url_hash().value()); | |
| statement.BindString(1, entry_key.resource_url()); | |
| // Wrapping the span with a RefCountedStaticMemory to avoid memory copy. | |
| // SAFETY: The memory referenced by `headers->span()`/`kEmptySpan` must | |
| // outlive `statement`. | |
| statement.BindBlob(2, base::MakeRefCounted<base::RefCountedStaticMemory>( | |
| headers ? headers->span() : kEmptySpan)); | |
| if (is_ready) { | |
| // SAFETY: The memory referenced by `body->span()`/`kEmptySpan` must | |
| // outlive `statement`. | |
| statement.BindBlob( | |
| 3, base::MakeRefCounted<base::RefCountedStaticMemory>( | |
| body ? body->span().first(total_body_size) : kEmptySpan)); | |
| } else { | |
| statement.BindBlobForStreaming(3, total_body_size); | |
| } | |
| statement.BindBool(4, is_ready); | |
| if (!statement.Run()) { | |
| return base::unexpected(Error::kFailedToExecuteStatement); | |
| } | |
| shared_cache_row_id = | |
| SqlSharedCacheRowId(db_assets_->db().GetLastInsertRowId()); | |
| } | |
| if (body && !is_ready) { | |
| RETURN_IF_ERROR(WriteBodyInternal(entry_key, shared_cache_row_id, 0, body, | |
| /*set_ready=*/false, | |
| /*in_transaction=*/true)); | |
| } | |
| if (!transaction.Commit()) { | |
| return base::unexpected(Error::kFailedToCommitTransaction); | |
| } | |
| return shared_cache_row_id; | |
| } | |
| base::expected<void, SqlSharedCacheIsolatedDatabase::Error> | |
| SqlSharedCacheIsolatedDatabase::WriteBody( | |
| const CacheEntryKey& entry_key, | |
| SqlSharedCacheRowId shared_cache_row_id, | |
| int offset, | |
| scoped_refptr<net::IOBuffer> buffer, | |
| bool set_ready) { | |
| return WriteBodyInternal(entry_key, shared_cache_row_id, offset, buffer, | |
| set_ready, /*in_transaction=*/false); | |
| } | |
| base::expected<void, SqlSharedCacheIsolatedDatabase::Error> | |
| SqlSharedCacheIsolatedDatabase::WriteBodyInternal( | |
| const CacheEntryKey& entry_key, | |
| SqlSharedCacheRowId shared_cache_row_id, | |
| int offset, | |
| scoped_refptr<net::IOBuffer> buffer, | |
| bool set_ready, | |
| bool in_transaction) { | |
| if (ShouldSimulateFailure(OperationForTesting::kWriteBody)) { | |
| return base::unexpected(Error::kFailedForTesting); | |
| } | |
| if (!db_assets_ || !db_assets_->db().is_open()) { | |
| return base::unexpected(Error::kDatabaseNotOpen); | |
| } | |
| const int size = buffer ? buffer->size() : 0; | |
| CHECK_GE(size, 0); | |
| if (offset < 0 || size > std::numeric_limits<int32_t>::max() - offset) { | |
| return base::unexpected(Error::kInvalidWriteRange); | |
| } | |
| std::optional<sql::Transaction> transaction; | |
| if (!in_transaction) { | |
| transaction.emplace(&db_assets_->db()); | |
| if (!transaction->Begin()) { | |
| return base::unexpected(Error::kFailedToStartTransaction); | |
| } | |
| } | |
| if (size > 0) { | |
| ASSIGN_OR_RETURN(auto blob_handle, | |
| db_assets_->db().GetStreamingBlob( | |
| "resources", "body", shared_cache_row_id.value(), | |
| /*readonly=*/false), | |
| [] { return Error::kFailedToGetBlob; }); | |
| if (!blob_handle.Write(offset, buffer->span())) { | |
| return base::unexpected(Error::kFailedToWriteBlob); | |
| } | |
| } | |
| if (set_ready) { | |
| sql::Statement statement(db_assets_->db().GetCachedStatement( | |
| SQL_FROM_HERE, | |
| GetSharedCacheIsolatedDatabaseQuery( | |
| SharedCacheIsolatedDatabaseQuery::kSetResourceReady))); | |
| statement.BindInt64(0, shared_cache_row_id.value()); | |
| if (!statement.Run()) { | |
| return base::unexpected(Error::kFailedToSetReady); | |
| } | |
| } | |
| if (transaction && !transaction->Commit()) { | |
| return base::unexpected(Error::kFailedToCommitTransaction); | |
| } | |
| return base::ok(); | |
| } | |
| SqlSharedCacheIsolatedDatabase::SqlSharedCacheBlobHandleImpl:: | |
| SqlSharedCacheBlobHandleImpl( | |
| base::OnceClosure decrement_closure, | |
| scoped_refptr<base::SequencedTaskRunner> task_runner) | |
| : decrement_closure_(std::move(decrement_closure)), | |
| task_runner_(std::move(task_runner)) {} | |
| SqlSharedCacheIsolatedDatabase::SqlSharedCacheBlobHandleImpl:: | |
| ~SqlSharedCacheBlobHandleImpl() { | |
| task_runner_->PostTask(FROM_HERE, std::move(decrement_closure_)); | |
| } | |
| SqlSharedCacheIsolatedDatabase::BlobHandleHolder::BlobHandleHolder( | |
| sql::StreamingBlobHandle blob_handle, | |
| const CacheEntryKey& entry_key, | |
| int body_size) | |
| : blob_handle_(std::move(blob_handle)), | |
| entry_key_(entry_key), | |
| body_size_(body_size) {} | |
| SqlSharedCacheIsolatedDatabase::BlobHandleHolder::~BlobHandleHolder() = default; | |
| base::expected<scoped_refptr<SqlSharedCacheBlobHandle>, | |
| SqlSharedCacheIsolatedDatabase::Error> | |
| SqlSharedCacheIsolatedDatabase::GetBlobHandle( | |
| const CacheEntryKey& entry_key, | |
| SqlSharedCacheRowId shared_cache_row_id, | |
| int body_size) { | |
| if (ShouldSimulateFailure(OperationForTesting::kRead)) { | |
| return base::unexpected(Error::kFailedForTesting); | |
| } | |
| if (!db_assets_ || !db_assets_->db().is_open()) { | |
| return base::unexpected(Error::kDatabaseNotOpen); | |
| } | |
| ASSIGN_OR_RETURN( | |
| auto* blob_handle_holder, | |
| GetCachedBlobHandleHolder(entry_key, shared_cache_row_id, body_size)); | |
| if (blob_handle_holder) { | |
| blob_handle_holder->IncrementRefCount(); | |
| return base::MakeRefCounted<SqlSharedCacheBlobHandleImpl>( | |
| base::BindOnce( | |
| &SqlSharedCacheIsolatedDatabase::DecrementBlobHandleRefCount, | |
| weak_factory_.GetWeakPtr(), shared_cache_row_id), | |
| task_runner_); | |
| } | |
| ASSIGN_OR_RETURN( | |
| auto streaming_blob_handle, | |
| GetStreamingBlobHandle(entry_key, shared_cache_row_id, body_size)); | |
| blob_handle_holders_.try_emplace( | |
| shared_cache_row_id, | |
| std::make_unique<BlobHandleHolder>(std::move(streaming_blob_handle), | |
| entry_key, body_size)); | |
| return base::MakeRefCounted<SqlSharedCacheBlobHandleImpl>( | |
| base::BindOnce( | |
| &SqlSharedCacheIsolatedDatabase::DecrementBlobHandleRefCount, | |
| weak_factory_.GetWeakPtr(), shared_cache_row_id), | |
| task_runner_); | |
| } | |
| base::expected<SqlSharedCacheIsolatedDatabase::BlobHandleHolder*, | |
| SqlSharedCacheIsolatedDatabase::Error> | |
| SqlSharedCacheIsolatedDatabase::GetCachedBlobHandleHolder( | |
| const CacheEntryKey& entry_key, | |
| SqlSharedCacheRowId shared_cache_row_id, | |
| int body_size) { | |
| auto it = blob_handle_holders_.find(shared_cache_row_id); | |
| if (it == blob_handle_holders_.end()) { | |
| return nullptr; | |
| } | |
| if (it->second->resource_url() != entry_key.resource_url()) { | |
| return base::unexpected(Error::kEntryNotFound); | |
| } | |
| if (it->second->body_size() != body_size) { | |
| return base::unexpected(Error::kBodySizeMismatch); | |
| } | |
| return it->second.get(); | |
| } | |
| base::expected<sql::StreamingBlobHandle, SqlSharedCacheIsolatedDatabase::Error> | |
| SqlSharedCacheIsolatedDatabase::GetStreamingBlobHandle( | |
| const CacheEntryKey& entry_key, | |
| SqlSharedCacheRowId shared_cache_row_id, | |
| int body_size) { | |
| if (!db_assets_ || !db_assets_->db().is_open()) { | |
| return base::unexpected(Error::kDatabaseNotOpen); | |
| } | |
| { | |
| sql::Statement statement(db_assets_->db().GetCachedStatement( | |
| SQL_FROM_HERE, | |
| GetSharedCacheIsolatedDatabaseQuery( | |
| SharedCacheIsolatedDatabaseQuery::kSelectUrlAndReadyByRowId))); | |
| statement.BindInt64(0, shared_cache_row_id.value()); | |
| if (!statement.Step() || !statement.ColumnBool(1) || | |
| statement.ColumnString(0) != entry_key.resource_url()) { | |
| return base::unexpected(Error::kEntryNotFound); | |
| } | |
| } | |
| ASSIGN_OR_RETURN(auto blob_handle, | |
| db_assets_->db().GetStreamingBlob( | |
| "resources", "body", shared_cache_row_id.value(), | |
| /*readonly=*/true), | |
| [] { return Error::kFailedToGetBlob; }); | |
| if (blob_handle.GetSize() != body_size) { | |
| return base::unexpected(Error::kBodySizeMismatch); | |
| } | |
| return blob_handle; | |
| } | |
| void SqlSharedCacheIsolatedDatabase::DecrementBlobHandleRefCount( | |
| SqlSharedCacheRowId shared_cache_row_id) { | |
| auto it = blob_handle_holders_.find(shared_cache_row_id); | |
| if (it == blob_handle_holders_.end()) { | |
| return; | |
| } | |
| CHECK(it->second); | |
| it->second->DecrementRefCount(); | |
| if (it->second->ref_count() == 0) { | |
| blob_handle_holders_.erase(it); | |
| } | |
| } | |
| SqlSharedCacheIsolatedDatabase::ReadResultOrError | |
| SqlSharedCacheIsolatedDatabase::Read(const CacheEntryKey& entry_key, | |
| SqlSharedCacheRowId shared_cache_row_id, | |
| int body_size, | |
| int offset, | |
| scoped_refptr<net::IOBuffer> buffer) { | |
| if (ShouldSimulateFailure(OperationForTesting::kRead)) { | |
| return base::unexpected(Error::kFailedForTesting); | |
| } | |
| if (!db_assets_ || !db_assets_->db().is_open()) { | |
| return base::unexpected(Error::kDatabaseNotOpen); | |
| } | |
| CHECK(buffer); | |
| const int buf_len = buffer->size(); | |
| CHECK_GE(buf_len, 0); | |
| CHECK_GE(body_size, 0); | |
| if (offset < 0 || buf_len > std::numeric_limits<int32_t>::max() - offset || | |
| offset + buf_len > body_size) { | |
| return base::unexpected(Error::kInvalidReadRange); | |
| } | |
| std::optional<sql::StreamingBlobHandle> new_blob_handle; | |
| sql::StreamingBlobHandle* blob_handle_ptr = nullptr; | |
| ASSIGN_OR_RETURN( | |
| auto* blob_handle_holder, | |
| GetCachedBlobHandleHolder(entry_key, shared_cache_row_id, body_size)); | |
| if (blob_handle_holder) { | |
| blob_handle_ptr = &blob_handle_holder->blob_handle(); | |
| } | |
| if (!blob_handle_ptr) { | |
| ASSIGN_OR_RETURN( | |
| new_blob_handle, | |
| GetStreamingBlobHandle(entry_key, shared_cache_row_id, body_size)); | |
| if (new_blob_handle.has_value()) { | |
| blob_handle_ptr = &new_blob_handle.value(); | |
| } | |
| } | |
| if (!blob_handle_ptr) { | |
| return base::unexpected(Error::kFailedToGetBlob); | |
| } | |
| if (!blob_handle_ptr->Read(offset, buffer->span())) { | |
| return base::unexpected(Error::kFailedToReadBlob); | |
| } | |
| ReadResult read_result; | |
| read_result.read_bytes = buf_len; | |
| int64_t read_end = static_cast<int64_t>(offset) + buf_len; | |
| if (read_end < static_cast<int64_t>(body_size) && | |
| read_cache_memory_monitor_) { | |
| const int64_t remaining_bytes = static_cast<int64_t>(body_size) - read_end; | |
| const int cache_size = base::saturated_cast<int>(remaining_bytes); | |
| if (read_cache_memory_monitor_->Allocate(cache_size)) { | |
| auto cache_buffer = base::MakeRefCounted<MonitoredVectorIOBuffer>( | |
| cache_size, read_cache_memory_monitor_); | |
| if (blob_handle_ptr->Read(read_end, cache_buffer->span())) { | |
| read_result.cache_buffer = std::move(cache_buffer); | |
| read_result.cache_buffer_offset = read_end; | |
| } | |
| } | |
| } | |
| return read_result; | |
| } | |
| void SqlSharedCacheIsolatedDatabase::DeleteEntry( | |
| SqlSharedCacheRowId shared_cache_row_id) { | |
| if (ShouldSimulateFailure(OperationForTesting::kDeleteEntry)) { | |
| return; | |
| } | |
| if (!db_assets_ || !db_assets_->db().is_open()) { | |
| return; | |
| } | |
| sql::Statement statement(db_assets_->db().GetCachedStatement( | |
| SQL_FROM_HERE, | |
| GetSharedCacheIsolatedDatabaseQuery( | |
| SharedCacheIsolatedDatabaseQuery::kDeleteResourceByRowId))); | |
| statement.BindInt64(0, shared_cache_row_id.value()); | |
| statement.Run(); | |
| } | |
| base::expected<void, SqlSharedCacheIsolatedDatabase::Error> | |
| SqlSharedCacheIsolatedDatabase::DeleteEntries( | |
| const std::vector<SqlSharedCacheRowId>& shared_cache_row_ids) { | |
| CHECK(!shared_cache_row_ids.empty()); | |
| if (ShouldSimulateFailure(OperationForTesting::kDeleteEntries)) { | |
| return base::unexpected(Error::kFailedForTesting); | |
| } | |
| if (!db_assets_ || !db_assets_->db().is_open()) { | |
| return base::unexpected(Error::kDatabaseNotOpen); | |
| } | |
| sql::Database& db = db_assets_->db(); | |
| sql::Transaction transaction(&db); | |
| if (!transaction.Begin()) { | |
| return base::unexpected(Error::kFailedToStartTransaction); | |
| } | |
| for (auto row_id : shared_cache_row_ids) { | |
| sql::Statement statement(db.GetCachedStatement( | |
| SQL_FROM_HERE, | |
| GetSharedCacheIsolatedDatabaseQuery( | |
| SharedCacheIsolatedDatabaseQuery::kDeleteResourceByRowId))); | |
| statement.BindInt64(0, row_id.value()); | |
| statement.Run(); | |
| } | |
| if (!transaction.Commit()) { | |
| return base::unexpected(Error::kFailedToCommitTransaction); | |
| } | |
| // TODO(crbug.com/473666511): Autovacuum is disabled on Desktop, so we need | |
| // to run VACUUM manually at some point. However, running it here might | |
| // cause performance issues. Figure out a better timing/strategy to run it. | |
| return base::ok(); | |
| } | |
| void SqlSharedCacheIsolatedDatabase::Cleanup() { | |
| if (db_assets_ && db_assets_->db().is_open()) { | |
| bool has_entries = false; | |
| { | |
| sql::Statement count_statement(db_assets_->db().GetCachedStatement( | |
| SQL_FROM_HERE, | |
| GetSharedCacheIsolatedDatabaseQuery( | |
| SharedCacheIsolatedDatabaseQuery::kSelectRowidLimit1))); | |
| has_entries = count_statement.Step(); | |
| } | |
| if (!has_entries) { | |
| db_assets_->AbandonAndDeleteFiles(); | |
| } | |
| } | |
| db_assets_.reset(); | |
| } | |
| bool SqlSharedCacheIsolatedDatabase::HasRowForTesting( // IN-TEST | |
| SqlSharedCacheRowId shared_cache_row_id) { | |
| if (!db_assets_ || !db_assets_->db().is_open()) { | |
| return false; | |
| } | |
| sql::Statement statement(db_assets_->db().GetCachedStatement( | |
| SQL_FROM_HERE, | |
| GetSharedCacheIsolatedDatabaseQuery( | |
| SharedCacheIsolatedDatabaseQuery::kSelectUrlAndReadyByRowId))); | |
| statement.BindInt64(0, shared_cache_row_id.value()); | |
| return statement.Step(); | |
| } | |
| } // namespace disk_cache |