| // 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_H_ | |
| #define NET_DISK_CACHE_SQL_SQL_PERSISTENT_STORE_H_ | |
| #include <atomic> | |
| #include <optional> | |
| #include <set> | |
| #include <variant> | |
| #include "base/containers/flat_set.h" | |
| #include "base/containers/queue.h" | |
| #include "base/functional/callback_forward.h" | |
| #include "base/memory/ref_counted.h" | |
| #include "base/memory/scoped_refptr.h" | |
| #include "base/time/time.h" | |
| #include "base/types/expected.h" | |
| #include "base/types/strong_alias.h" | |
| #include "net/base/cache_type.h" | |
| #include "net/base/net_export.h" | |
| #include "net/base/network_isolation_key.h" | |
| #include "net/disk_cache/backend_cleanup_tracker.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/entry_write_buffer.h" | |
| #include "net/disk_cache/sql/sql_backend_aliases.h" | |
| #include "net/disk_cache/sql/sql_persistent_store_in_memory_index.h" | |
| #include "net/disk_cache/sql/sql_read_cache_memory_monitor.h" | |
| #include "url/gurl.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 FilePath; | |
| class SequencedTaskRunner; | |
| } // namespace base | |
| namespace net { | |
| class GrowableIOBuffer; | |
| class IOBuffer; | |
| class HttpResponseInfo; | |
| } // namespace net | |
| namespace disk_cache { | |
| class BackendCleanupTracker; | |
| class SqlAsyncTaskManager; | |
| class SqlSharedCacheBlobHandle; | |
| class SqlSharedCacheHandle; | |
| class SqlSharedCacheManager; | |
| // This class serves as the main entry point for the SQL-based disk cache's | |
| // persistence layer. It manages multiple database shards to improve | |
| // concurrency, distributing operations across them based on cache entry keys. | |
| // All actual database I/O is performed asynchronously on background task | |
| // runners, with each shard handling its own database file and operations. | |
| class NET_EXPORT_PRIVATE SqlPersistentStore { | |
| public: | |
| class BackendShard; | |
| class Backend; | |
| // Holds metadata for a cache entry that is eligible to be copied into the | |
| // shared cache. | |
| struct NET_EXPORT_PRIVATE SharedCacheEligibleEntry { | |
| SharedCacheEligibleEntry(); | |
| ~SharedCacheEligibleEntry(); | |
| SharedCacheEligibleEntry(const SharedCacheEligibleEntry&) = delete; | |
| SharedCacheEligibleEntry& operator=(const SharedCacheEligibleEntry&) = | |
| delete; | |
| SharedCacheEligibleEntry(SharedCacheEligibleEntry&&); | |
| SharedCacheEligibleEntry& operator=(SharedCacheEligibleEntry&&); | |
| SharedCacheEligibleEntry( | |
| CacheEntryKey key, | |
| GURL url, | |
| std::unique_ptr<net::HttpResponseInfo> response_info, | |
| net::NetworkIsolationKey nik); | |
| // The key identifying the cache entry. | |
| CacheEntryKey key; | |
| // The URL of the cache entry. | |
| GURL url; | |
| // The HTTP response information for the entry. This will be written to | |
| // SqlSharedCacheIsolatedDatabase and made directly accessible from the | |
| // renderer. Note that some fields are omitted compared to what is stored | |
| // in the standard HTTP cache. | |
| std::unique_ptr<net::HttpResponseInfo> response_info; | |
| // The NetworkIsolationKey associated with the entry. | |
| net::NetworkIsolationKey nik; | |
| }; | |
| // The primary key for resources managed in the SqlPersistentStore's resources | |
| // table. | |
| using ResId = SqlPersistentStoreResId; | |
| // A unique identifier for a database shard. | |
| using ShardId = SqlPersistentStoreShardId; | |
| // Represents the error of SqlPersistentStore operation. | |
| // These values are persisted to logs. Entries should not be renumbered and | |
| // numeric values should never be reused. | |
| // | |
| // LINT.IfChange(SqlDiskCacheStoreError) | |
| enum class Error { | |
| kOk = 0, | |
| kFailedToCreateDirectory = 1, | |
| kFailedToOpenDatabase = 2, | |
| kFailedToRazeIncompatibleDatabase = 3, | |
| kFailedToStartTransaction = 4, | |
| kFailedToCommitTransaction = 5, | |
| kFailedToInitializeMetaTable = 6, | |
| kFailedToInitializeSchema = 7, | |
| kFailedToSetEntryCountMetadata = 8, | |
| kFailedToSetTotalSizeMetadata = 9, | |
| kFailedToExecute = 10, | |
| kInvalidData = 11, | |
| kAlreadyExists = 12, | |
| kNotFound = 13, | |
| kInvalidArgument = 14, | |
| kBodyEndMismatch = 15, | |
| kFailedForTesting = 16, | |
| kAborted = 17, | |
| kNotInitialized = 18, | |
| kCheckSumError = 19, | |
| kDatabaseClosed = 20, | |
| kAbortedDueToBrowserActivity = 21, | |
| kFailedToSetAutoVacuum = 22, | |
| kIncrementalVacuumDisabled = 23, | |
| kFailedToInitializeSharedCacheIndexDatabase = 24, | |
| kFailedToSetSharedCacheEnabledMetadata = 25, | |
| kSharedCacheEnabledMismatch = 26, | |
| kMaxValue = kSharedCacheEnabledMismatch | |
| }; | |
| // LINT.ThenChange(//tools/metrics/histograms/metadata/net/enums.xml:SqlDiskCacheStoreError) | |
| // Represents the urgency of cache eviction. | |
| enum class EvictionUrgency { | |
| kNotNeeded, | |
| kIdleTime, | |
| kNeeded, | |
| }; | |
| // Holds information about a specific cache entry. | |
| struct NET_EXPORT_PRIVATE EntryInfo { | |
| EntryInfo(); | |
| ~EntryInfo(); | |
| EntryInfo(EntryInfo&&); | |
| EntryInfo& operator=(EntryInfo&&); | |
| // A unique identifier for this entry instance, used for safe data access. | |
| ResId res_id; | |
| // The last time this entry was used. | |
| base::Time last_used; | |
| // The total size of the entry's body (all data streams). | |
| int64_t body_end = 0; | |
| // The entry's header data (stream 0). | |
| scoped_refptr<net::GrowableIOBuffer> head; | |
| // The resource ID of the shared cache database where the blobs are stored. | |
| std::optional<SqlSharedCacheResourceId> shared_cache_resource_id; | |
| // The shared cache handle and blob handle for accessing shared cache blobs. | |
| // Populated automatically for `OpenEntry` and `OpenOrCreateEntry` when | |
| // `shared_cache_resource_id` is present. | |
| // Note: These handles are omitted (left null) in `OpenNextEntry` for | |
| // performance reasons to avoid asynchronous fetch overhead during | |
| // iteration. | |
| scoped_refptr<SqlSharedCacheHandle> shared_cache_handle; | |
| scoped_refptr<SqlSharedCacheBlobHandle> shared_cache_blob_handle; | |
| // True if the entry was opened, false if it was newly created. | |
| bool opened = false; | |
| }; | |
| // Holds approximate metadata about a cache entry. | |
| struct NET_EXPORT_PRIVATE EntryMetadata { | |
| EntryMetadata(ResId res_id, | |
| base::Time last_used, | |
| std::optional<int64_t> bytes_usage); | |
| ~EntryMetadata(); | |
| EntryMetadata(const EntryMetadata&); | |
| EntryMetadata& operator=(const EntryMetadata&); | |
| EntryMetadata(EntryMetadata&&); | |
| EntryMetadata& operator=(EntryMetadata&&); | |
| ResId res_id; | |
| base::Time last_used; | |
| std::optional<int64_t> bytes_usage; | |
| }; | |
| // Represents the result of a read operation. | |
| struct NET_EXPORT_PRIVATE ReadResult { | |
| ReadResult(); | |
| ~ReadResult(); | |
| ReadResult(const ReadResult&); | |
| ReadResult& operator=(const ReadResult&); | |
| ReadResult(ReadResult&&); | |
| ReadResult& operator=(ReadResult&&); | |
| // The number of bytes successfully read. | |
| int read_bytes = 0; | |
| // Optionally, a buffer containing data read beyond the requested range. | |
| scoped_refptr<net::IOBuffer> cache_buffer; | |
| // The offset within the entry's body where `cache_buffer` starts. | |
| int64_t cache_buffer_offset = 0; | |
| }; | |
| // Holds a resource ID and the ID of the shard it belongs to. | |
| struct NET_EXPORT_PRIVATE ResIdAndShardId { | |
| ResIdAndShardId(ResId res_id, ShardId shard_id); | |
| ResIdAndShardId(); | |
| ~ResIdAndShardId(); | |
| ResIdAndShardId(const ResIdAndShardId&); | |
| ResIdAndShardId& operator=(const ResIdAndShardId&); | |
| ResIdAndShardId(ResIdAndShardId&&); | |
| ResIdAndShardId& operator=(ResIdAndShardId&&); | |
| // Initialized to the maximum value for convenience when iterating through | |
| // entries. | |
| ResId res_id = ResId(std::numeric_limits<int64_t>::max()); | |
| ShardId shard_id = ShardId(0); | |
| }; | |
| // Holds the position of an entry, used for iterating through entries. | |
| using EntryIterator = | |
| base::StrongAlias<class EntryIteratorTag, ResIdAndShardId>; | |
| // Holds information about a specific cache entry, including its `key` and | |
| // an `iterator` which is used when iterating through entries. | |
| struct NET_EXPORT_PRIVATE EntryInfoWithKeyAndIterator { | |
| EntryInfoWithKeyAndIterator(); | |
| ~EntryInfoWithKeyAndIterator(); | |
| EntryInfoWithKeyAndIterator(EntryInfoWithKeyAndIterator&&); | |
| EntryInfoWithKeyAndIterator& operator=(EntryInfoWithKeyAndIterator&&); | |
| EntryInfo info; | |
| CacheEntryKey key; | |
| EntryIterator iterator; | |
| }; | |
| // Holds summary statistics about the cache store. | |
| class StoreStatus { | |
| public: | |
| StoreStatus() = default; | |
| ~StoreStatus() = default; | |
| StoreStatus(const StoreStatus& other) = default; | |
| StoreStatus& operator=(const StoreStatus& other) = default; | |
| StoreStatus(StoreStatus&& other) = default; | |
| StoreStatus& operator=(StoreStatus&& other) = default; | |
| int64_t GetEstimatedDiskUsage() const; | |
| int32_t entry_count = 0; | |
| int64_t total_size = 0; | |
| }; | |
| struct HashAndResId { | |
| CacheEntryKey::Hash hash; | |
| ResId res_id; | |
| }; | |
| using ResIdList = std::vector<ResId>; | |
| using HashAndResIdList = std::vector<HashAndResId>; | |
| // 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, | |
| ResIdList doomed_entry_res_ids); | |
| ~InMemoryIndexAndDoomedResIds(); | |
| InMemoryIndexAndDoomedResIds(InMemoryIndexAndDoomedResIds&& other); | |
| InMemoryIndexAndDoomedResIds& operator=( | |
| InMemoryIndexAndDoomedResIds&& other); | |
| SqlPersistentStoreInMemoryIndex index; | |
| ResIdList doomed_entry_res_ids; | |
| }; | |
| struct NET_EXPORT_PRIVATE EvictionTarget { | |
| EvictionTarget(SqlPersistentStore::ResId res_id, | |
| int64_t entry_size_with_overhead); | |
| ~EvictionTarget(); | |
| EvictionTarget(EvictionTarget&&); | |
| EvictionTarget& operator=(EvictionTarget&&); | |
| EvictionTarget(const EvictionTarget&); | |
| EvictionTarget& operator=(const EvictionTarget&); | |
| bool operator==(const EvictionTarget& other) const; | |
| SqlPersistentStore::ResId res_id; | |
| int64_t entry_size_with_overhead; | |
| }; | |
| using EvictionTargetQueue = base::queue<EvictionTarget>; | |
| // The result of an eviction operation. | |
| struct EvictionResult { | |
| EvictionResult( | |
| Error error, | |
| size_t evicted_entry_count, | |
| std::vector<SqlSharedCacheResourceId> deleted_shared_cache_resources); | |
| ~EvictionResult(); | |
| EvictionResult(EvictionResult&& other); | |
| EvictionResult& operator=(EvictionResult&& other); | |
| Error error; | |
| size_t evicted_entry_count; | |
| std::vector<SqlSharedCacheResourceId> deleted_shared_cache_resources; | |
| }; | |
| struct EvictionResultWithMetadata { | |
| EvictionResultWithMetadata( | |
| EvictionResult result, | |
| EvictionTargetQueue pending_eviction_targets, | |
| std::optional<SqlPersistentStoreInMemoryIndex> index, | |
| StoreStatus store_status, | |
| bool index_mismatch_detected); | |
| ~EvictionResultWithMetadata(); | |
| EvictionResultWithMetadata(EvictionResultWithMetadata&& other); | |
| EvictionResultWithMetadata& operator=(EvictionResultWithMetadata&& other); | |
| EvictionResult result; | |
| EvictionTargetQueue pending_eviction_targets; | |
| std::optional<SqlPersistentStoreInMemoryIndex> index; | |
| StoreStatus store_status; | |
| bool index_mismatch_detected; | |
| }; | |
| // A helper struct to bundle an operation's result with a flag indicating | |
| // whether an eviction check is needed. This allows the background sequence, | |
| // which has direct access to cache size information, to notify the main | |
| // sequence that an eviction might be necessary without requiring an extra | |
| // cross-sequence call to check the cache size. | |
| template <typename ResultType> | |
| struct ResultAndStoreStatus { | |
| ResultAndStoreStatus(ResultType result, const StoreStatus& store_status) | |
| : result(std::move(result)), store_status(store_status) {} | |
| ~ResultAndStoreStatus() = default; | |
| ResultAndStoreStatus(ResultAndStoreStatus&&) = default; | |
| // The actual result of the operation. | |
| ResultType result; | |
| // The store status. | |
| StoreStatus store_status; | |
| }; | |
| using ErrorCallback = base::OnceCallback<void(Error)>; | |
| using Int32Callback = base::OnceCallback<void(int32_t)>; | |
| using Int64Callback = base::OnceCallback<void(int64_t)>; | |
| using EntryInfoOrError = base::expected<EntryInfo, Error>; | |
| using EntryInfoOrErrorCallback = base::OnceCallback<void(EntryInfoOrError)>; | |
| using OptionalEntryInfoWithKeyAndIterator = | |
| std::optional<EntryInfoWithKeyAndIterator>; | |
| using OptionalEntryInfoWithKeyAndIteratorCallback = | |
| base::OnceCallback<void(OptionalEntryInfoWithKeyAndIterator)>; | |
| using ReadResultOrError = base::expected<ReadResult, Error>; | |
| using ReadResultOrErrorCallback = base::OnceCallback<void(ReadResultOrError)>; | |
| using Int64OrError = base::expected<int64_t, Error>; | |
| using Int64OrErrorCallback = base::OnceCallback<void(Int64OrError)>; | |
| using ResIdOrTime = std::variant<ResId, base::Time>; | |
| using EntryMetadataOrError = base::expected<EntryMetadata, Error>; | |
| using EntryMetadataOrErrorCallback = | |
| base::OnceCallback<void(EntryMetadataOrError)>; | |
| using HashAndResIdListOrError = base::expected<HashAndResIdList, Error>; | |
| using HashAndResIdListOrErrorCallback = | |
| base::OnceCallback<void(HashAndResIdListOrError)>; | |
| using ErrorAndStoreStatus = ResultAndStoreStatus<Error>; | |
| using EntryInfoOrErrorAndStoreStatus = ResultAndStoreStatus<EntryInfoOrError>; | |
| using EntryMetadataOrErrorAndStoreStatus = | |
| ResultAndStoreStatus<EntryMetadataOrError>; | |
| using ReadResultOrErrorAndStoreStatus = | |
| ResultAndStoreStatus<ReadResultOrError>; | |
| using ResIdOrError = base::expected<ResId, Error>; | |
| using ResIdOrErrorCallback = base::OnceCallback<void(ResIdOrError)>; | |
| using ResIdOrErrorAndStoreStatus = ResultAndStoreStatus<ResIdOrError>; | |
| using HashAndResIdListOrErrorAndStoreStatus = | |
| ResultAndStoreStatus<HashAndResIdListOrError>; | |
| struct HashAndSharedCacheResource { | |
| CacheEntryKey::Hash hash; | |
| std::optional<SqlSharedCacheResourceId> shared_cache_resource_id; | |
| }; | |
| using HashAndSharedCacheResourceOrError = | |
| base::expected<HashAndSharedCacheResource, Error>; | |
| using HashOrError = base::expected<CacheEntryKey::Hash, Error>; | |
| struct UsageAndHash { | |
| int64_t bytes_usage; | |
| CacheEntryKey::Hash hash; | |
| std::optional<SqlSharedCacheResourceId> shared_cache_resource_id; | |
| }; | |
| using UsageAndHashOrError = base::expected<UsageAndHash, Error>; | |
| using EvictionResultCallback = base::OnceCallback<void(EvictionResult)>; | |
| using EvictionResultWithMetadataCallback = | |
| base::OnceCallback<void(EvictionResultWithMetadata)>; | |
| using DeletedSharedCacheResourceOrError = | |
| base::expected<std::optional<SqlSharedCacheResourceId>, Error>; | |
| using DeletedSharedCacheResourceOrErrorCallback = | |
| base::OnceCallback<void(DeletedSharedCacheResourceOrError)>; | |
| using DeletedSharedCacheResourcesOrError = | |
| base::expected<std::vector<SqlSharedCacheResourceId>, Error>; | |
| using DeletedSharedCacheResourcesOrErrorCallback = | |
| base::OnceCallback<void(DeletedSharedCacheResourcesOrError)>; | |
| struct DeleteLiveEntryResult { | |
| HashAndResIdList deleted_hash_and_res_ids; | |
| std::vector<SqlSharedCacheResourceId> deleted_shared_cache_resources; | |
| }; | |
| using DeleteLiveEntryResultOrError = | |
| base::expected<DeleteLiveEntryResult, Error>; | |
| using DeleteLiveEntryResultOrErrorAndStoreStatus = | |
| ResultAndStoreStatus<DeleteLiveEntryResultOrError>; | |
| using DeleteLiveEntryResultOrErrorCallback = | |
| base::OnceCallback<void(DeleteLiveEntryResultOrError)>; | |
| using InMemoryIndexAndDoomedResIdsOrError = | |
| base::expected<InMemoryIndexAndDoomedResIds, Error>; | |
| // Creates a new instance of the persistent store. The returned object must be | |
| // initialized by calling `Initialize()`. | |
| SqlPersistentStore(const base::FilePath& path, | |
| int64_t max_bytes, | |
| net::CacheType type, | |
| std::vector<scoped_refptr<base::SequencedTaskRunner>> | |
| background_task_runners, | |
| SqlAsyncTaskManager& async_task_manager, | |
| scoped_refptr<BackendCleanupTracker> cleanup_tracker); | |
| ~SqlPersistentStore(); | |
| SqlPersistentStore(const SqlPersistentStore&) = delete; | |
| SqlPersistentStore& operator=(const SqlPersistentStore&) = delete; | |
| // Initializes the store. `callback` will be invoked upon completion. | |
| void Initialize(ErrorCallback callback); | |
| // Opens an entry with the given `key`. If the entry does not exist, it is | |
| // created. `callback` is invoked with the entry information on success or | |
| // an error code on failure. | |
| void OpenOrCreateEntry(const CacheEntryKey& key, | |
| EntryInfoOrErrorCallback callback); | |
| // Opens an existing entry with the given `key`. | |
| // The `callback` is invoked with the entry's information on success. If the | |
| // entry does not exist, the `callback` is invoked with `Error::kNotFound`. | |
| void OpenEntry(const CacheEntryKey& key, EntryInfoOrErrorCallback callback); | |
| // Creates a new entry with the given `key`. `creation_time` is the time the | |
| // entry is created and will be used as the initial `last_used` time. | |
| // The `callback` is invoked with the new entry's information on success. If | |
| // an entry with this key already exists, the callback is invoked with a | |
| // `kAlreadyExists` error. | |
| void CreateEntry(const CacheEntryKey& key, | |
| base::Time creation_time, | |
| EntryInfoOrErrorCallback callback); | |
| // Marks an entry for future deletion. When an entry is "doomed", it is | |
| // immediately removed from the cache's entry count and total size, but its | |
| // data remains on disk until `DeleteDoomedEntry()` is called. The `res_id` | |
| // ensures that only the correct instance of an entry is doomed. | |
| // `accept_index_mismatch` should be set to true if the entry might have | |
| // already been removed from the in-memory index by a concurrent operation | |
| // (e.g., a previous Doom call). | |
| void DoomEntry(const CacheEntryKey& key, | |
| ResId res_id, | |
| bool accept_index_mismatch, | |
| ErrorCallback callback); | |
| // Physically deletes an entry that has been previously marked as doomed. This | |
| // operation completes the deletion process by removing the entry's data from | |
| // the database. The `res_id` ensures that only a specific, doomed instance of | |
| // the entry is deleted. | |
| void DeleteDoomedEntry(const CacheEntryKey& key, | |
| ResId res_id, | |
| ErrorCallback callback); | |
| // Deletes a "live" entry, i.e., an entry whose `doomed` flag is not set. | |
| // This is for use for entries which are not open; open entries should have | |
| // `DoomEntry()` called, and then `DeleteDoomedEntry()` once they're no longer | |
| // in used. | |
| void DeleteLiveEntry(const CacheEntryKey& key, ErrorCallback callback); | |
| // Deletes all entries from the cache. `callback` is invoked on completion. | |
| void DeleteAllEntries(ErrorCallback callback); | |
| // Deletes all "live" (not doomed) entries whose `last_used` time falls | |
| // within the range [`initial_time`, `end_time`), excluding any entries whose | |
| // IDs are present in `excluded_list`. `callback` is invoked on completion. | |
| void DeleteLiveEntriesBetween(base::Time initial_time, | |
| base::Time end_time, | |
| std::vector<ResIdAndShardId> excluded_list, | |
| ErrorCallback callback); | |
| // Updates the `last_used` timestamp for the entry with the specified `key`. | |
| // `callback` is invoked with `kOk` on success, or `kNotFound` if the entry | |
| // does not exist or is already doomed. | |
| void UpdateEntryLastUsedByKey(const CacheEntryKey& key, | |
| base::Time last_used, | |
| ErrorCallback callback); | |
| // Writes data and updates metadata (header and last_used) for an entry in a | |
| // single operation. | |
| // `key` and `res_id` identify the target entry. If `res_id` is std::nullopt, | |
| // a new entry is created. | |
| // `old_body_end`: If provided, indicates that body data should be updated. | |
| // It represents the expected current size of the body. | |
| // `buffer`: contains the body data and offset to write. | |
| // `last_used`: The new last used time. If a new entry is created, this is | |
| // used as the creation time. | |
| // `new_hints`: Optional new hints to set. | |
| // `head_buffer`: Optional new header data. | |
| // `header_size_delta`: The change in header size. | |
| // `doomed_new_entry`: If true, the entry is marked as doomed (deleted) upon | |
| // creation. This parameter is only used when creating a | |
| // new entry. | |
| // `callback`: Invoked with the result of the operation. Returns the resource | |
| // ID on success, or an error code on failure. | |
| void 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, | |
| ResIdOrErrorCallback callback); | |
| // Writes data to an entry's body. This can be used to write new data, | |
| // overwrite existing data, or append to the entry. | |
| // `key`: Identifies the target entry. | |
| // `res_id_or_last_used_time`: Identifies the target entry. If it holds | |
| // `base::Time`, a new entry is created with that | |
| // time as the creation time. Otherwise, it holds | |
| // the `ResId` of the existing entry. | |
| // `old_body_end`: The expected current size of the body. It is used to | |
| // determine whether to trim or truncate existing data, and | |
| // for consistency checks. | |
| // `buffer`: Contains the data and offset to be written. | |
| // `truncate`: If true, the entry's body will be truncated to the end of this | |
| // write. Otherwise, the body size will grow if the write extends | |
| // past the current end. | |
| // `doomed_new_entry`: If true, the entry is marked as doomed (deleted) upon | |
| // creation. This parameter is only used when creating a | |
| // new entry. | |
| // `callback`: Invoked with the result of the operation. Returns the resource | |
| // ID on success, or an error code on failure. | |
| void 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, | |
| bool sparse_write, | |
| int64_t header_size, | |
| ResIdOrErrorCallback callback); | |
| // Reads data from an entry's body. | |
| // `res_id` identifies the entry to read from. | |
| // `offset` is the position within the entry's body to start reading. | |
| // `buffer` is the destination for the read data. | |
| // `buf_len` is the size of `buffer`. | |
| // `body_end` is the logical size of the entry's body. | |
| // If `sparse_reading` is true, the read will stop at the first gap in the | |
| // stored data. If false, gaps will be filled with zeros (meaning read_bytes | |
| // will equal `buf_len` as long as offset + buf_len <= body_end). | |
| // `callback` is invoked with the number of bytes read on success, or an error | |
| // code on failure. | |
| void 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, | |
| ReadResultOrErrorCallback callback); | |
| // Moves the entry's blob data to the shared cache. | |
| // Deletes all blobs associated with `res_id` from the `blobs` table and | |
| // updates the `resources` table entry with `shared_cache_resource_id`. | |
| // `callback` will be invoked with `Error::kOk` on success, or an error code. | |
| void MoveBlobsToSharedCache(const CacheEntryKey& key, | |
| ResId res_id, | |
| SqlSharedCacheResourceId shared_cache_resource_id, | |
| ErrorCallback callback); | |
| // Finds the available contiguous range of data for a given entry. | |
| // `res_id` identifies the entry. | |
| // `offset` is the starting position of the range to check. | |
| // `len` is the length of the range to check. | |
| // `callback` is invoked with the result. The `RangeResult` will contain the | |
| // starting offset and length of the first contiguous block of data found | |
| // within the requested range `[offset, offset + len)`. If no data is found | |
| // in the requested range, the `available_len` in the result will be 0. | |
| void GetEntryAvailableRange(const CacheEntryKey& key, | |
| ResId res_id, | |
| int64_t offset, | |
| int len, | |
| RangeResultCallback callback); | |
| // Calculates the total size of all entries whose `last_used` time falls | |
| // within the range [`initial_time`, `end_time`). The size includes the key, | |
| // header, body data, and a static overhead per entry. `callback` is invoked | |
| // with the total size on success, or an error code on failure. | |
| void CalculateSizeOfEntriesBetween(base::Time initial_time, | |
| base::Time end_time, | |
| Int64OrErrorCallback callback); | |
| // Opens the next cache entry in reverse `res_id` order. This method is used | |
| // for iterating through entries. To fetch all entries, start with a | |
| // default-constructed `iterator`. `callback` receives the entry (or | |
| // `std::nullopt` if no more entries exist). | |
| void OpenNextEntry(const EntryIterator& iterator, | |
| OptionalEntryInfoWithKeyAndIteratorCallback callback); | |
| // Checks if cache eviction should be initiated. This is typically called by | |
| // the backend after an operation that increases the cache size. | |
| EvictionUrgency GetEvictionUrgency(); | |
| // Starts or resumes the eviction process to reduce the cache size. This | |
| // method removes the least recently used entries until the total cache size | |
| // is below the low watermark. Entries with ResId in `excluded_res_ids` | |
| // (typically active entries) will not be evicted. `callback` is invoked upon | |
| // completion. | |
| // | |
| // `excluded_list`: A list of ResIds (typically active entries) to exclude | |
| // from eviction. | |
| // `is_idle_time_eviction`: True if this eviction is triggered by idle time. | |
| // If true, eviction may be aborted if the browser | |
| // becomes active. | |
| // `eviction_abort_flag`: A shared atomic flag that can be set to true to | |
| // signal an abort request. Note that even if this flag | |
| // is set, eviction continues until the cache size | |
| // drops below the high watermark. | |
| // `callback`: Invoked with the result of the eviction. | |
| void StartEviction( | |
| std::vector<ResIdAndShardId> excluded_list, | |
| bool is_idle_time_eviction, | |
| scoped_refptr<base::RefCountedData<std::atomic_bool>> eviction_abort_flag, | |
| ErrorCallback callback); | |
| // Returns true if there is a pending eviction that was paused and needs to be | |
| // resumed. | |
| bool HasPendingEviction() const; | |
| // The maximum size of an individual cache entry's data stream. | |
| int64_t MaxFileSize() const; | |
| // The maximum total size of the cache. | |
| int64_t MaxSize() const; | |
| // Updates the maximum total size of the cache. | |
| void SetMaxSize(int64_t max_bytes); | |
| // Retrieves the count of entries. | |
| // Note that this value may be stale, as it doesn't account for ongoing | |
| // database operations. | |
| int32_t GetEntryCount() const; | |
| // Asynchronously retrieves the count of entries. | |
| // Retrieves the entry count asynchronously, ensuring all pending database | |
| // operations are complete. | |
| void GetEntryCountAsync(Int32Callback callback) const; | |
| // Retrieves the total size of all entries. | |
| // Note that this value may be stale, as it doesn't account for ongoing | |
| // database operations. | |
| int64_t GetSizeOfAllEntries() const; | |
| // Loads the in-memory index if it hasn't been loaded yet. `callback` is | |
| // invoked with the result of the load. If the index is already loaded, the | |
| // callback is run immediately with the previous result. Multiple concurrent | |
| // calls will all receive the same result when the load completes. | |
| void MaybeLoadInMemoryIndex(ErrorCallback callback); | |
| // If there are entries that were doomed in a previous session, this method | |
| // triggers a task to delete them from the database. The cleanup is performed | |
| // in the background. Returns true if a cleanup task was scheduled, and false | |
| // otherwise. `callback` is invoked upon completion of the cleanup task. | |
| bool MaybeRunCleanupDoomedEntries(ErrorCallback callback); | |
| // If the browser is idle and the number of pages recorded in the WAL exceeds | |
| // kSqlDiskCacheIdleCheckpointThreshold, a checkpoint is executed. | |
| void MaybeRunCheckpoint(base::OnceCallback<void(bool)> callback); | |
| // If the browser is idle and the database is configured with incremental | |
| // vacuum, this method runs incremental vacuum to reclaim free pages. It | |
| // vacuums up to N pages (controlled by feature param) at a time in a loop | |
| // while the browser remains idle and `abort_flag` is not set. `callback` is | |
| // invoked when the operation is complete on all shards. | |
| void MaybeRunIncrementalVacuum( | |
| scoped_refptr<base::RefCountedData<std::atomic_bool>> abort_flag, | |
| base::OnceCallback<void(bool)> callback); | |
| // Notifies the SqlSharedCacheManager that the specified shared cache | |
| // resources were deleted from the store shards, so that corresponding entries | |
| // in the shared cache database can be cleaned up. | |
| void OnSharedCacheResourcesDeleted(std::vector<SqlSharedCacheResourceId> ids); | |
| enum class OpenEntryMode { kOpenEntry, kOpenOrCreateEntry }; | |
| // Called when shard opening for `key` finishes. If successful and | |
| // `shared_cache_resource_id` is present on the entry, populates | |
| // `shared_cache_handle` and `shared_cache_blob_handle`. | |
| // If fetching shared cache handles fails, deletes the existing entry from the | |
| // store, and depending on `mode`, either recreates a new entry | |
| // (kOpenOrCreateEntry) or returns kNotFound (kOpenEntry). | |
| void OnOpenEntryFinished(CacheEntryKey key, | |
| OpenEntryMode mode, | |
| EntryInfoOrErrorCallback callback, | |
| EntryInfoOrError result); | |
| // Handles failure when retrieving shared cache handles or blob handles. | |
| // Deletes the invalid entry from the store, and depending on `mode`, either | |
| // recreates a new entry (kOpenOrCreateEntry) or returns kNotFound | |
| // (kOpenEntry). | |
| void OnSharedCacheFetchFailed(const CacheEntryKey& key, | |
| OpenEntryMode mode, | |
| EntryInfoOrErrorCallback callback); | |
| // Wraps an ErrorCallback to handle deletion of a single shared cache | |
| // resource. When the shard operation completes successfully, if a shared | |
| // cache resource was deleted, triggers OnSharedCacheResourcesDeleted. | |
| DeletedSharedCacheResourceOrErrorCallback | |
| WrapCallbackWithSingleSharedCacheDelete(ErrorCallback callback); | |
| // Wraps an ErrorCallback to handle deletion of multiple shared cache | |
| // resources. When the shard operation completes successfully, if shared cache | |
| // resources were deleted, triggers OnSharedCacheResourcesDeleted. | |
| DeletedSharedCacheResourcesOrErrorCallback WrapCallbackWithSharedCacheDelete( | |
| ErrorCallback callback); | |
| enum class IndexState { | |
| // The in-memory index is not available (e.g., not yet loaded or | |
| // invalidated). | |
| kNotReady, | |
| // The index is ready and the hash was found. This may be a false positive. | |
| kHashFound, | |
| // The index is ready, but the hash was not found. | |
| kHashNotFound, | |
| }; | |
| // Synchronously checks the state of a key hash against the in-memory index. | |
| IndexState GetIndexStateForHash(CacheEntryKey::Hash key_hash) const; | |
| // Updates the in-memory index with the given hints for the specified entry. | |
| void SetInMemoryEntryDataHints(CacheEntryKey::Hash key_hash, | |
| ResId res_id, | |
| MemoryEntryDataHints hints); | |
| // Retrieves the hints for the specified entry from the in-memory index, if | |
| // available. | |
| std::optional<MemoryEntryDataHints> GetInMemoryEntryDataHints( | |
| CacheEntryKey::Hash key_hash) const; | |
| // Returns the shard ID for a given cache key hash. | |
| ShardId GetShardIdForHash(CacheEntryKey::Hash key_hash) const; | |
| SqlAsyncTaskManager& GetAsyncTaskManager() const { | |
| return async_task_manager_.get(); | |
| } | |
| SqlSharedCacheManager* GetSharedCacheManager() const { | |
| return shared_cache_manager_.get(); | |
| } | |
| SqlSharedCacheManager* shared_cache_manager_for_testing() { | |
| return shared_cache_manager_.get(); | |
| } | |
| // Enables a strict corruption checking mode for testing purposes. | |
| void EnableStrictCorruptionCheckForTesting(); | |
| // Sets a flag to simulate database operation failures for testing. | |
| void SetSimulateDbFailureForTesting(bool fail); | |
| // Sets a flag to simulate database operation failure for a specific shard. | |
| void SetSimulateDbShardFailureForTesting(size_t shard_index, bool fail); | |
| // Raze the Database and the poison the database handle for testing. This is | |
| // useful for testing the behavior after a catastrophic error. | |
| void RazeAndPoisonForTesting(); | |
| // Sets a hook to be called during eviction, allowing tests to control timing. | |
| void SetEvictionHookForTesting(base::RepeatingClosure hook); | |
| BackendShard& GetShardForTesting(ShardId shard_id) const { | |
| return *backend_shards_[shard_id.value()]; | |
| } | |
| BackendShard& GetShardForTesting(CacheEntryKey::Hash hash) const { | |
| return GetShard(hash); | |
| } | |
| private: | |
| // The result of a successful initialization. | |
| struct NET_EXPORT_PRIVATE InitResult { | |
| InitResult(std::optional<int64_t> max_bytes, | |
| const StoreStatus& store_status, | |
| int64_t database_size, | |
| std::optional<InMemoryIndexAndDoomedResIds> in_memory_data); | |
| ~InitResult(); | |
| InitResult(InitResult&& other); | |
| InitResult& operator=(InitResult&& other); | |
| // max_bytes is set only on the first shard. | |
| std::optional<int64_t> max_bytes; | |
| StoreStatus store_status; | |
| int64_t database_size; | |
| // Used only when features::kSqlDiskCacheLoadIndexOnInit is true. | |
| std::optional<InMemoryIndexAndDoomedResIds> in_memory_data; | |
| }; | |
| using InitResultOrError = base::expected<InitResult, Error>; | |
| using InitResultOrErrorCallback = base::OnceCallback<void(InitResultOrError)>; | |
| base::RepeatingCallback<void(Error)> CreateBarrierErrorCallback( | |
| ErrorCallback callback, | |
| size_t num_tasks = 0); | |
| size_t GetSizeOfShards() const; | |
| BackendShard& GetShard(CacheEntryKey::Hash hash) const; | |
| BackendShard& GetShard(const CacheEntryKey& key) const; | |
| static std::vector<std::unique_ptr<BackendShard>> CreateBackendShards( | |
| const base::FilePath& path, | |
| net::CacheType type, | |
| bool shared_cache_enabled, | |
| scoped_refptr<SqlReadCacheMemoryMonitor> read_cache_memory_monitor, | |
| std::vector<scoped_refptr<base::SequencedTaskRunner>> | |
| background_task_runners, | |
| SqlAsyncTaskManager& async_task_manager, | |
| scoped_refptr<BackendCleanupTracker> cleanup_tracker); | |
| void OnInitializeFinished(ErrorCallback callback, | |
| std::vector<InitResultOrError> results); | |
| void InitializeNextShard(ErrorCallback callback, | |
| std::vector<InitResultOrError> results); | |
| void OnShardInitialized(ErrorCallback callback, | |
| std::vector<InitResultOrError> results, | |
| InitResultOrError result); | |
| void OnLoadInMemoryIndexFinished(Error result); | |
| void StartEvictionInternal( | |
| std::vector<ResIdAndShardId> excluded_list, | |
| bool is_idle_time_eviction, | |
| scoped_refptr<base::RefCountedData<std::atomic_bool>> eviction_abort_flag, | |
| ErrorCallback callback, | |
| Error index_load_result); | |
| void ResumePendingEviction( | |
| std::vector<base::flat_set<SqlPersistentStore::ResId>> | |
| excluded_res_id_sets, | |
| bool is_idle_time_eviction, | |
| scoped_refptr<base::RefCountedData<std::atomic_bool>> eviction_abort_flag, | |
| ErrorCallback callback); | |
| void OnPendingEvictionFinished( | |
| std::vector<base::flat_set<SqlPersistentStore::ResId>> | |
| excluded_res_id_sets, | |
| bool is_idle_time_eviction, | |
| scoped_refptr<base::RefCountedData<std::atomic_bool>> eviction_abort_flag, | |
| base::TimeTicks start_time, | |
| ErrorCallback callback, | |
| std::vector<EvictionResult> results); | |
| void StartNewEviction( | |
| std::vector<base::flat_set<SqlPersistentStore::ResId>> | |
| excluded_res_id_sets, | |
| bool is_idle_time_eviction, | |
| scoped_refptr<base::RefCountedData<std::atomic_bool>> eviction_abort_flag, | |
| ErrorCallback callback); | |
| void OnEvictionFinished(bool is_idle_time_eviction, | |
| base::TimeTicks start_time, | |
| std::vector<EvictionResult> results); | |
| void RunNextCheckpoint(base::OnceCallback<void(bool)> callback, | |
| std::vector<bool> results); | |
| const std::vector<scoped_refptr<base::SequencedTaskRunner>> | |
| background_task_runners_; | |
| const raw_ref<SqlAsyncTaskManager> async_task_manager_; | |
| scoped_refptr<SqlReadCacheMemoryMonitor> read_cache_memory_monitor_; | |
| std::unique_ptr<SqlSharedCacheManager> shared_cache_manager_; | |
| const std::vector<std::unique_ptr<BackendShard>> backend_shards_; | |
| const int64_t user_max_bytes_; | |
| // Cached value of `net::features::kSqlDiskCacheReduceUma`. | |
| const bool reduce_uma_; | |
| int64_t max_bytes_ = 0; | |
| int64_t high_watermark_ = 0; | |
| int64_t idle_time_high_watermark_ = 0; | |
| int64_t low_watermark_ = 0; | |
| int64_t max_file_size_ = 0; | |
| // Whether a cache eviction operation is currently in progress. | |
| bool eviction_in_progress_ = false; | |
| // A callback to be called when the eviction is finished. | |
| ErrorCallback eviction_result_callback_; | |
| // Whether loading of the in-memory index has been triggered. | |
| bool in_memory_load_triggered_ = false; | |
| // The result of the in-memory index load, if it has finished. | |
| std::optional<Error> in_memory_load_result_; | |
| // Callbacks waiting for the in-memory index load to complete. | |
| std::vector<ErrorCallback> pending_in_memory_load_result_callbacks_; | |
| base::WeakPtrFactory<SqlPersistentStore> weak_factory_{this}; | |
| }; | |
| } // namespace disk_cache | |
| #endif // NET_DISK_CACHE_SQL_SQL_PERSISTENT_STORE_H_ |