| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_BUCKET_CONTEXT_H_ |
| #define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_BUCKET_CONTEXT_H_ |
| |
| #include <stdint.h> |
| #include <memory> |
| #include <queue> |
| #include <string> |
| |
| #include "base/containers/flat_map.h" |
| #include "base/feature_list.h" |
| #include "base/functional/callback.h" |
| #include "base/gtest_prod_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/sequence_checker.h" |
| #include "base/time/clock.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "components/services/storage/indexed_db/locks/partitioned_lock_manager.h" |
| #include "components/services/storage/public/cpp/buckets/bucket_info.h" |
| #include "components/services/storage/public/cpp/quota_error_or.h" |
| #include "components/services/storage/public/mojom/blob_storage_context.mojom.h" |
| #include "components/services/storage/public/mojom/file_system_access_context.mojom.h" |
| #include "content/browser/indexed_db/indexed_db_bucket_context_handle.h" |
| #include "content/browser/indexed_db/indexed_db_external_object.h" |
| #include "content/browser/indexed_db/indexed_db_task_helper.h" |
| #include "content/common/content_export.h" |
| #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h" |
| #include "third_party/leveldatabase/src/include/leveldb/status.h" |
| |
| namespace storage { |
| class QuotaManagerProxy; |
| } |
| |
| namespace content { |
| class IndexedDBBackingStore; |
| class IndexedDBDatabase; |
| class IndexedDBDataItemReader; |
| class IndexedDBFactory; |
| class IndexedDBPreCloseTaskQueue; |
| |
| constexpr const char kIDBCloseImmediatelySwitch[] = "idb-close-immediately"; |
| |
| // IndexedDBBucketContext manages the per-bucket IndexedDB state, and other |
| // important context like the backing store and lock manager. |
| // |
| // IndexedDBBucketContext will keep itself alive while any of these is true: |
| // * There are handles referencing the factory, |
| // * There are outstanding blob references to this database's blob files, or |
| // * The factory is in an incognito profile. |
| // |
| // When these qualities are no longer true, `RunTasks()` will return |
| // `kCanBeDestroyed` which lets the owning `IndexedDBFactory` know it's time to |
| // delete this object. |
| // |
| // TODO(crbug.com/1474996): it's intended that each bucket gets its own |
| // IndexedDB task runner. To facilitate IndexedDB code running on multiple task |
| // runners, `IndexedDBBucketContext` is in the process of becoming the single |
| // point of communication between classes running on the main task runner, such |
| // as `IndexedDBFactory`, and those that pertain to a specific bucket and |
| // therefore run on a bucket's IDB task runner, such as `IndexedDBDatabase` or |
| // `IndexedDBCursor`. |
| class CONTENT_EXPORT IndexedDBBucketContext { |
| public: |
| using DBMap = |
| base::flat_map<std::u16string, std::unique_ptr<IndexedDBDatabase>>; |
| |
| // Represents a method of `IndexedDBBucketContext` which is not yet bound to a |
| // particular instance of `IndexedDBBucketContext`. This is used for the |
| // `for_each_bucket_context` delegate callback. |
| using InstanceClosure = |
| base::RepeatingCallback<void(IndexedDBBucketContext&)>; |
| |
| // Maximum time interval between runs of the IndexedDBSweeper. Sweeping only |
| // occurs after backing store close. |
| // Visible for testing. |
| static constexpr const base::TimeDelta kMaxEarliestGlobalSweepFromNow = |
| base::Hours(1); |
| // Maximum time interval between runs of the IndexedDBSweeper for a given |
| // bucket. Sweeping only occurs after backing store close. |
| // Visible for testing. |
| static constexpr const base::TimeDelta kMaxEarliestBucketSweepFromNow = |
| base::Days(3); |
| |
| // Maximum time interval between runs of the IndexedDBCompactionTask. |
| // Compaction only occurs after backing store close. |
| // Visible for testing. |
| static constexpr const base::TimeDelta kMaxEarliestGlobalCompactionFromNow = |
| base::Hours(1); |
| // Maximum time interval between runs of the IndexedDBCompactionTask for a |
| // given bucket. Compaction only occurs after backing store close. |
| // Visible for testing. |
| static constexpr const base::TimeDelta kMaxEarliestBucketCompactionFromNow = |
| base::Days(3); |
| |
| // `CheckCanUseDiskSpace` fudges quota values a little. If there is excess |
| // free space, QuotaManager may not be checked the next time a transaction |
| // requests space. The decays over this time period. |
| static constexpr const base::TimeDelta kBucketSpaceCacheTimeLimit = |
| base::Seconds(30); |
| |
| enum class ClosingState { |
| // IndexedDBBucketContext isn't closing. |
| kNotClosing, |
| // IndexedDBBucketContext is pausing for kBackingStoreGracePeriodSeconds |
| // to allow new references to open before closing the backing store. |
| kPreCloseGracePeriod, |
| // The `pre_close_task_queue` is running any pre-close tasks. |
| kRunningPreCloseTasks, |
| kClosed, |
| }; |
| |
| // This structure defines the interface between `IndexedDBBucketContext` and |
| // the broader context that exists per Storage Partition (i.e. |
| // BrowserContext). |
| // TODO(crbug.com/1474996): for now these callbacks execute on the current |
| // sequence, but in the future they should be bound to the main IDB sequence. |
| struct CONTENT_EXPORT Delegate { |
| Delegate(); |
| Delegate(Delegate&&); |
| ~Delegate(); |
| |
| Delegate(const Delegate&) = delete; |
| Delegate& operator=(const Delegate&) = delete; |
| |
| // Called to pump the IDB task queue (generally results in `RunTasks()` |
| // being called). |
| base::RepeatingClosure on_tasks_available; |
| |
| // Called when a fatal error has occurred that should result in tearing down |
| // the backing store. `IndexedDBBucketContext` *may* be synchronously |
| // destroyed after this is invoked. |
| base::RepeatingCallback<void(leveldb::Status)> on_fatal_error; |
| |
| // Called when database content has changed. |
| base::RepeatingCallback<void(const std::u16string& /*database_name*/, |
| const std::u16string& /*object_store_name*/)> |
| on_content_changed; |
| |
| // Called to run a given callback on every bucket context (including the one |
| // in the current sequence and those in other sequences/associated with |
| // other buckets). This method will also be called on every subsequently |
| // created bucket context (see `initialization_closure` in constructor), |
| // until it is replaced by another initialization closure. |
| base::RepeatingCallback<void(InstanceClosure)> for_each_bucket_context; |
| }; |
| |
| // If non-null, `initialization_closure` is immediately run on `this`. If it |
| // is null, `this` will generate a new initialization closure and return it to |
| // the delegate via `for_each_bucket_context`. The delegate, i.e. |
| // `IDBFactory`, will pass a null `InstanceClosure` to the first |
| // `IndexedDBBucketContext` it creates. |
| IndexedDBBucketContext( |
| storage::BucketInfo bucket_info, |
| bool persist_for_incognito, |
| base::Clock* clock, |
| std::unique_ptr<PartitionedLockManager> lock_manager, |
| Delegate&& delegate, |
| std::unique_ptr<IndexedDBBackingStore> backing_store, |
| scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy, |
| scoped_refptr<base::TaskRunner> io_task_runner, |
| mojo::PendingRemote<storage::mojom::BlobStorageContext> |
| blob_storage_context, |
| mojo::PendingRemote<storage::mojom::FileSystemAccessContext> |
| file_system_access_context, |
| InstanceClosure initialization_closure); |
| |
| IndexedDBBucketContext(const IndexedDBBucketContext&) = delete; |
| IndexedDBBucketContext& operator=(const IndexedDBBucketContext&) = delete; |
| |
| ~IndexedDBBucketContext(); |
| |
| void ForceClose(); |
| |
| bool IsClosing() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return closing_stage_ != ClosingState::kNotClosing; |
| } |
| |
| ClosingState closing_stage() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return closing_stage_; |
| } |
| |
| void ReportOutstandingBlobs(bool blobs_outstanding); |
| |
| void StopPersistingForIncognito(); |
| |
| // Runs `method` on `this`. This exists to facilitate running the setter on |
| // the correct sequence. |
| void RunInstanceClosure(InstanceClosure method); |
| |
| // Called when `space_requested` bytes are about to be used by committing a |
| // transaction. Will invoke `disk_space_check_callback` if this usage is |
| // approved, or false if there's insufficient space as per the `QuotaManager`. |
| // If `disk-space_check_callback` is non-null, it will be invoked with the |
| // response. If it is null, the check is considered a dry-run, which warms up |
| // the space cache but doesn't decrement from it. |
| void CheckCanUseDiskSpace( |
| int64_t space_requested, |
| base::OnceCallback<void(bool)> disk_space_check_callback); |
| |
| // Create external objects from |objects| and store the results in |
| // |mojo_objects|. |mojo_objects| must be the same length as |objects|. |
| void CreateAllExternalObjects( |
| const std::vector<IndexedDBExternalObject>& objects, |
| std::vector<blink::mojom::IDBExternalObjectPtr>* mojo_objects); |
| |
| const storage::BucketInfo& bucket_info() { return bucket_info_; } |
| storage::BucketLocator bucket_locator() { |
| return bucket_info_.ToBucketLocator(); |
| } |
| IndexedDBBackingStore* backing_store() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return backing_store_.get(); |
| } |
| const DBMap& databases() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return databases_; |
| } |
| PartitionedLockManager* lock_manager() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return lock_manager_.get(); |
| } |
| const PartitionedLockManager* lock_manager() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return lock_manager_.get(); |
| } |
| IndexedDBPreCloseTaskQueue* pre_close_task_queue() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return pre_close_task_queue_.get(); |
| } |
| |
| Delegate& delegate() { return delegate_; } |
| |
| bool is_running_tasks() const { return running_tasks_; } |
| bool is_task_run_scheduled() const { return task_run_scheduled_; } |
| void set_task_run_scheduled() { task_run_scheduled_ = true; } |
| |
| base::OneShotTimer* close_timer() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return &close_timer_; |
| } |
| |
| enum class RunTasksResult { kDone, kError, kCanBeDestroyed }; |
| std::tuple<RunTasksResult, leveldb::Status> RunTasks(); |
| |
| base::WeakPtr<IndexedDBBucketContext> AsWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| storage::QuotaManagerProxy* quota_manager() { |
| return quota_manager_proxy_.get(); |
| } |
| |
| storage::mojom::BlobStorageContext* blob_storage_context() { |
| return blob_storage_context_.get(); |
| } |
| storage::mojom::FileSystemAccessContext* file_system_access_context() { |
| return file_system_access_context_.get(); |
| } |
| |
| private: |
| friend IndexedDBFactory; |
| friend IndexedDBBucketContextHandle; |
| |
| // Test needs access to ShouldRunTombstoneSweeper. |
| FRIEND_TEST_ALL_PREFIXES(IndexedDBFactoryTestWithMockTime, |
| TombstoneSweeperTiming); |
| |
| // Test needs access to ShouldRunCompaction. |
| FRIEND_TEST_ALL_PREFIXES(IndexedDBFactoryTestWithMockTime, |
| CompactionTaskTiming); |
| |
| // Test needs access to CompactionKillSwitchWorks. |
| FRIEND_TEST_ALL_PREFIXES(IndexedDBFactoryTest, CompactionKillSwitchWorks); |
| |
| FRIEND_TEST_ALL_PREFIXES(IndexedDBBucketContextTest, BucketSpaceDecay); |
| |
| // Used to synchronize the global throttling of LevelDB cleanup operations. |
| // See `for_each_bucket_context`. |
| static void SetInternalState(base::Time earliest_global_sweep_time, |
| base::Time earliest_global_compaction_time, |
| IndexedDBBucketContext& context); |
| |
| IndexedDBDatabase* AddDatabase(const std::u16string& name, |
| std::unique_ptr<IndexedDBDatabase> database); |
| |
| void OnHandleCreated(); |
| void OnHandleDestruction(); |
| |
| // Returns true if this factory can be closed (no references, no blobs, and |
| // not persisting for incognito). |
| bool CanClose(); |
| |
| void MaybeStartClosing(); |
| void StartClosing(); |
| void CloseNow(); |
| void StartPreCloseTasks(); |
| |
| // Executes database operations, and if `true` is returned by this function, |
| // then the current time will be written to the database as the last sweep |
| // time. |
| bool ShouldRunTombstoneSweeper(); |
| |
| // Executes database operations, and if `true` is returned by this function, |
| // then the current time will be written to the database as the last |
| // compaction time. |
| bool ShouldRunCompaction(); |
| |
| void OnGotBucketSpaceRemaining(storage::QuotaErrorOr<int64_t> space_left); |
| |
| // Returns the amount of bucket space `this` has the authority to approve by |
| // decaying `bucket_space_remaining_` according to the amount of time passed |
| // since `bucket_space_remaining_timestamp_`. |
| int64_t GetBucketSpaceToAllot(); |
| |
| // Bind `receiver` to read from the file at `path`. |
| void BindFileReader( |
| const base::FilePath& path, |
| base::Time expected_modification_time, |
| base::OnceClosure release_callback, |
| mojo::PendingReceiver<storage::mojom::BlobDataItemReader> receiver); |
| // Removes all readers for this file path. |
| void RemoveBoundReaders(const base::FilePath& path); |
| |
| SEQUENCE_CHECKER(sequence_checker_); |
| |
| storage::BucketInfo bucket_info_; |
| |
| // True if this factory should be remain alive due to the storage partition |
| // being for incognito mode, and our backing store being in-memory. This is |
| // used as closing criteria for this object, see CanClose. |
| bool persist_for_incognito_; |
| // True if there are blobs referencing this backing store that are still |
| // alive. This is used as closing criteria for this object, see |
| // CanClose. |
| bool has_blobs_outstanding_ = false; |
| bool skip_closing_sequence_ = false; |
| const raw_ptr<base::Clock> clock_; |
| |
| bool running_tasks_ = false; |
| bool task_run_scheduled_ = false; |
| |
| base::Time earliest_global_sweep_time_; |
| base::Time earliest_global_compaction_time_; |
| ClosingState closing_stage_ = ClosingState::kNotClosing; |
| base::OneShotTimer close_timer_; |
| const std::unique_ptr<PartitionedLockManager> lock_manager_; |
| std::unique_ptr<IndexedDBBackingStore> backing_store_; |
| scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy_; |
| |
| DBMap databases_; |
| // This is the refcount for the number of IndexedDBBucketContextHandle's |
| // given out for this factory using OpenReference. This is used as closing |
| // criteria for this object, see CanClose. |
| int64_t open_handles_ = 0; |
| |
| // A queue of callbacks representing `CheckCanUseDiskSpace()` requests. |
| std::queue<std::tuple<int64_t /*space_requested*/, |
| base::OnceCallback<void(bool /*allowed*/)>>> |
| bucket_space_check_callbacks_; |
| // The number of bytes `this` has the authority to approve in response to |
| // `CheckCanUseDiskSpace` requests before the QuotaManager will be consulted |
| // once more. This is a performance optimization. |
| int64_t bucket_space_remaining_ = 0; |
| // Timestamp when `bucket_space_remaining_` was last fetched from the quota |
| // manager. |
| base::TimeTicks bucket_space_remaining_timestamp_; |
| |
| // Members in the following block are used for `CreateAllExternalObjects`. |
| // Shared task runner used to read blob files on. |
| const scoped_refptr<base::TaskRunner> file_task_runner_; |
| // Shared task runner used for async I/O while reading blob files. |
| const scoped_refptr<base::TaskRunner> io_task_runner_; |
| // Mojo connection to `BlobStorageContext`, which runs on the IO thread. |
| mojo::Remote<storage::mojom::BlobStorageContext> blob_storage_context_; |
| // Mojo connection to `FileSystemAccessContextImpl`, which runs on the UI |
| // thread. |
| mojo::Remote<storage::mojom::FileSystemAccessContext> |
| file_system_access_context_; |
| std::map<base::FilePath, std::unique_ptr<IndexedDBDataItemReader>> |
| file_reader_map_; |
| |
| std::unique_ptr<IndexedDBPreCloseTaskQueue> pre_close_task_queue_; |
| |
| Delegate delegate_; |
| |
| base::WeakPtrFactory<IndexedDBBucketContext> weak_factory_{this}; |
| }; |
| |
| } // namespace content |
| |
| #endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_BUCKET_CONTEXT_H_ |