session_proto_db.h 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #ifndef COMPONENTS_SESSION_PROTO_DB_SESSION_PROTO_DB_H_
  5. #define COMPONENTS_SESSION_PROTO_DB_SESSION_PROTO_DB_H_
  6. #include <queue>
  7. #include <string>
  8. #include <vector>
  9. #include "base/bind.h"
  10. #include "base/callback_helpers.h"
  11. #include "base/containers/contains.h"
  12. #include "base/memory/raw_ptr.h"
  13. #include "base/memory/weak_ptr.h"
  14. #include "base/metrics/histogram_functions.h"
  15. #include "base/task/sequenced_task_runner.h"
  16. #include "base/task/thread_pool.h"
  17. #include "base/threading/thread_task_runner_handle.h"
  18. #include "components/commerce/core/proto/persisted_state_db_content.pb.h"
  19. #include "components/keyed_service/core/keyed_service.h"
  20. #include "components/leveldb_proto/public/proto_database.h"
  21. #include "components/leveldb_proto/public/proto_database_provider.h"
  22. #include "components/session_proto_db/session_proto_storage.h"
  23. #include "content/public/browser/browser_context.h"
  24. #include "third_party/abseil-cpp/absl/types/optional.h"
  25. #include "third_party/leveldatabase/src/include/leveldb/options.h"
  26. namespace {
  27. const char kOrphanedDataCountHistogramName[] =
  28. "Tabs.PersistedTabData.Storage.LevelDB.OrphanedDataCount";
  29. } // namespace
  30. class SessionProtoDBTest;
  31. template <typename T>
  32. class SessionProtoDBFactory;
  33. // General purpose per session (BrowserContext/BrowserState), per proto key ->
  34. // proto database where the template is the proto which is being stored. A
  35. // SessionProtoDB should be acquired using SessionProtoDBFactory. SessionProtoDB
  36. // is a wrapper on top of leveldb_proto which:
  37. // - Is specifically for databases which are per session
  38. // (BrowserContext/BrowserState)
  39. // and per proto (leveldb_proto is a proto database which may or may not be
  40. // per BrowserContext/BrowserState).
  41. // - Provides a simplified interface for the use cases that surround
  42. // SessionProtoDB such as providing LoadContentWithPrefix instead of the
  43. // more generic API in
  44. // leveldb_proto which requires a filter to be passed in.
  45. // - Is a KeyedService to support the per session (BrowserContext/BrowserState)
  46. // nature of the database.
  47. template <typename T>
  48. class SessionProtoDB : public KeyedService, public SessionProtoStorage<T> {
  49. public:
  50. using KeyAndValue = std::pair<std::string, T>;
  51. // Callback which is used when content is acquired.
  52. using LoadCallback = base::OnceCallback<void(bool, std::vector<KeyAndValue>)>;
  53. // Used for confirming an operation was completed successfully (e.g.
  54. // insert, delete). This will be invoked on a different SequenceRunner
  55. // to SessionProtoDB.
  56. using OperationCallback = base::OnceCallback<void(bool)>;
  57. // Represents an entry in the database.
  58. using ContentEntry = typename leveldb_proto::ProtoDatabase<T>::KeyEntryVector;
  59. SessionProtoDB(const SessionProtoDB&) = delete;
  60. SessionProtoDB& operator=(const SessionProtoDB&) = delete;
  61. ~SessionProtoDB() override;
  62. // SessionProtoStorage implementation:
  63. void LoadOneEntry(const std::string& key, LoadCallback callback) override;
  64. void LoadAllEntries(LoadCallback callback) override;
  65. void LoadContentWithPrefix(const std::string& key_prefix,
  66. LoadCallback callback) override;
  67. void PerformMaintenance(const std::vector<std::string>& keys_to_keep,
  68. const std::string& key_substring_to_match,
  69. OperationCallback callback) override;
  70. void InsertContent(const std::string& key,
  71. const T& value,
  72. OperationCallback callback) override;
  73. void DeleteOneEntry(const std::string& key,
  74. OperationCallback callback) override;
  75. void DeleteContentWithPrefix(const std::string& key_prefix,
  76. OperationCallback callback) override;
  77. void DeleteAllContent(OperationCallback callback) override;
  78. void Destroy() const override;
  79. private:
  80. friend class ::SessionProtoDBTest;
  81. template <typename U>
  82. friend class ::SessionProtoDBFactory;
  83. // Initializes the database.
  84. SessionProtoDB(content::BrowserContext* browser_context,
  85. leveldb_proto::ProtoDatabaseProvider* proto_database_provider,
  86. const base::FilePath& database_dir,
  87. leveldb_proto::ProtoDbType proto_db_type);
  88. // Used for testing.
  89. SessionProtoDB(
  90. std::unique_ptr<leveldb_proto::ProtoDatabase<T>> storage_database,
  91. scoped_refptr<base::SequencedTaskRunner> task_runner);
  92. // Passes back database status following database initialization.
  93. void OnDatabaseInitialized(leveldb_proto::Enums::InitStatus status);
  94. // Callback when one entry is loaded.
  95. void OnLoadOneEntry(LoadCallback callback,
  96. bool success,
  97. std::unique_ptr<T> entry);
  98. // Callback when content is loaded.
  99. void OnLoadContent(LoadCallback callback,
  100. bool success,
  101. std::unique_ptr<std::vector<T>> content);
  102. // Callback when PerformMaintenance is complete.
  103. void OnPerformMaintenance(OperationCallback callback,
  104. bool success,
  105. std::unique_ptr<std::vector<T>> entries_to_delete);
  106. // Callback when an operation (e.g. insert or delete) is called.
  107. void OnOperationCommitted(OperationCallback callback, bool success);
  108. // Returns true if initialization status of database is not yet known.
  109. bool InitStatusUnknown() const;
  110. // Returns true if the database failed to initialize.
  111. bool FailedToInit() const;
  112. static bool DatabasePrefixFilter(const std::string& key_prefix,
  113. const std::string& key) {
  114. return base::StartsWith(key, key_prefix, base::CompareCase::SENSITIVE);
  115. }
  116. // Browser context associated with SessionProtoDB (SessionProtoDB are per
  117. // BrowserContext).
  118. raw_ptr<content::BrowserContext> browser_context_;
  119. // Status of the database initialization.
  120. absl::optional<leveldb_proto::Enums::InitStatus> database_status_;
  121. // The database for storing content storage information.
  122. std::unique_ptr<leveldb_proto::ProtoDatabase<T>> storage_database_;
  123. // Store operations until the database is initialized at which point
  124. // |deferred_operations_| is flushed and all operations are executed.
  125. std::vector<base::OnceClosure> deferred_operations_;
  126. base::WeakPtrFactory<SessionProtoDB> weak_ptr_factory_{this};
  127. };
  128. template <typename T>
  129. SessionProtoDB<T>::~SessionProtoDB() = default;
  130. template <typename T>
  131. void SessionProtoDB<T>::LoadOneEntry(const std::string& key,
  132. LoadCallback callback) {
  133. if (InitStatusUnknown()) {
  134. deferred_operations_.push_back(base::BindOnce(
  135. &SessionProtoDB::LoadOneEntry, weak_ptr_factory_.GetWeakPtr(), key,
  136. std::move(callback)));
  137. } else if (FailedToInit()) {
  138. base::ThreadPool::PostTask(
  139. FROM_HERE,
  140. base::BindOnce(std::move(callback), false, std::vector<KeyAndValue>()));
  141. } else {
  142. storage_database_->GetEntry(
  143. key,
  144. base::BindOnce(&SessionProtoDB::OnLoadOneEntry,
  145. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  146. }
  147. }
  148. template <typename T>
  149. void SessionProtoDB<T>::LoadAllEntries(LoadCallback callback) {
  150. if (InitStatusUnknown()) {
  151. deferred_operations_.push_back(
  152. base::BindOnce(&SessionProtoDB::LoadAllEntries,
  153. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  154. } else if (FailedToInit()) {
  155. base::ThreadPool::PostTask(
  156. FROM_HERE,
  157. base::BindOnce(std::move(callback), false, std::vector<KeyAndValue>()));
  158. } else {
  159. storage_database_->LoadEntries(
  160. base::BindOnce(&SessionProtoDB::OnLoadContent,
  161. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  162. }
  163. }
  164. template <typename T>
  165. void SessionProtoDB<T>::LoadContentWithPrefix(const std::string& key_prefix,
  166. LoadCallback callback) {
  167. if (InitStatusUnknown()) {
  168. deferred_operations_.push_back(base::BindOnce(
  169. &SessionProtoDB::LoadContentWithPrefix, weak_ptr_factory_.GetWeakPtr(),
  170. key_prefix, std::move(callback)));
  171. } else if (FailedToInit()) {
  172. base::ThreadPool::PostTask(
  173. FROM_HERE,
  174. base::BindOnce(std::move(callback), false, std::vector<KeyAndValue>()));
  175. } else {
  176. storage_database_->LoadEntriesWithFilter(
  177. base::BindRepeating(&DatabasePrefixFilter, key_prefix),
  178. {.fill_cache = false},
  179. /* target_prefix */ "",
  180. base::BindOnce(&SessionProtoDB::OnLoadContent,
  181. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  182. }
  183. }
  184. template <typename T>
  185. void SessionProtoDB<T>::PerformMaintenance(
  186. const std::vector<std::string>& keys_to_keep,
  187. const std::string& key_substring_to_match,
  188. OperationCallback callback) {
  189. if (InitStatusUnknown()) {
  190. deferred_operations_.push_back(base::BindOnce(
  191. &SessionProtoDB::PerformMaintenance, weak_ptr_factory_.GetWeakPtr(),
  192. keys_to_keep, key_substring_to_match, std::move(callback)));
  193. } else if (FailedToInit()) {
  194. base::ThreadPool::PostTask(FROM_HERE,
  195. base::BindOnce(std::move(callback), false));
  196. } else {
  197. // The following could be achieved with UpdateEntriesWithRemoveFilter rather
  198. // than LoadEntriesWithFilter followed by UpdateEntries, however, that would
  199. // not allow metrics to be recorded regarding how much orphaned data was
  200. // identified.
  201. storage_database_->LoadEntriesWithFilter(
  202. base::BindRepeating(
  203. [](const std::vector<std::string>& keys_to_keep,
  204. const std::string& key_substring_to_match,
  205. const std::string& key) {
  206. // Return all keys which where key_substring_to_match is a
  207. // substring of said keys and hasn't been explicitly marked
  208. // not to be removed in keys_to_keep.
  209. return base::Contains(key, key_substring_to_match) &&
  210. !base::Contains(keys_to_keep, key);
  211. },
  212. keys_to_keep, key_substring_to_match),
  213. base::BindOnce(&SessionProtoDB::OnPerformMaintenance,
  214. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  215. }
  216. }
  217. // Inserts a value for a given key and passes the result (success/failure) to
  218. // OperationCallback.
  219. template <typename T>
  220. void SessionProtoDB<T>::InsertContent(const std::string& key,
  221. const T& value,
  222. OperationCallback callback) {
  223. if (InitStatusUnknown()) {
  224. deferred_operations_.push_back(base::BindOnce(
  225. &SessionProtoDB::InsertContent, weak_ptr_factory_.GetWeakPtr(), key,
  226. std::move(value), std::move(callback)));
  227. } else if (FailedToInit()) {
  228. base::ThreadPool::PostTask(FROM_HERE,
  229. base::BindOnce(std::move(callback), false));
  230. } else {
  231. auto contents_to_save = std::make_unique<ContentEntry>();
  232. contents_to_save->emplace_back(key, value);
  233. storage_database_->UpdateEntries(
  234. std::move(contents_to_save),
  235. std::make_unique<std::vector<std::string>>(),
  236. base::BindOnce(&SessionProtoDB::OnOperationCommitted,
  237. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  238. }
  239. }
  240. template <typename T>
  241. void SessionProtoDB<T>::DeleteOneEntry(const std::string& key,
  242. OperationCallback callback) {
  243. if (InitStatusUnknown()) {
  244. deferred_operations_.push_back(base::BindOnce(
  245. &SessionProtoDB::DeleteOneEntry, weak_ptr_factory_.GetWeakPtr(), key,
  246. std::move(callback)));
  247. } else if (FailedToInit()) {
  248. base::ThreadPool::PostTask(FROM_HERE,
  249. base::BindOnce(std::move(callback), false));
  250. } else {
  251. auto keys = std::make_unique<std::vector<std::string>>();
  252. keys->push_back(key);
  253. storage_database_->UpdateEntries(
  254. std::make_unique<ContentEntry>(), std::move(keys),
  255. base::BindOnce(&SessionProtoDB::OnOperationCommitted,
  256. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  257. }
  258. }
  259. // Deletes content in the database, matching all keys which have a prefix
  260. // that matches the key.
  261. template <typename T>
  262. void SessionProtoDB<T>::DeleteContentWithPrefix(const std::string& key_prefix,
  263. OperationCallback callback) {
  264. if (InitStatusUnknown()) {
  265. deferred_operations_.push_back(base::BindOnce(
  266. &SessionProtoDB::DeleteContentWithPrefix,
  267. weak_ptr_factory_.GetWeakPtr(), key_prefix, std::move(callback)));
  268. } else if (FailedToInit()) {
  269. base::ThreadPool::PostTask(FROM_HERE,
  270. base::BindOnce(std::move(callback), false));
  271. } else {
  272. storage_database_->UpdateEntriesWithRemoveFilter(
  273. std::make_unique<ContentEntry>(),
  274. std::move(base::BindRepeating(&DatabasePrefixFilter, key_prefix)),
  275. base::BindOnce(&SessionProtoDB::OnOperationCommitted,
  276. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  277. }
  278. }
  279. // Delete all content in the database.
  280. template <typename T>
  281. void SessionProtoDB<T>::DeleteAllContent(OperationCallback callback) {
  282. if (InitStatusUnknown()) {
  283. deferred_operations_.push_back(
  284. base::BindOnce(&SessionProtoDB::DeleteAllContent,
  285. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  286. } else if (FailedToInit()) {
  287. base::ThreadPool::PostTask(FROM_HERE,
  288. base::BindOnce(std::move(callback), false));
  289. } else {
  290. storage_database_->Destroy(std::move(callback));
  291. }
  292. }
  293. template <typename T>
  294. void SessionProtoDB<T>::Destroy() const {
  295. // TODO(davidjm): Consider calling the factory's disassociate method here.
  296. // This isn't strictly necessary since it will be called when
  297. // the context is destroyed anyway.
  298. }
  299. template <typename T>
  300. SessionProtoDB<T>::SessionProtoDB(
  301. content::BrowserContext* browser_context,
  302. leveldb_proto::ProtoDatabaseProvider* proto_database_provider,
  303. const base::FilePath& database_dir,
  304. leveldb_proto::ProtoDbType proto_db_type)
  305. : SessionProtoStorage<T>(),
  306. browser_context_(browser_context),
  307. database_status_(absl::nullopt),
  308. storage_database_(proto_database_provider->GetDB<T>(
  309. proto_db_type,
  310. database_dir,
  311. base::ThreadPool::CreateSequencedTaskRunner(
  312. {base::MayBlock(), base::TaskPriority::USER_VISIBLE}))) {
  313. static_assert(std::is_base_of<google::protobuf::MessageLite, T>::value,
  314. "T must implement 'google::protobuf::MessageLite'");
  315. storage_database_->Init(base::BindOnce(&SessionProtoDB::OnDatabaseInitialized,
  316. weak_ptr_factory_.GetWeakPtr()));
  317. }
  318. // Used for tests.
  319. template <typename T>
  320. SessionProtoDB<T>::SessionProtoDB(
  321. std::unique_ptr<leveldb_proto::ProtoDatabase<T>> storage_database,
  322. scoped_refptr<base::SequencedTaskRunner> task_runner)
  323. : SessionProtoStorage<T>(),
  324. database_status_(absl::nullopt),
  325. storage_database_(std::move(storage_database)) {
  326. static_assert(std::is_base_of<google::protobuf::MessageLite, T>::value,
  327. "T must implement 'google::protobuf::MessageLite'");
  328. storage_database_->Init(base::BindOnce(&SessionProtoDB::OnDatabaseInitialized,
  329. weak_ptr_factory_.GetWeakPtr()));
  330. }
  331. // Passes back database status following database initialization.
  332. template <typename T>
  333. void SessionProtoDB<T>::OnDatabaseInitialized(
  334. leveldb_proto::Enums::InitStatus status) {
  335. database_status_ =
  336. absl::make_optional<leveldb_proto::Enums::InitStatus>(status);
  337. for (auto& deferred_operation : deferred_operations_) {
  338. std::move(deferred_operation).Run();
  339. }
  340. deferred_operations_.clear();
  341. }
  342. // Callback when one entry is loaded.
  343. template <typename T>
  344. void SessionProtoDB<T>::OnLoadOneEntry(LoadCallback callback,
  345. bool success,
  346. std::unique_ptr<T> entry) {
  347. std::vector<KeyAndValue> results;
  348. if (success && entry) {
  349. results.emplace_back(entry->key(), *entry);
  350. }
  351. std::move(callback).Run(success, std::move(results));
  352. }
  353. // Callback when content is loaded.
  354. template <typename T>
  355. void SessionProtoDB<T>::OnLoadContent(LoadCallback callback,
  356. bool success,
  357. std::unique_ptr<std::vector<T>> content) {
  358. std::vector<KeyAndValue> results;
  359. if (success) {
  360. for (const auto& proto : *content) {
  361. // TODO(crbug.com/1157881) relax requirement for proto to have a key field
  362. // and return key value pairs OnLoadContent.
  363. results.emplace_back(proto.key(), proto);
  364. }
  365. }
  366. std::move(callback).Run(success, std::move(results));
  367. }
  368. template <typename T>
  369. void SessionProtoDB<T>::OnPerformMaintenance(
  370. OperationCallback callback,
  371. bool success,
  372. std::unique_ptr<std::vector<T>> entries_to_delete) {
  373. auto keys_to_delete = std::make_unique<std::vector<std::string>>();
  374. if (success) {
  375. for (const auto& proto : *entries_to_delete) {
  376. keys_to_delete->emplace_back(proto.key());
  377. }
  378. base::UmaHistogramCounts100(kOrphanedDataCountHistogramName,
  379. keys_to_delete->size());
  380. }
  381. auto save_no_entries =
  382. std::make_unique<std::vector<std::pair<std::string, T>>>();
  383. storage_database_->UpdateEntries(
  384. std::move(save_no_entries), std::move(keys_to_delete),
  385. base::BindOnce(&SessionProtoDB::OnOperationCommitted,
  386. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  387. }
  388. // Callback when an operation (e.g. insert or delete) is called.
  389. template <typename T>
  390. void SessionProtoDB<T>::OnOperationCommitted(OperationCallback callback,
  391. bool success) {
  392. std::move(callback).Run(success);
  393. }
  394. // Returns true if initialization status of database is not yet known.
  395. template <typename T>
  396. bool SessionProtoDB<T>::InitStatusUnknown() const {
  397. return database_status_ == absl::nullopt;
  398. }
  399. // Returns true if the database failed to initialize.
  400. template <typename T>
  401. bool SessionProtoDB<T>::FailedToInit() const {
  402. return database_status_.has_value() &&
  403. database_status_.value() != leveldb_proto::Enums::InitStatus::kOK;
  404. }
  405. #endif // COMPONENTS_SESSION_PROTO_DB_SESSION_PROTO_DB_H_