blob: 8c391fb3807709ba7ef3c34291b50c4fb02a846c [file] [log] [blame]
// 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_