blob: fadde9f6776ee3e11d0ee8c673273b8fd306a0e6 [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 <atomic>
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "net/disk_cache/sql/entry_write_buffer.h"
#include "net/disk_cache/sql/eviction_candidate_aggregator.h"
#include "net/disk_cache/sql/sql_persistent_store.h"
#include "sql/database.h"
#include "sql/meta_table.h"
namespace sql {
class Statement;
class Transaction;
} // namespace sql
namespace disk_cache {
class SqlReadCacheMemoryMonitor;
// 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:
Backend(ShardId shard_id,
const base::FilePath& path,
net::CacheType type,
scoped_refptr<SqlReadCacheMemoryMonitor> read_cache_memory_monitor);
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);
ResIdOrErrorAndStoreStatus WriteEntryDataAndMetadata(
const CacheEntryKey& key,
std::optional<ResId> res_id,
std::optional<int64_t> old_body_end,
EntryWriteBuffer buffer,
base::Time last_used,
const std::optional<MemoryEntryDataHints>& new_hints,
scoped_refptr<net::IOBuffer> head_buffer,
int64_t header_size_delta,
bool doomed_new_entry,
base::TimeTicks start_time);
ResIdOrErrorAndStoreStatus WriteEntryData(
const CacheEntryKey& key,
const ResIdOrTime& res_id_or_last_used_time,
int64_t old_body_end,
EntryWriteBuffer buffer,
bool truncate,
bool doomed_new_entry,
base::TimeTicks start_time);
ReadResultOrError 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);
// Starts the eviction process.
//
// The process begins by selecting eviction candidates from the database.
// Then, `aggregator->OnCandidate()` is called to aggregate candidates from
// all shards. Finally, `EvictEntries()` is called to delete the selected
// entries.
//
// `size_to_be_removed`: The target size to be removed from this shard. This
// is used to select candidates.
// `excluded_res_ids`: A set of resource IDs to exclude from eviction (e.g.,
// currently active entries).
// `high_priority_res_ids`: A list of resource IDs that should be prioritized
// for staying in the cache.
// `is_idle_time_eviction`: True if this is an eviction triggered by idle
// time. If true, the eviction may be aborted if the
// browser becomes active.
// `aggregator`: The aggregator used to collect and select candidates across
// all shards.
// `abort_flag`: A flag used to signal an abort request. If set to true, the
// eviction process will stop after the mandatory size has been
// removed.
// `remaining_mandatory_size`: The remaining size that *must* be evicted even
// if `abort_flag` is set. This is typically the
// amount needed to bring the cache size below the
// high watermark. Once this value becomes <= 0,
// and `abort_flag` is set, the eviction will
// stop.
// `callback`: Called when the eviction finishes or is aborted.
void StartEviction(
int64_t size_to_be_removed,
base::flat_set<ResId> excluded_res_ids,
std::vector<ResId> high_priority_res_ids,
bool is_idle_time_eviction,
scoped_refptr<EvictionCandidateAggregator> aggregator,
scoped_refptr<base::RefCountedData<std::atomic_bool>> abort_flag,
scoped_refptr<base::RefCountedData<std::atomic_int64_t>>
remaining_mandatory_size,
EvictionResultOrErrorAndStoreStatusCallback callback);
// Resumes a previously paused eviction.
//
// This method continues evicting entries from `eviction_targets` that were
// left over from a previous `StartEviction` or `ResumePendingEviction` call
// that was aborted.
//
// `eviction_targets`: The queue of eviction targets remaining from the
// previous attempt.
// `excluded_res_ids`: A set of resource IDs to exclude from eviction.
// `is_idle_time_eviction`: See `StartEviction`.
// `abort_flag`: See `StartEviction`.
// `remaining_mandatory_size`: See `StartEviction`.
// `start_time`: The time when the resume operation was posted.
EvictionResultOrErrorAndStoreStatus ResumePendingEviction(
EvictionTargetQueue eviction_targets,
base::flat_set<ResId> excluded_res_ids,
bool is_idle_time_eviction,
scoped_refptr<base::RefCountedData<std::atomic_bool>> abort_flag,
scoped_refptr<base::RefCountedData<std::atomic_int64_t>>
remaining_mandatory_size,
base::TimeTicks start_time);
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();
}
void SetEvictionHookForTesting(base::RepeatingClosure hook) {
eviction_hook_ = std::move(hook);
}
private:
using RangeResultOrError = base::expected<RangeResult, Error>;
using OptionalEntryInfoWithKeyAndIteratorOrError =
base::expected<OptionalEntryInfoWithKeyAndIterator, Error>;
using EvictionCandidateList =
EvictionCandidateAggregator::EvictionCandidateList;
using EvictionTargetQueue = SqlPersistentStore::EvictionTargetQueue;
// 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);
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 WriteEntryBodyDataHelper(
const CacheEntryKey& key,
ResId res_id,
int64_t old_body_end,
EntryWriteBuffer buffer,
bool truncate,
int64_t& body_end_delta,
base::CheckedNumeric<int64_t>& checked_total_size_delta,
int64_t& new_body_end,
bool& corruption_detected);
ResIdOrError WriteEntryDataAndMetadataInternal(
const CacheEntryKey& key,
std::optional<ResId> res_id,
std::optional<int64_t> old_body_end,
EntryWriteBuffer buffer,
base::Time last_used,
const std::optional<MemoryEntryDataHints>& new_hints,
scoped_refptr<net::IOBuffer> head_buffer,
int64_t header_size_delta,
bool doomed_new_entry,
bool& corruption_detected);
ResIdOrError WriteEntryDataInternal(
const CacheEntryKey& key,
const ResIdOrTime& res_id_or_last_used_time,
int64_t old_body_end,
EntryWriteBuffer buffer,
bool truncate,
bool doomed_new_entry,
bool& corruption_detected);
ReadResultOrError 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 a single live resource entry from the `resources` table by its
// `res_id` and returns the `bytes_usage` of the deleted entry.
Int64OrError DeleteLiveResourceByResIdReturnUsage(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.
// Entries in `high_priority_res_ids` are less likely to be selected as
// candidates if prioritized caching is enabled.
EvictionCandidateList SelectEvictionCandidates(
int64_t size_to_be_removed,
base::flat_set<ResId> excluded_res_ids,
std::vector<ResId> high_priority_res_ids,
bool is_idle_time_eviction);
// Called by the `EvictionCandidateAggregator` to evict a list of selected
// entries.
void EvictEntries(
EvictionResultOrErrorAndStoreStatusCallback callback,
bool is_idle_time_eviction,
scoped_refptr<base::RefCountedData<std::atomic_bool>> abort_flag,
scoped_refptr<base::RefCountedData<std::atomic_int64_t>>
remaining_mandatory_size,
EvictionTargetQueue eviction_targets,
base::TimeTicks post_task_time);
// A helper function to evict entries.
// `trust_target_size`: If true, it assumes the entry exists and uses the size
// from `eviction_targets` (used for new eviction). If false, entries that are
// not found in the DB are ignored, and the size is retrieved from the DB
// (used for resuming eviction).
EvictionResultOrError EvictEntriesHelper(
EvictionTargetQueue eviction_targets,
const base::flat_set<ResId>& excluded_res_ids,
bool is_idle_time_eviction,
scoped_refptr<base::RefCountedData<std::atomic_bool>> abort_flag,
scoped_refptr<base::RefCountedData<std::atomic_int64_t>>
remaining_mandatory_size,
bool trust_target_size,
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_;
const scoped_refptr<SqlReadCacheMemoryMonitor> read_cache_memory_monitor_;
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_);
// A hook called during eviction for testing purposes.
base::RepeatingClosure eviction_hook_;
base::WeakPtrFactory<Backend> weak_factory_{this};
};
} // namespace disk_cache
#endif // NET_DISK_CACHE_SQL_SQL_PERSISTENT_STORE_BACKEND_H_