| // 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_INSTANCE_BUCKET_CONTEXT_H_ |
| #define CONTENT_BROWSER_INDEXED_DB_INSTANCE_BUCKET_CONTEXT_H_ |
| |
| #include <stdint.h> |
| |
| #include <list> |
| #include <memory> |
| #include <optional> |
| #include <queue> |
| #include <string> |
| |
| #include "base/auto_reset.h" |
| #include "base/containers/flat_map.h" |
| #include "base/feature_list.h" |
| #include "base/files/file_path.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/gtest_prod_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/sequence_checker.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "base/trace_event/memory_dump_provider.h" |
| #include "components/services/storage/indexed_db/locks/partitioned_lock_manager.h" |
| #include "components/services/storage/privileged/cpp/bucket_client_info.h" |
| #include "components/services/storage/privileged/mojom/indexed_db_client_state_checker.mojom.h" |
| #include "components/services/storage/privileged/mojom/indexed_db_control_test.mojom.h" |
| #include "components/services/storage/privileged/mojom/indexed_db_internals_types.mojom.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/blob_reader.h" |
| #include "content/browser/indexed_db/indexed_db_data_loss_info.h" |
| #include "content/browser/indexed_db/indexed_db_database_error.h" |
| #include "content/browser/indexed_db/indexed_db_external_object.h" |
| #include "content/browser/indexed_db/status.h" |
| #include "content/common/content_export.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/receiver_set.h" |
| #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h" |
| |
| namespace base { |
| class UpdateableSequencedTaskRunner; |
| } |
| |
| namespace storage { |
| class QuotaManagerProxy; |
| } |
| |
| namespace content::indexed_db { |
| |
| class BackingStore; |
| class BucketContextHandle; |
| class Database; |
| struct PendingConnection; |
| |
| // BucketContext manages the per-bucket IndexedDB state, and other important |
| // context like the backing store and lock manager. |
| // |
| // BucketContext will keep its backing store around while any of these is true: |
| // * There are handles referencing the bucket context, |
| // * There are outstanding blob references to this database's blob files, or |
| // * The bucket context is in-memory (i.e. an incognito profile). |
| // |
| // When these qualities are no longer true, `RunTasks()` will invoke |
| // `ResetBackingStore()`, which returns `this` to an uninitialized state. |
| // |
| // Each instance of this class is created and run on a unique thread pool |
| // SequencedTaskRunner, i.e. the "bucket thread". |
| class CONTENT_EXPORT BucketContext |
| : public blink::mojom::IDBFactory, |
| public base::trace_event::MemoryDumpProvider { |
| public: |
| using DBMap = base::flat_map<std::u16string, std::unique_ptr<Database>>; |
| |
| // `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 { |
| // BucketContext isn't closing. |
| kNotClosing, |
| // BucketContext 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 `BucketContext` and the |
| // broader context that exists per Storage Partition (i.e. BrowserContext). |
| // TODO(crbug.com/40279485): 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 when the bucket context is ready to be destroyed. After this is |
| // called, the bucket context will no longer accept new IDBFactory |
| // connections (receivers in `receivers_`). |
| base::OnceCallback<void()> on_ready_for_destruction; |
| |
| // Called when `BucketContext` can't handle an `AddReceiver()` call: |
| // specifically, if destruction has already been initiated by calling |
| // `on_ready_for_destruction`. |
| base::RepeatingCallback<void( |
| const storage::BucketClientInfo& /*client_info*/, |
| mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker> |
| /*client_state_checker_remote*/, |
| mojo::PendingReceiver<blink::mojom::IDBFactory> /*pending_receiver*/)> |
| on_receiver_bounced; |
| |
| // Called when database content has changed. Technically this is called when |
| // the content *probably will* change --- it's invoked before a transaction |
| // is actually committed --- but it's only used for devtools so accuracy |
| // isn't that important. |
| base::RepeatingCallback<void(const std::u16string& /*database_name*/, |
| const std::u16string& /*object_store_name*/)> |
| on_content_changed; |
| |
| // Called to inform the quota system that an action which may have updated |
| // the amount of disk space used has completed. The parameter is true for |
| // transactions that caused the backing store to flush. |
| base::RepeatingCallback<void(bool /*did_sync*/)> on_files_written; |
| }; |
| |
| BucketContext( |
| storage::BucketInfo bucket_info, |
| const base::FilePath& data_path, |
| Delegate&& delegate, |
| scoped_refptr<base::UpdateableSequencedTaskRunner> updateable_task_runner, |
| scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy, |
| mojo::PendingRemote<storage::mojom::BlobStorageContext> |
| blob_storage_context, |
| mojo::PendingRemote<storage::mojom::FileSystemAccessContext> |
| file_system_access_context); |
| |
| BucketContext(const BucketContext&) = delete; |
| BucketContext& operator=(const BucketContext&) = delete; |
| |
| ~BucketContext() override; |
| |
| // All `BucketContext` instances created during the lifetime of the returned |
| // object will use SQLite iff `use_sqlite` is true. |
| static base::AutoReset<std::optional<bool>> OverrideShouldUseSqliteForTesting( |
| bool use_sqlite); |
| |
| bool ShouldUseSqlite() const { return should_use_sqlite_; } |
| |
| void QueueRunTasks(); |
| |
| // Normally, in-memory bucket contexts never self-close. If this is called |
| // with `doom` set to true, they will self-close. |
| void ForceClose(bool doom, const std::string& message); |
| |
| // Starts capturing state data for indexeddb-internals. The data will be |
| // returned the next time `StopMetadataRecording()` is invoked. |
| void StartMetadataRecording(); |
| std::vector<storage::mojom::IdbBucketMetadataPtr> StopMetadataRecording(); |
| |
| int64_t GetInMemorySize(); |
| |
| 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); |
| |
| // Called whenever some active or new connection's scheduling priority has |
| // changed. |
| void OnConnectionPriorityUpdated(); |
| |
| // Determines the scheduling priority for this bucket (which is the highest of |
| // all active connections). Public for testing. |
| std::optional<int> CalculateSchedulingPriority(); |
| |
| // 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|. |
| // Only used for LevelDB. |
| 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(); |
| } |
| BackingStore* backing_store() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return backing_store_.get(); |
| } |
| const DBMap& GetDatabasesForTesting() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return databases_; |
| } |
| PartitionedLockManager& lock_manager() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return *lock_manager_; |
| } |
| const PartitionedLockManager& lock_manager() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return *lock_manager_; |
| } |
| |
| Delegate& delegate() { return delegate_; } |
| |
| base::OneShotTimer* close_timer() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return &close_timer_; |
| } |
| |
| base::WeakPtr<BucketContext> 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(); |
| } |
| |
| void AddReceiver( |
| const storage::BucketClientInfo& client_info, |
| mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker> |
| client_state_checker_remote, |
| mojo::PendingReceiver<blink::mojom::IDBFactory> pending_receiver); |
| |
| // blink::mojom::IDBFactory implementation: |
| void GetDatabaseInfo(GetDatabaseInfoCallback callback) override; |
| void Open(mojo::PendingAssociatedRemote<blink::mojom::IDBFactoryClient> |
| factory_client, |
| mojo::PendingAssociatedRemote<blink::mojom::IDBDatabaseCallbacks> |
| database_callbacks_remote, |
| const std::u16string& name, |
| int64_t version, |
| mojo::PendingAssociatedReceiver<blink::mojom::IDBTransaction> |
| transaction_receiver, |
| int64_t transaction_id, |
| int scheduling_priority) override; |
| void DeleteDatabase(mojo::PendingAssociatedRemote< |
| blink::mojom::IDBFactoryClient> factory_client, |
| const std::u16string& name, |
| bool force_close) override; |
| |
| // Finishes filling in `info` with data relevant to idb-internals and passes |
| // the result back via the return value. |
| storage::mojom::IdbBucketMetadataPtr FillInMetadata( |
| storage::mojom::IdbBucketMetadataPtr info); |
| // Called when the data used to populate the struct in `FillInMetadata` is |
| // changed in a significant way. |
| void NotifyOfIdbInternalsRelevantChange(); |
| |
| // This exists to facilitate unit tests. Since `this` is owned via a |
| // `SequenceBound`, it's not possible to directly grab pointer to `this`. |
| BucketContext* GetReferenceForTesting(); |
| |
| void FlushBackingStoreForTesting(); |
| void BindMockFailureSingletonForTesting( |
| mojo::PendingReceiver<storage::mojom::MockFailureInjector> receiver); |
| |
| // Called when a fatal error has occurred that should result in tearing down |
| // the backing store. `BucketContext` *may* be synchronously destroyed after |
| // this is invoked. The string, if non-empty, is used as an error message. |
| // `database` is used in SQLite mode only. |
| void OnDatabaseError(Database* database, |
| Status status, |
| const std::string& message); |
| |
| // Called when the backing store has been corrupted. |
| void HandleBackingStoreCorruption(const std::string& error_message); |
| |
| // base::trace_event::MemoryDumpProvider: |
| bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args, |
| base::trace_event::ProcessMemoryDump* pmd) override; |
| |
| bool in_memory() const { return data_path_.empty(); } |
| |
| private: |
| friend BucketContextHandle; |
| friend class BackingStoreTestBase; |
| friend class DatabaseTest; |
| friend class IndexedDBTest; |
| friend class TransactionTest; |
| |
| FRIEND_TEST_ALL_PREFIXES(IndexedDBTest, CompactionKillSwitchWorks); |
| FRIEND_TEST_ALL_PREFIXES(IndexedDBTest, TooLongOrigin); |
| FRIEND_TEST_ALL_PREFIXES(IndexedDBTest, BasicFactoryCreationAndTearDown); |
| FRIEND_TEST_ALL_PREFIXES(IndexedDBTest, TaskRunnerPriority); |
| FRIEND_TEST_ALL_PREFIXES(BucketContextTest, BucketSpaceDecay); |
| FRIEND_TEST_ALL_PREFIXES(BucketContextTest, MetadataRecordingStateHistory); |
| |
| // The data structure that stores everything bound to the receiver. This will |
| // be stored together with the receiver in the `mojo::ReceiverSet`. |
| struct ReceiverContext { |
| ReceiverContext( |
| const storage::BucketClientInfo& client_info, |
| mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker> |
| client_state_checker_remote); |
| |
| ~ReceiverContext(); |
| |
| ReceiverContext(const ReceiverContext&) = delete; |
| ReceiverContext(ReceiverContext&&) noexcept; |
| ReceiverContext& operator=(const ReceiverContext&) = delete; |
| ReceiverContext& operator=(ReceiverContext&&) = delete; |
| |
| const storage::BucketClientInfo client_info; |
| mojo::Remote<storage::mojom::IndexedDBClientStateChecker> |
| client_state_checker_remote; |
| }; |
| |
| Database* CreateAndAddDatabase(const std::u16string& name); |
| |
| void OnHandleCreated(); |
| void OnHandleDestruction(); |
| |
| // Returns true if this bucket context can be closed (no references, no blobs, |
| // and not persisting for incognito). |
| bool CanClose(); |
| |
| void MaybeStartClosing(); |
| void StartClosing(); |
| void CloseNow(); |
| void StartPreCloseTasks(); |
| |
| void RunTasks(); |
| |
| 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(); |
| |
| // Hooks up a `BlobReader` to `receiver` for the blob described by |
| // `blob_info`. |
| void BindBlobReader(const IndexedDBExternalObject& blob_info, |
| mojo::PendingReceiver<blink::mojom::Blob> receiver); |
| // Removes all readers for this file path. |
| void RemoveBoundReaders(const base::FilePath& path); |
| |
| std::tuple<Status, DatabaseError, IndexedDBDataLossInfo> |
| InitBackingStoreIfNeeded(bool create_if_missing); |
| |
| // Destroys `backing_store_` and all associated state. If there are no |
| // receivers remaining, it will also destroy `this`. |
| void ResetBackingStore(); |
| |
| // Called when a receiver from `receiver_set_` has been disconnected. If there |
| // are no receivers left and the backing store is already destroyed, this will |
| // initiate destruction of `this`. |
| void OnReceiverDisconnected(); |
| |
| // Records one tick of Metadata during a metadata recording session. |
| void RecordInternalsSnapshot(); |
| |
| std::string SanitizeErrorMessage(const std::string& message); |
| |
| SEQUENCE_CHECKER(sequence_checker_); |
| |
| const storage::BucketInfo bucket_info_; |
| |
| // The task runner `this` runs on, if `this` runs on a task runner that can be |
| // updated with different task traits (priority). |
| scoped_refptr<base::UpdateableSequencedTaskRunner> updateable_task_runner_; |
| |
| // Base directory for blobs and backing store files. |
| const base::FilePath data_path_; |
| |
| // True if the backing store is SQLite, or would be SQLite if it existed. |
| bool should_use_sqlite_ = false; |
| |
| // 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; |
| |
| bool running_tasks_ = false; |
| |
| ClosingState closing_stage_ = ClosingState::kNotClosing; |
| base::OneShotTimer close_timer_; |
| std::unique_ptr<PartitionedLockManager> lock_manager_; |
| std::unique_ptr<BackingStore> backing_store_; |
| scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy_; |
| |
| // Databases in the backing store which are already loaded/represented by |
| // Database objects. The backing store may have other databases which |
| // have not yet been loaded. |
| uint32_t next_database_id_for_locks_ = 0; |
| DBMap databases_; |
| // This is the refcount for the number of BucketContextHandle's given out for |
| // this bucket context using OpenReference. This is used as closing criteria |
| // for this object, see CanClose. |
| int64_t open_handles_ = 0; |
| |
| // Pending connections are also inputs to the calculated scheduling priority. |
| std::list<base::WeakPtr<PendingConnection>> pending_connections_; |
| |
| // 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`. |
| // 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_; |
| // This map's value type contains a closure which will run on destruction. |
| std::map<base::FilePath, |
| std::tuple<std::unique_ptr<BlobReader>, |
| base::ScopedClosureRunner /*release_callback*/>> |
| file_reader_map_; |
| |
| Delegate delegate_; |
| |
| // In-memory contexts will not self-close until this bit is flipped to true. |
| bool is_doomed_ = false; |
| |
| // True if there's already a task queued to call `RunTasks()`. |
| bool task_run_queued_ = false; |
| |
| std::vector<storage::mojom::IdbBucketMetadataPtr> metadata_recording_buffer_; |
| bool metadata_recording_enabled_ = false; |
| base::Time metadata_recording_start_time_; |
| |
| mojo::ReceiverSet<blink::mojom::IDBFactory, ReceiverContext> receivers_; |
| |
| base::WeakPtrFactory<BucketContext> weak_factory_{this}; |
| }; |
| |
| } // namespace content::indexed_db |
| |
| #endif // CONTENT_BROWSER_INDEXED_DB_INSTANCE_BUCKET_CONTEXT_H_ |