blob: 34d20dedabd62a1e697854dbbc5884f3184fae91 [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 COMPONENTS_PERSISTENT_CACHE_PERSISTENT_CACHE_COLLECTION_H_
#define COMPONENTS_PERSISTENT_CACHE_PERSISTENT_CACHE_COLLECTION_H_
#include <stdint.h>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include "base/component_export.h"
#include "base/containers/lru_cache.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/gtest_prod_util.h"
#include "base/sequence_checker.h"
#include "base/thread_annotations.h"
#include "base/types/expected.h"
#include "components/persistent_cache/backend.h"
#include "components/persistent_cache/backend_storage.h"
#include "components/persistent_cache/entry_metadata.h"
namespace persistent_cache {
struct BackendParams;
class Entry;
class PersistentCache;
// Use PersistentCacheCollection to seamlessly access multiple PersistentCache
// instances. For example when used instead of double-keying with backends that
// use disk storage this can result in smaller separated files. Unlike
// PersistentCache itself PersistentCacheCollection is not thread-safe in any
// way.
//
// Example:
// PersistentCacheCollection collection(temp_dir.GetPath(), 4096);
// collection.Insert("first_cache_id", "key", value_span);
// collection.Insert("second_cache_id","key", value_span);
// ASSIGN_OR_RETURN(auto entry, collection->Find("first_cache_id", "key"),
// [](persistent_cache::TransactionError error) {
// // Translate error to return type here.
// });
//
// Use PersistentCacheCollection to store and retrieve key-value pairs from
// multiple `PersistentCache`s which are created just-in-time.
//
// PersistentCaches stored in the collection can be shared through exported
// parameters but cannot keep being used after they are evicted from the
// collection. PersistentCacheCollection ensures this doesn't happen by
// automatically abandoning caches when evicted.
class COMPONENT_EXPORT(PERSISTENT_CACHE) PersistentCacheCollection {
public:
static constexpr size_t kDefaultLruCacheCapacity = 100;
PersistentCacheCollection(base::FilePath top_directory,
int64_t target_footprint,
size_t lru_capacity = kDefaultLruCacheCapacity);
PersistentCacheCollection(const PersistentCacheCollection&) = delete;
PersistentCacheCollection& operator=(const PersistentCacheCollection&) =
delete;
~PersistentCacheCollection();
// Pass-through to PersistentCache functions that first select the correct
// cache. `cache_id` must be a US-ASCII string consisting more-or-less of
// lower-case letters, numbers, and select punctuation; see
// `BaseNameFromCacheId()` below for gory details.
base::expected<std::unique_ptr<Entry>, TransactionError> Find(
const std::string& cache_id,
std::string_view key);
base::expected<void, TransactionError> Insert(
const std::string& cache_id,
std::string_view key,
base::span<const uint8_t> content,
EntryMetadata metadata = EntryMetadata{});
// Deletes all files used by the collection, including any present on-disk
// that are not actively in-use.
void DeleteAllFiles();
// Returns params for an independent read-only connection to the persistent
// cache at `cache_id`, or nothing if the cache's backend is not operating or
// the params cannot be exported.
std::optional<BackendParams> ExportReadOnlyBackendParams(
const std::string& cache_id);
// Returns params for an independent read-write connection to the persistent
// cache at `cache_id`, or nothing if the cache's backend is not operating or
// the params cannot be exported.
std::optional<BackendParams> ExportReadWriteBackendParams(
const std::string& cache_id);
private:
struct AbandoningDeleter;
friend class PersistentCacheCollectionTest;
FRIEND_TEST_ALL_PREFIXES(PersistentCacheCollectionTest, BaseNameFromCacheId);
FRIEND_TEST_ALL_PREFIXES(PersistentCacheCollectionTest,
FullAllowedCharacterSetHandled);
FRIEND_TEST_ALL_PREFIXES(PersistentCacheCollectionTest, RetrievalAfterClear);
FRIEND_TEST_ALL_PREFIXES(PersistentCacheCollectionTest,
InstancesAbandonnedOnClear);
// To be called on receiving a transaction error from the cache at `cache_id`.
TransactionError HandleTransactionError(const std::string& cache_id,
TransactionError error);
// Deletes files in the instance's directory from oldest to newest until the
// instance is using no more than 90% of its target footprint.
void ReduceFootPrint();
// Returns the PersistentCache for `cache_id`, creating it if needed. Returns
// nullptr if creation fails.
PersistentCache* GetOrCreateCache(const std::string& cache_id);
// Clears out the LRU map for testing.
void ClearForTesting();
// Returns the basename of the file(s) used by a backend given a cache id. An
// extension MUST be added to a returned basename before use. Returns an empty
// path if `cache_id` contains any character that does not match the following
// regular expression (where '\' escapes the character it precedes):
// "[\n !\"#$&'()*+,\-./0-9:;<=>?@[\\\]_a-z|~]". In other words, `cache_id`
// must be a subset of US-ASCII consisting of newline, space, numbers,
// lower-case letters, and select punctuation.
static base::FilePath BaseNameFromCacheId(const std::string& cache_id);
// Returns a string holding all valid characters for a cache id.
static std::string GetAllAllowedCharactersInCacheIds();
BackendStorage backend_storage_ GUARDED_BY_CONTEXT(sequence_checker_);
// Desired maximum disk footprint for the cache collection in bytes.
const int64_t target_footprint_;
base::HashingLRUCache<std::string,
std::unique_ptr<PersistentCache, AbandoningDeleter>>
persistent_caches_ GUARDED_BY_CONTEXT(sequence_checker_);
// Running tally of how many bytes can be inserted before a footprint
// reduction is triggered.
int64_t bytes_until_footprint_reduction_
GUARDED_BY_CONTEXT(sequence_checker_);
SEQUENCE_CHECKER(sequence_checker_);
};
} // namespace persistent_cache
#endif // COMPONENTS_PERSISTENT_CACHE_PERSISTENT_CACHE_COLLECTION_H_