// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef COMPONENTS_SQLITE_PROTO_KEY_VALUE_DATA_H_ #define COMPONENTS_SQLITE_PROTO_KEY_VALUE_DATA_H_ #include #include #include #include #include #include #include #include "base/bind.h" #include "base/location.h" #include "base/memory/raw_ptr.h" #include "base/memory/ref_counted.h" #include "base/sequence_checker.h" #include "base/time/time.h" #include "base/timer/timer.h" #include "components/sqlite_proto/key_value_table.h" #include "components/sqlite_proto/table_manager.h" #include "third_party/abseil-cpp/absl/types/optional.h" namespace sqlite_proto { namespace internal { // FakeCompare is a dummy comparator provided so that clients using // KeyValueData objects with unbounded-size caches need not // specify the Compare template parameter, which is used exclusively // for pruning the cache when it would exceed its size bound. template struct FakeCompare { bool operator()(const T& unused_lhs, const T& unused_rhs) { return true; } }; } // namespace internal // The class provides a synchronous access to the data backed by // KeyValueTable. The current implementation caches all the // data in the memory. The cache size is limited by the |max_num_entries| // parameter, using the Compare function to decide which entries should // be evicted. // // NOTE: If the data store is larger than the maximum cache size, it // will be pruned on construction to satisfy the size invariant specified // by |max_num_entries|. If this is undesirable, set a sufficiently high // |max_num_entries| (or pass |max_num_entries| = absl::nullopt for // unbounded size). // // InitializeOnDBSequence() must be called on the DB sequence of the // TableManager. All other methods must be called on UI thread. template > class KeyValueData { public: // Constructor. Parameters: // - |manager| provides an interface for scheduling database tasks for // execution on the database thread. // - |backend| provides the operations for updating and querying the // backing database (to be scheduled and executed using |manager|). // - |max_num_entries|, if given, caps the size of the in-memory cache; // the Compare template parameter requires a meaningful (in particular, // non-default) value iff max_num_entries is non-nullopt. // - |flush_delay| is the interval for which to gather writes and deletes // passing them through to the backing store; a value of zero will // pass writes and deletes through immediately. KeyValueData(scoped_refptr manager, KeyValueTable* backend, absl::optional max_num_entries, base::TimeDelta flush_delay); KeyValueData(const KeyValueData&) = delete; KeyValueData& operator=(const KeyValueData&) = delete; // Must be called on the provided TableManager's DB sequence // before calling all other methods. void InitializeOnDBSequence(); // Assigns data associated with the |key| to |data|. Returns true iff the // |key| exists, false otherwise. |data| pointer may be nullptr to get the // return value only. bool TryGetData(const std::string& key, T* data) const; // Returns a view of all the cached data. (The next write or delete may // invalidate this reference.) const std::map& GetAllCached() { return *data_cache_; } // Assigns data associated with the |key| to |data|. void UpdateData(const std::string& key, const T& data); // Deletes data associated with the |keys| from the database. void DeleteData(const std::vector& keys); // Deletes all entries from the database. void DeleteAllData(); private: struct EntryCompare : private Compare { bool operator()(const std::pair& lhs, const std::pair& rhs) { return Compare::operator()(lhs.second, rhs.second); } }; enum class DeferredOperation { kUpdate, kDelete }; void FlushDataToDisk(); scoped_refptr manager_; raw_ptr> backend_table_; std::unique_ptr> data_cache_; std::unordered_map deferred_updates_; base::RepeatingTimer flush_timer_; const base::TimeDelta flush_delay_; const absl::optional max_num_entries_; EntryCompare entry_compare_; SEQUENCE_CHECKER(sequence_checker_); }; template KeyValueData::KeyValueData(scoped_refptr manager, KeyValueTable* backend, absl::optional max_num_entries, base::TimeDelta flush_delay) : manager_(manager), backend_table_(backend), flush_delay_(flush_delay), max_num_entries_(max_num_entries) {} template void KeyValueData::InitializeOnDBSequence() { DCHECK(manager_->GetTaskRunner()->RunsTasksInCurrentSequence()); auto data_map = std::make_unique>(); manager_->ExecuteDBTaskOnDBSequence( base::BindOnce(&KeyValueTable::GetAllData, backend_table_->AsWeakPtr(), data_map.get())); // To ensure invariant that data_cache_.size() <= max_num_entries_. std::vector keys_to_delete; while (max_num_entries_.has_value() && data_map->size() > *max_num_entries_) { auto entry_to_delete = std::min_element(data_map->begin(), data_map->end(), entry_compare_); keys_to_delete.emplace_back(entry_to_delete->first); data_map->erase(entry_to_delete); } if (!keys_to_delete.empty()) { manager_->ExecuteDBTaskOnDBSequence(base::BindOnce( &KeyValueTable::DeleteData, backend_table_->AsWeakPtr(), std::vector(keys_to_delete))); } data_cache_ = std::move(data_map); } template bool KeyValueData::TryGetData(const std::string& key, T* data) const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(data_cache_); auto it = data_cache_->find(key); if (it == data_cache_->end()) return false; if (data) *data = it->second; return true; } template void KeyValueData::UpdateData(const std::string& key, const T& data) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(data_cache_); auto it = data_cache_->find(key); if (it == data_cache_->end()) { if (data_cache_->size() == max_num_entries_) { auto entry_to_delete = std::min_element( data_cache_->begin(), data_cache_->end(), entry_compare_); deferred_updates_[entry_to_delete->first] = DeferredOperation::kDelete; data_cache_->erase(entry_to_delete); } data_cache_->emplace(key, data); } else { it->second = data; } deferred_updates_[key] = DeferredOperation::kUpdate; if (flush_delay_.is_zero()) { // Flush immediately, only for tests. FlushDataToDisk(); } else if (!flush_timer_.IsRunning()) { flush_timer_.Start(FROM_HERE, flush_delay_, this, &KeyValueData::FlushDataToDisk); } } template void KeyValueData::DeleteData( const std::vector& keys) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(data_cache_); for (const std::string& key : keys) { if (data_cache_->erase(key)) deferred_updates_[key] = DeferredOperation::kDelete; } // Run all deferred updates immediately because it was requested by user. if (!deferred_updates_.empty()) FlushDataToDisk(); } template void KeyValueData::DeleteAllData() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(data_cache_); data_cache_->clear(); deferred_updates_.clear(); // Delete all the content of the database immediately because it was requested // by user. manager_->ScheduleDBTask(FROM_HERE, base::BindOnce(&KeyValueTable::DeleteAllData, backend_table_->AsWeakPtr())); } template void KeyValueData::FlushDataToDisk() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (deferred_updates_.empty()) return; std::vector keys_to_delete; for (const auto& entry : deferred_updates_) { const std::string& key = entry.first; switch (entry.second) { case DeferredOperation::kUpdate: { auto it = data_cache_->find(key); if (it != data_cache_->end()) { manager_->ScheduleDBTask( FROM_HERE, base::BindOnce(&KeyValueTable::UpdateData, backend_table_->AsWeakPtr(), key, it->second)); } break; } case DeferredOperation::kDelete: keys_to_delete.push_back(key); } } if (!keys_to_delete.empty()) { manager_->ScheduleDBTask( FROM_HERE, base::BindOnce(&KeyValueTable::DeleteData, backend_table_->AsWeakPtr(), keys_to_delete)); } deferred_updates_.clear(); } } // namespace sqlite_proto #endif // COMPONENTS_SQLITE_PROTO_KEY_VALUE_DATA_H_