blob: f778eb90f1a02588ce2859b5afefae2ab28bf0ae [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef NET_DISK_CACHE_SQL_SQL_PERSISTENT_STORE_BACKEND_H_
#define NET_DISK_CACHE_SQL_SQL_PERSISTENT_STORE_BACKEND_H_
#include "base/memory/weak_ptr.h"
#include "net/disk_cache/sql/eviction_candidate_aggregator.h"
#include "net/disk_cache/sql/sql_persistent_store.h"
#include "net/disk_cache/sql/sql_persistent_store_in_memory_index.h"
#include "sql/database.h"
#include "sql/meta_table.h"
namespace sql {
class Statement;
class Transaction;
} // namespace sql
namespace disk_cache {
// The `Backend` class encapsulates all direct interaction with the SQLite
// database. It is designed to be owned by a `base::SequenceBound` and run on a
// dedicated background sequence to avoid blocking the network IO thread.
class SqlPersistentStore::Backend {
public:
// A struct to hold the in-memory index and the list of doomed resource IDs.
// This is used to return both from the backend task that loads them.
struct InMemoryIndexAndDoomedResIds {
InMemoryIndexAndDoomedResIds(
SqlPersistentStoreInMemoryIndex&& index,
std::vector<SqlPersistentStore::ResId> doomed_entry_res_ids);
~InMemoryIndexAndDoomedResIds();
InMemoryIndexAndDoomedResIds(InMemoryIndexAndDoomedResIds&& other);
InMemoryIndexAndDoomedResIds& operator=(
InMemoryIndexAndDoomedResIds&& other);
SqlPersistentStoreInMemoryIndex index;
std::vector<SqlPersistentStore::ResId> doomed_entry_res_ids;
};
using InMemoryIndexAndDoomedResIdsOrError =
base::expected<InMemoryIndexAndDoomedResIds, Error>;
Backend(ShardId shard_id, const base::FilePath& path, net::CacheType type);
Backend(const Backend&) = delete;
Backend& operator=(const Backend&) = delete;
~Backend();
// Initializes the database, including setting up the schema and reading
// metadata. Returns the initialization result on success.
InitResultOrError Initialize(int64_t user_max_bytes,
base::TimeTicks start_time);
int32_t GetEntryCount() const;
EntryInfoOrErrorAndStoreStatus OpenOrCreateEntry(const CacheEntryKey& key,
base::TimeTicks start_time);
OptionalEntryInfoOrError OpenEntry(const CacheEntryKey& key,
base::TimeTicks start_time);
EntryInfoOrErrorAndStoreStatus CreateEntry(const CacheEntryKey& key,
base::Time creation_time,
bool run_existance_check,
base::TimeTicks start_time);
ErrorAndStoreStatus DoomEntry(const CacheEntryKey& key,
ResId res_id,
base::TimeTicks start_time);
ErrorAndStoreStatus DeleteDoomedEntry(const CacheEntryKey& key,
ResId res_id,
base::TimeTicks start_time);
Error DeleteDoomedEntries(ResIdList res_ids_to_delete,
base::TimeTicks start_time);
ResIdListOrErrorAndStoreStatus DeleteLiveEntry(const CacheEntryKey& key,
base::TimeTicks start_time);
ErrorAndStoreStatus DeleteAllEntries(base::TimeTicks start_time);
ResIdListOrErrorAndStoreStatus DeleteLiveEntriesBetween(
base::Time initial_time,
base::Time end_time,
base::flat_set<ResId> excluded_res_ids,
base::TimeTicks start_time);
Error UpdateEntryLastUsedByKey(const CacheEntryKey& key,
base::Time last_used,
base::TimeTicks start_time);
Error UpdateEntryLastUsedByResId(ResId res_id,
base::Time last_used,
base::TimeTicks start_time);
ErrorAndStoreStatus UpdateEntryHeaderAndLastUsed(
const CacheEntryKey& key,
ResId res_id,
base::Time last_used,
scoped_refptr<net::IOBuffer> buffer,
int64_t header_size_delta,
base::TimeTicks start_time);
ErrorAndStoreStatus WriteEntryData(const CacheEntryKey& key,
ResId res_id,
int64_t old_body_end,
int64_t offset,
scoped_refptr<net::IOBuffer> buffer,
int buf_len,
bool truncate,
base::TimeTicks start_time);
IntOrError ReadEntryData(const CacheEntryKey& key,
ResId res_id,
int64_t offset,
scoped_refptr<net::IOBuffer> buffer,
int buf_len,
int64_t body_end,
bool sparse_reading,
base::TimeTicks start_time);
RangeResult GetEntryAvailableRange(ResId res_id,
int64_t offset,
int len,
base::TimeTicks start_time);
Int64OrError CalculateSizeOfEntriesBetween(base::Time initial_time,
base::Time end_time,
base::TimeTicks start_time);
OptionalEntryInfoWithKeyAndIterator OpenNextEntry(
const EntryIterator& iterator,
base::TimeTicks start_time);
void StartEviction(int64_t size_to_be_removed,
base::flat_set<ResId> excluded_res_ids,
bool is_idle_time_eviction,
scoped_refptr<EvictionCandidateAggregator> aggregator,
ResIdListOrErrorAndStoreStatusCallback callback);
InMemoryIndexAndDoomedResIdsOrError LoadInMemoryIndex();
bool MaybeRunCheckpoint();
void EnableStrictCorruptionCheckForTesting() {
strict_corruption_check_enabled_ = true;
}
void SetSimulateDbFailureForTesting(bool fail) {
simulate_db_failure_for_testing_ = fail;
}
void RazeAndPoisonForTesting() {
db_.RazeAndPoison();
store_status_ = StoreStatus();
}
private:
using RangeResultOrError = base::expected<RangeResult, Error>;
using OptionalEntryInfoWithKeyAndIteratorOrError =
base::expected<OptionalEntryInfoWithKeyAndIterator, Error>;
using EvictionCandidateList =
EvictionCandidateAggregator::EvictionCandidateList;
// A helper struct to associate an IOBuffer with a starting offset.
struct BufferWithStart {
BufferWithStart(scoped_refptr<net::IOBuffer> buffer, int64_t start);
~BufferWithStart();
BufferWithStart(BufferWithStart&& other);
BufferWithStart& operator=(BufferWithStart&& other);
scoped_refptr<net::IOBuffer> buffer;
int64_t start;
};
void DatabaseErrorCallback(int error, sql::Statement* statement);
Error InitializeInternal(bool& corruption_detected,
SqlPersistentStoreInMemoryIndex& index,
ResIdList& doomed_entry_res_ids);
EntryInfoOrError OpenOrCreateEntryInternal(const CacheEntryKey& key,
bool& corruption_detected);
OptionalEntryInfoOrError OpenEntryInternal(const CacheEntryKey& key);
EntryInfoOrError CreateEntryInternal(const CacheEntryKey& key,
base::Time creation_time,
bool run_existance_check,
bool& corruption_detected);
Error DoomEntryInternal(ResId res_id, bool& corruption_detected);
Error DeleteDoomedEntryInternal(ResId res_id);
Error DeleteDoomedEntriesInternal(const ResIdList& res_ids_to_delete,
bool& corruption_detected);
ResIdListOrError DeleteLiveEntryInternal(const CacheEntryKey& key,
bool& corruption_detected);
Error DeleteAllEntriesInternal(bool& corruption_detected);
ResIdListOrError DeleteLiveEntriesBetweenInternal(
base::Time initial_time,
base::Time end_time,
const base::flat_set<ResId>& excluded_res_ids,
bool& corruption_detected);
Error UpdateEntryLastUsedByKeyInternal(const CacheEntryKey& key,
base::Time last_used);
Error UpdateEntryLastUsedByResIdInternal(ResId res_id, base::Time last_used);
Error UpdateEntryHeaderAndLastUsedInternal(
const CacheEntryKey& key,
ResId res_id,
base::Time last_used,
scoped_refptr<net::IOBuffer> buffer,
int64_t header_size_delta,
bool& corruption_detected);
Error WriteEntryDataInternal(const CacheEntryKey& key,
ResId res_id,
int64_t old_body_end,
int64_t offset,
scoped_refptr<net::IOBuffer> buffer,
int buf_len,
bool truncate,
bool& corruption_detected);
IntOrError ReadEntryDataInternal(const CacheEntryKey& key,
ResId res_id,
int64_t offset,
scoped_refptr<net::IOBuffer> buffer,
int buf_len,
int64_t body_end,
bool sparse_reading,
bool& corruption_detected);
RangeResultOrError GetEntryAvailableRangeInternal(ResId res_id,
int64_t offset,
int len);
Int64OrError CalculateSizeOfEntriesBetweenInternal(base::Time initial_time,
base::Time end_time);
OptionalEntryInfoWithKeyAndIteratorOrError OpenNextEntryInternal(
const EntryIterator& iterator,
bool& corruption_detected);
InMemoryIndexAndDoomedResIdsOrError LoadInMemoryIndexInternal();
// Trims blobs that overlap with the new write range [offset, end), and
// updates the total size delta.
Error TrimOverlappingBlobs(
const CacheEntryKey& key,
ResId res_id,
int64_t offset,
int64_t end,
bool truncate,
base::CheckedNumeric<int64_t>& checked_total_size_delta,
bool& corruption_detected);
// Truncates data by deleting all blobs that start at or after the given
// offset.
Error TruncateBlobsAfter(
ResId res_id,
int64_t truncate_offset,
base::CheckedNumeric<int64_t>& checked_total_size_delta);
// Inserts a vector of new blobs into the database, and updates the total size
// delta.
Error InsertNewBlobs(const CacheEntryKey& key,
ResId res_id,
const std::vector<BufferWithStart>& new_blobs,
base::CheckedNumeric<int64_t>& checked_total_size_delta);
// Inserts a single new blob into the database, and updates the total size
// delta.
Error InsertNewBlob(const CacheEntryKey& key,
ResId res_id,
int64_t start,
const scoped_refptr<net::IOBuffer>& buffer,
int buf_len,
base::CheckedNumeric<int64_t>& checked_total_size_delta);
// Deletes blobs by their IDs, and updates the total size delta.
Error DeleteBlobsById(const std::vector<int64_t>& blob_ids_to_be_removed,
base::CheckedNumeric<int64_t>& checked_total_size_delta,
bool& corruption_detected);
// Deletes a single blob by its ID, and updates the total size delta.
Error DeleteBlobById(int64_t blob_id,
base::CheckedNumeric<int64_t>& checked_total_size_delta,
bool& corruption_detected);
// Deletes all blobs associated with a given res_id.
Error DeleteBlobsByResId(ResId res_id);
// Deletes all blobs associated with a list of entry res_ids.
Error DeleteBlobsByResIds(const std::vector<ResId>& res_ids);
// Deletes a single resource entry from the `resources` table by its `res_id`.
Error DeleteResourceByResId(ResId res_id);
// Deletes multiple resource entries from the `resources` table by their
// `res_id`s.
Error DeleteResourcesByResIds(const std::vector<ResId>& res_ids);
// Selects a list of eviction candidates from the `resources` table, ordered
// by `last_used` time.
EvictionCandidateList SelectEvictionCandidates(
int64_t size_to_be_removed,
base::flat_set<ResId> excluded_res_ids,
bool is_idle_time_eviction);
// Called by the `EvictionCandidateAggregator` to evict a list of selected
// entries.
void EvictEntries(ResIdListOrErrorAndStoreStatusCallback callback,
bool is_idle_time_eviction,
ResIdList res_ids,
int64_t bytes_usage,
base::TimeTicks post_task_time);
// The internal implementation of `EvictEntries`. Deletes the entries from the
// database and updates the store status.
Error EvictEntriesInternal(const ResIdList& res_ids,
int64_t bytes_usage,
bool is_idle_time_eviction,
bool& corruption_detected);
// Updates the in-memory `store_status_` by `entry_count_delta` and
// `total_size_delta`. If the update results in an overflow or a negative
// value, it recalculates the correct value from the database to recover from
// potential metadata corruption.
// It then updates the meta table values and attempts to commit the
// `transaction`.
// Returns Error::kOk on success, or an error code on failure.
Error UpdateStoreStatusAndCommitTransaction(sql::Transaction& transaction,
int64_t entry_count_delta,
int64_t total_size_delta,
bool& corruption_detected);
// Recalculates the store's status (entry count and total size) directly from
// the database. This is a recovery mechanism used when metadata might be
// inconsistent, e.g., after a numerical overflow.
// Returns Error::kOk on success, or an error code on failure.
Error RecalculateStoreStatusAndCommitTransaction(
sql::Transaction& transaction);
int64_t CalculateResourceEntryCount();
int64_t CalculateTotalSize();
// Checks the database status. Returns Error::kOk on success, or an error
// code if something is wrong.
Error CheckDatabaseStatus();
void MaybeCrashIfCorrupted(bool corruption_detected);
void OnCommitCallback(int pages);
base::FilePath GetDatabaseFilePath() const;
const ShardId shard_id_;
const base::FilePath path_;
const net::CacheType type_;
sql::Database db_;
sql::MetaTable meta_table_;
std::optional<Error> db_init_status_;
StoreStatus store_status_;
bool strict_corruption_check_enabled_ = false;
bool simulate_db_failure_for_testing_ = false;
// The number of pages in the write-ahead log file. This is updated by
// `OnCommitCallback` and reset to 0 after a checkpoint.
int wal_pages_ = 0;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<Backend> weak_factory_{this};
};
} // namespace disk_cache
#endif // NET_DISK_CACHE_SQL_SQL_PERSISTENT_STORE_BACKEND_H_