| // 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_BACKEND_IMPL_H_ |
| #define NET_DISK_CACHE_SQL_SQL_BACKEND_IMPL_H_ |
| |
| #include <list> |
| #include <map> |
| #include <queue> |
| #include <set> |
| #include <variant> |
| #include <vector> |
| |
| #include "base/files/file_path.h" |
| #include "base/memory/raw_ref.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/time/time.h" |
| #include "base/types/strong_alias.h" |
| #include "net/base/net_export.h" |
| #include "net/disk_cache/buildflags.h" |
| #include "net/disk_cache/disk_cache.h" |
| #include "net/disk_cache/sql/cache_entry_key.h" |
| #include "net/disk_cache/sql/exclusive_operation_coordinator.h" |
| #include "net/disk_cache/sql/sql_persistent_store.h" |
| #include "third_party/abseil-cpp/absl/container/flat_hash_map.h" |
| |
| // This backend is experimental and only available when the build flag is set. |
| static_assert(BUILDFLAG(ENABLE_DISK_CACHE_SQL_BACKEND)); |
| |
| namespace base { |
| class SequencedTaskRunner; |
| } // namespace base |
| |
| namespace disk_cache { |
| |
| class SqlEntryImpl; |
| |
| // Provides a concrete implementation of the disk cache backend that stores |
| // entries in a SQLite database. This class is responsible for all operations |
| // related to creating, opening, dooming, and enumerating cache entries. |
| // |
| // NOTE: This is currently a skeleton implementation, and some methods are not |
| // yet implemented, returning `net::ERR_NOT_IMPLEMENTED`. |
| class NET_EXPORT_PRIVATE SqlBackendImpl final : public Backend { |
| public: |
| // For a speculatively created entry, this holds `std::nullopt` initially, and |
| // when the entry creation task is complete, it will hold either the `ResId` |
| // on success or an `Error` on failure. Otherwise, it just holds a `ResId`. |
| // Callbacks passed to `SqlBackendImpl::PostOrRunNormalOperation()` with the |
| // entry key can expect this to be populated with either a `ResId` or an |
| // `Error`. |
| using ResIdOrErrorHolder = base::RefCountedData<std::optional< |
| std::variant<SqlPersistentStore::ResId, SqlPersistentStore::Error>>>; |
| |
| // An enumeration of errors that can occur during the fake index file check. |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| // |
| // LINT.IfChange(FakeIndexFileError) |
| enum class FakeIndexFileError { |
| kOkNew = 0, |
| kOkExisting = 1, |
| kCreateFileFailed = 2, |
| kWriteFileFailed = 3, |
| kWrongFileSize = 4, |
| kOpenFileFailed = 5, |
| kReadFileFailed = 6, |
| kWrongMagicNumber = 7, |
| kFailedToCreateDirectory = 8, |
| kMaxValue = kFailedToCreateDirectory, |
| }; |
| // LINT.ThenChange(//tools/metrics/histograms/metadata/net/enums.xml:SqlDiskCacheFakeIndexFileError) |
| |
| SqlBackendImpl(const base::FilePath& path, |
| int64_t max_bytes, |
| net::CacheType cache_type); |
| |
| SqlBackendImpl(const SqlBackendImpl&) = delete; |
| SqlBackendImpl& operator=(const SqlBackendImpl&) = delete; |
| |
| ~SqlBackendImpl() override; |
| |
| // Initializes the backend, which includes initializing the persistent store |
| // and checking for a fake index file. These two operations are performed in |
| // parallel. |
| void Init(CompletionOnceCallback callback); |
| |
| // Backend interface. |
| int64_t MaxFileSize() const override; |
| int32_t GetEntryCount( |
| net::Int32CompletionOnceCallback callback) const override; |
| EntryResult OpenOrCreateEntry(const std::string& key, |
| net::RequestPriority priority, |
| EntryResultCallback callback) override; |
| EntryResult OpenEntry(const std::string& key, |
| net::RequestPriority priority, |
| EntryResultCallback callback) override; |
| EntryResult CreateEntry(const std::string& key, |
| net::RequestPriority priority, |
| EntryResultCallback callback) override; |
| net::Error DoomEntry(const std::string& key, |
| net::RequestPriority priority, |
| CompletionOnceCallback callback) override; |
| net::Error DoomAllEntries(CompletionOnceCallback callback) override; |
| net::Error DoomEntriesBetween(base::Time initial_time, |
| base::Time end_time, |
| CompletionOnceCallback callback) override; |
| net::Error DoomEntriesSince(base::Time initial_time, |
| CompletionOnceCallback callback) override; |
| int64_t CalculateSizeOfAllEntries( |
| Int64CompletionOnceCallback callback) override; |
| int64_t CalculateSizeOfEntriesBetween( |
| base::Time initial_time, |
| base::Time end_time, |
| Int64CompletionOnceCallback callback) override; |
| std::unique_ptr<Iterator> CreateIterator() override; |
| void GetStats(base::StringPairs* stats) override; |
| void OnExternalCacheHit(const std::string& key) override; |
| void OnBrowserIdle() override; |
| |
| // Called by SqlEntryImpl when it's being closed and is not doomed. |
| // Removes the entry from `active_entries_`. |
| void ReleaseActiveEntry(SqlEntryImpl& entry); |
| // Called by SqlEntryImpl when it's being closed and is doomed. |
| // Removes the entry from `doomed_entries_`. |
| void ReleaseDoomedEntry(SqlEntryImpl& entry); |
| |
| // Marks an active entry as doomed and initiates its removal from the store. |
| void DoomActiveEntry(SqlEntryImpl& entry); |
| |
| // Updates the `last_used` timestamp for an entry. |
| void UpdateEntryLastUsed( |
| const CacheEntryKey& key, |
| const scoped_refptr<ResIdOrErrorHolder>& res_id_or_error, |
| base::Time last_used); |
| |
| // Updates the header data and `last_used` timestamp for an entry. |
| void UpdateEntryHeaderAndLastUsed( |
| const CacheEntryKey& key, |
| const scoped_refptr<ResIdOrErrorHolder>& res_id_or_error, |
| base::Time last_used, |
| scoped_refptr<net::GrowableIOBuffer> buffer, |
| int64_t header_size_delta); |
| |
| // Writes data to an entry's body (stream 1). This can be used to write new |
| // data, overwrite existing data, or append to the entry. The operation is |
| // scheduled via the `ExclusiveOperationCoordinator` to ensure proper |
| // serialization. |
| // If the backend is deleted during execution, the callback will be called |
| // with net::ERR_ABORTED. |
| int WriteEntryData(const CacheEntryKey& key, |
| const scoped_refptr<ResIdOrErrorHolder>& res_id_or_error, |
| int64_t old_body_end, |
| int64_t body_end, |
| int64_t offset, |
| scoped_refptr<net::IOBuffer> buffer, |
| int buf_len, |
| bool truncate, |
| CompletionOnceCallback callback); |
| |
| // Reads data from an entry's body (stream 1). The operation is scheduled via |
| // the `ExclusiveOperationCoordinator`. `sparse_reading` controls whether |
| // gaps in the data are filled with zeros or cause the read to stop. |
| // If the backend is deleted during execution, the callback will be called |
| // with net::ERR_ABORTED. |
| int ReadEntryData(const CacheEntryKey& key, |
| const scoped_refptr<ResIdOrErrorHolder>& res_id_or_error, |
| int64_t offset, |
| scoped_refptr<net::IOBuffer> buffer, |
| int buf_len, |
| int64_t body_end, |
| bool sparse_reading, |
| CompletionOnceCallback callback); |
| |
| // Finds the available contiguous range of data for a given entry. The |
| // operation is scheduled via the `ExclusiveOperationCoordinator` to ensure |
| // proper serialization. |
| // If the backend is deleted during execution, the callback will be called |
| // with net::ERR_ABORTED. |
| RangeResult GetEntryAvailableRange( |
| const CacheEntryKey& key, |
| const scoped_refptr<ResIdOrErrorHolder>& res_id_or_error, |
| int64_t offset, |
| int len, |
| RangeResultCallback callback); |
| |
| // Sends a dummy operation through the background task runner via the |
| // operation coordinator, for unit tests. |
| int FlushQueueForTest(CompletionOnceCallback callback); |
| |
| std::vector<scoped_refptr<base::SequencedTaskRunner>>& |
| GetBackgroundTaskRunnersForTest() { |
| return background_task_runners_; |
| } |
| |
| SqlPersistentStore* GetSqlStoreForTest() { return store_.get(); } |
| |
| // Enables a strict corruption checking mode for testing purposes. When |
| // enabled, any detected database corruption will cause an immediate crash |
| // via a `CHECK` failure. This is primarily useful for fuzzers, which can more |
| // easily identify problematic inputs if the process fails fast, rather than |
| // silently recovering. |
| void EnableStrictCorruptionCheckForTesting(); |
| |
| // Returns the current size of the `in_flight_entry_modifications_` map. |
| // This is for testing purposes only. |
| size_t GetSizeOfInFlightEntryModificationsMapForTesting() { |
| return in_flight_entry_modifications_.size(); |
| } |
| |
| int64_t GetOptimisticWriteBufferTotalSizeForTesting() { |
| return optimistic_write_buffer_total_size_; |
| } |
| |
| private: |
| class IteratorImpl; |
| |
| // A RAII helper that ensures `PopInFlightEntryModification` is called when |
| // it goes out of scope. This guarantees that an in-flight modification is |
| // removed from `in_flight_entry_modifications_` once the associated operation |
| // completes or is aborted. |
| using PopInFlightEntryModificationRunner = |
| base::StrongAlias<class PopInFlightEntryModificationRunnerTag, |
| base::ScopedClosureRunner>; |
| |
| // Identifies the type of a entry operation. |
| enum class OpenOrCreateEntryOperationType { |
| kCreateEntry, |
| kOpenEntry, |
| kOpenOrCreateEntry, |
| }; |
| |
| // Represents an in-flight modification to an entry's metadata (e.g., |
| // last_used, header). These modifications are queued and applied when the |
| // entry is re-activated by `Iterator::OpenNextEntry()`. |
| struct InFlightEntryModification { |
| InFlightEntryModification( |
| const scoped_refptr<ResIdOrErrorHolder>& res_id_or_error, |
| base::Time last_used); |
| InFlightEntryModification( |
| const scoped_refptr<ResIdOrErrorHolder>& res_id_or_error, |
| base::Time last_used, |
| scoped_refptr<net::GrowableIOBuffer> head); |
| InFlightEntryModification( |
| const scoped_refptr<ResIdOrErrorHolder>& res_id_or_error, |
| int64_t body_end); |
| ~InFlightEntryModification(); |
| InFlightEntryModification(InFlightEntryModification&&); |
| |
| scoped_refptr<ResIdOrErrorHolder> res_id_or_error; |
| std::optional<base::Time> last_used; |
| std::optional<scoped_refptr<net::GrowableIOBuffer>> head; |
| std::optional<int64_t> body_end; |
| }; |
| |
| void OnInitialized(CompletionOnceCallback callback, |
| const std::vector<bool>& results); |
| void RunDelayedPostInitializationTasks(); |
| |
| SqlEntryImpl* GetActiveEntry(const CacheEntryKey& key); |
| |
| // Checks if the cache size has exceeded the high watermark and, if so, |
| // schedules an eviction task. This is typically called after operations that |
| // might increase the cache size. The eviction itself is run as an exclusive |
| // operation to prevent conflicts with other cache activities. |
| void MaybeTriggerEviction(bool is_idle_time_eviction); |
| |
| // Internal helper for Open/Create/OpenOrCreate operations. It uses |
| // `ExclusiveOperationCoordinator` to serialize operations on the same key and |
| // to correctly handle synchronous vs. asynchronous returns. |
| EntryResult OpenOrCreateEntryInternal(OpenOrCreateEntryOperationType type, |
| const std::string& key, |
| EntryResultCallback callback); |
| |
| // Handles the backend logic for Open/Create/OpenOrCreate operations. This |
| // method is scheduled as a normal operation via the |
| // `ExclusiveOperationCoordinator` to ensure proper serialization against |
| // other operations on the same key. |
| void HandleOpenOrCreateEntryOperation( |
| OpenOrCreateEntryOperationType type, |
| const CacheEntryKey& entry_key, |
| EntryResultCallback callback, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle); |
| |
| // Callback for store operations that return an EntryInfo (`OpenOrCreate()`, |
| // `Create()`). |
| void OnEntryOperationFinished( |
| const CacheEntryKey& key, |
| EntryResultCallback callback, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle, |
| SqlPersistentStore::EntryInfoOrError result); |
| // Callback for store operations that return an optional<EntryInfo> |
| // (`Open()`). |
| void OnOptionalEntryOperationFinished( |
| const CacheEntryKey& key, |
| EntryResultCallback callback, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle, |
| SqlPersistentStore::OptionalEntryInfoOrError result); |
| |
| // Creates a new entry speculatively and returns it immediately. The actual |
| // database insertion is performed in the background. |
| EntryResult SpeculativeCreateEntry( |
| const CacheEntryKey& entry_key, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle); |
| |
| // Called when the background database operation for a speculative entry |
| // creation is finished. |
| void OnSpeculativeCreateEntryFinished( |
| const scoped_refptr<ResIdOrErrorHolder>& res_id_or_error, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle, |
| SqlPersistentStore::EntryInfoOrError result); |
| |
| // Handles the backend logic for `DoomActiveEntry()`. This method is scheduled |
| // as a normal operation via the `ExclusiveOperationCoordinator`. |
| void HandleDoomActiveEntryOperation( |
| scoped_refptr<SqlEntryImpl> entry, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle); |
| |
| // Dooms an active entry. This method must be called while holding an |
| // `ExclusiveOperationCoordinator::OperationHandle` to ensure proper |
| // serialization of operations on the entry. |
| void DoomActiveEntryInternal(SqlEntryImpl& entry, |
| CompletionOnceCallback callback); |
| |
| // Handles the backend logic for `ReleaseDoomedEntry()`. This method is |
| // scheduled as a normal operation via the `ExclusiveOperationCoordinator`. |
| void HandleDeleteDoomedEntry( |
| const CacheEntryKey& key, |
| const scoped_refptr<ResIdOrErrorHolder>& res_id_or_error, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle); |
| |
| // Handles the backend logic for `DoomEntry()`. This method is scheduled as a |
| // normal operation via the `ExclusiveOperationCoordinator`. |
| void HandleDoomEntryOperation( |
| const CacheEntryKey& key, |
| net::RequestPriority priority, |
| CompletionOnceCallback callback, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle); |
| |
| // Handles the backend logic for `DoomEntriesBetween()`. This method is |
| // scheduled as an exclusive operation via the |
| // `ExclusiveOperationCoordinator`. |
| void HandleDoomEntriesBetweenOperation( |
| base::Time initial_time, |
| base::Time end_time, |
| CompletionOnceCallback callback, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle); |
| |
| // Handles the backend logic for `CalculateSizeOfEntriesBetween()`. This |
| // method is scheduled as an exclusive operation via the |
| // `ExclusiveOperationCoordinator`. |
| void HandleCalculateSizeOfEntriesBetweenOperation( |
| base::Time initial_time, |
| base::Time end_time, |
| Int64CompletionOnceCallback callback, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle); |
| |
| // Handles the backend logic for `UpdateEntryLastUsed()`. This method is |
| // scheduled as a normal operation via the `ExclusiveOperationCoordinator`. |
| void HandleUpdateEntryLastUsedOperation( |
| const CacheEntryKey& key, |
| const scoped_refptr<ResIdOrErrorHolder>& res_id_or_error, |
| base::Time last_used, |
| PopInFlightEntryModificationRunner pop_in_flight_entry_modification, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle); |
| |
| // Handles the backend logic for `UpdateEntryHeaderAndLastUsed()`. This method |
| // is scheduled as a normal operation via the `ExclusiveOperationCoordinator`. |
| void HandleUpdateEntryHeaderAndLastUsedOperation( |
| const CacheEntryKey& key, |
| const scoped_refptr<ResIdOrErrorHolder>& res_id_or_error, |
| base::Time last_used, |
| scoped_refptr<net::GrowableIOBuffer> buffer, |
| int64_t header_size_delta, |
| PopInFlightEntryModificationRunner pop_in_flight_entry_modification, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle); |
| |
| // Handles the backend logic for a non-optimistic write operation. This method |
| // is scheduled as a normal operation via the `ExclusiveOperationCoordinator` |
| // and forwards the call to the persistent store. |
| void HandleWriteEntryDataOperation( |
| const CacheEntryKey& key, |
| const scoped_refptr<ResIdOrErrorHolder>& res_id_or_error, |
| int64_t old_body_end, |
| int64_t offset, |
| scoped_refptr<net::IOBuffer> buffer, |
| int buf_len, |
| bool truncate, |
| SqlPersistentStore::ErrorCallback callback, |
| PopInFlightEntryModificationRunner pop_in_flight_entry_modification, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle); |
| |
| // Handles the backend logic for an optimistic write operation. This method is |
| // scheduled as a normal operation via the `ExclusiveOperationCoordinator` and |
| // forwards the write to the persistent store. |
| void HandleOptimisticWriteEntryDataOperation( |
| const CacheEntryKey& key, |
| const scoped_refptr<ResIdOrErrorHolder>& res_id_or_error, |
| int64_t old_body_end, |
| int64_t offset, |
| scoped_refptr<net::IOBuffer> buffer, |
| int buf_len, |
| bool truncate, |
| SqlPersistentStore::ErrorCallback maybe_update_res_id_or_error_callback, |
| PopInFlightEntryModificationRunner pop_in_flight_entry_modification, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle); |
| |
| // Called when an optimistic write is finished. `buf_len` is the memory usage |
| // consumed for the optimistic write. |
| void OnOptimisticWriteFinished( |
| const CacheEntryKey& key, |
| SqlPersistentStore::ResId res_id, |
| int buf_len, |
| SqlPersistentStore::ErrorCallback maybe_update_res_id_or_error_callback, |
| PopInFlightEntryModificationRunner pop_in_flight_entry_modification, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle, |
| SqlPersistentStore::Error result); |
| |
| // Handles the backend logic for `ReadEntryData()`. This method is scheduled |
| // as a normal operation via the `ExclusiveOperationCoordinator` and forwards |
| // the call to the persistent store. |
| void HandleReadEntryDataOperation( |
| const CacheEntryKey& key, |
| const scoped_refptr<ResIdOrErrorHolder>& res_id_or_error, |
| int64_t offset, |
| scoped_refptr<net::IOBuffer> buffer, |
| int buf_len, |
| int64_t body_end, |
| bool sparse_reading, |
| SqlPersistentStore::IntOrErrorCallback callback, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle); |
| |
| // Handles the backend logic for `GetEntryAvailableRange()`. This method is |
| // scheduled as a normal operation via the `ExclusiveOperationCoordinator` |
| // and forwards the call to the persistent store. |
| void HandleGetEntryAvailableRangeOperation( |
| const CacheEntryKey& key, |
| const scoped_refptr<ResIdOrErrorHolder>& res_id_or_error, |
| int64_t offset, |
| int len, |
| RangeResultCallback callback, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle); |
| |
| // Handles the backend logic for cache eviction. This method is scheduled as |
| // an exclusive operation to ensure no other cache activities are running. It |
| // gathers the keys of all active entries to prevent them from being evicted |
| // and then delegates the actual eviction logic to the persistent store. |
| void HandleTriggerEvictionOperation( |
| bool is_idle_time_eviction, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle); |
| |
| // Handles the backend logic for `OnExternalCacheHit()`. This method is |
| // scheduled as a normal operation via the `ExclusiveOperationCoordinator`. |
| void HandleOnExternalCacheHitOperation( |
| const CacheEntryKey& key, |
| base::Time now, |
| PopInFlightEntryModificationRunner pop_in_flight_entry_modification, |
| std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle> handle); |
| |
| // Adds an `InFlightEntryModification` to the queue for `entry_key` and |
| // returns a `PopInFlightEntryModificationRunner`. The returned runner ensures |
| // that the modification is removed from the queue when the runner goes out of |
| // scope, guaranteeing proper cleanup even in error paths. |
| PopInFlightEntryModificationRunner PushInFlightEntryModification( |
| const CacheEntryKey& entry_key, |
| InFlightEntryModification in_flight_entry_modification); |
| |
| // Removes the oldest `InFlightEntryModification` from the queue for |
| // `entry_key`. This is called by `PopInFlightEntryModificationRunner` when |
| // an operation completes or is aborted. |
| void PopInFlightEntryModification(const CacheEntryKey& entry_key); |
| |
| // Applies in-flight modifications to an entry's info. |
| void ApplyInFlightEntryModifications( |
| const CacheEntryKey& key, |
| SqlPersistentStore::EntryInfo& entry_info); |
| |
| const base::FilePath path_; |
| |
| // Task runners for background SQLite operations. |
| std::vector<scoped_refptr<base::SequencedTaskRunner>> |
| background_task_runners_; |
| |
| // The persistent store that manages the SQLite database. |
| std::unique_ptr<SqlPersistentStore> store_; |
| |
| // Map of cache keys to currently active (opened) entries. |
| // `raw_ref` is used because the SqlEntryImpl objects are ref-counted and |
| // their lifetime is managed by their ref_count. This map only holds |
| // non-owning references to them. |
| std::map<CacheEntryKey, raw_ref<SqlEntryImpl>> active_entries_; |
| |
| // Set of entries that have been marked as doomed but are still active |
| // (i.e., have outstanding references). |
| std::set<raw_ref<const SqlEntryImpl>> doomed_entries_; |
| |
| // Coordinates exclusive and normal operations to ensure that exclusive |
| // operations have exclusive access. |
| ExclusiveOperationCoordinator exclusive_operation_coordinator_; |
| |
| // Queue of in-flight entry modifications that need to be applied. |
| // These are typically updates to `last_used` or header data that occur |
| // while an entry is not actively open. |
| std::map<CacheEntryKey, std::list<InFlightEntryModification>> |
| in_flight_entry_modifications_; |
| |
| // A flag to prevent queuing multiple eviction operations. It is set to true |
| // when an eviction operation is posted to the `ExclusiveOperationCoordinator` |
| // and reset to false when the operation begins execution. This ensures that |
| // even if `MaybeTriggerEviction()` is called multiple times while an eviction |
| // task is pending, only one will be in the queue at any time. |
| bool eviction_operation_queued_ = false; |
| |
| // The memory usage consumed for optimistic writes. Optimistic writes are |
| // performed as long as this value does not exceed |
| // `kSqlDiskCacheOptimisticWriteBufferSize`. |
| int64_t optimistic_write_buffer_total_size_ = 0; |
| |
| // Weak pointer factory for this class. |
| base::WeakPtrFactory<SqlBackendImpl> weak_factory_{this}; |
| }; |
| |
| } // namespace disk_cache |
| |
| #endif // NET_DISK_CACHE_SQL_SQL_BACKEND_IMPL_H_ |