| // Copyright 2019 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. |
| |
| #include "content/browser/indexed_db/indexed_db_connection_coordinator.h" |
| |
| #include <set> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "content/browser/indexed_db/indexed_db_callback_helpers.h" |
| #include "content/browser/indexed_db/indexed_db_callbacks.h" |
| #include "content/browser/indexed_db/indexed_db_database.h" |
| #include "content/browser/indexed_db/indexed_db_database_callbacks.h" |
| #include "content/browser/indexed_db/indexed_db_database_error.h" |
| #include "content/browser/indexed_db/indexed_db_leveldb_coding.h" |
| #include "content/browser/indexed_db/indexed_db_origin_state.h" |
| #include "content/browser/indexed_db/leveldb/leveldb_env.h" |
| #include "content/browser/indexed_db/leveldb/transactional_leveldb_database.h" |
| #include "content/browser/indexed_db/leveldb/transactional_leveldb_transaction.h" |
| #include "content/browser/indexed_db/scopes/leveldb_scope.h" |
| #include "content/browser/indexed_db/scopes/leveldb_scopes.h" |
| #include "content/browser/indexed_db/scopes/scopes_lock_manager.h" |
| #include "third_party/blink/public/common/indexeddb/indexeddb_metadata.h" |
| |
| using base::ASCIIToUTF16; |
| using base::NumberToString16; |
| using blink::IndexedDBDatabaseMetadata; |
| using leveldb::Status; |
| |
| namespace content { |
| namespace { |
| enum class RequestState { |
| kNotStarted, |
| kPendingNoConnections, |
| kPendingLocks, |
| kPendingTransactionComplete, |
| kError, |
| kDone, |
| }; |
| } // namespace |
| |
| // This represents what script calls an 'IDBOpenDBRequest' - either a database |
| // open or delete call. These may be blocked on other connections. After every |
| // callback, the request must call |
| // IndexedDBConnectionCoordinator::RequestComplete() or be expecting a further |
| // callback. |
| class IndexedDBConnectionCoordinator::ConnectionRequest { |
| public: |
| ConnectionRequest(IndexedDBOriginStateHandle origin_state_handle, |
| IndexedDBDatabase* db, |
| IndexedDBConnectionCoordinator* connection_coordinator, |
| TasksAvailableCallback tasks_available_callback) |
| : origin_state_handle_(std::move(origin_state_handle)), |
| db_(db), |
| connection_coordinator_(connection_coordinator), |
| tasks_available_callback_(std::move(tasks_available_callback)) {} |
| |
| virtual ~ConnectionRequest() {} |
| |
| // Called when the request makes it to the front of the queue. The state() |
| // will be checked after this call, so there is no need to run the |
| // |tasks_available_callback_|. |
| virtual void Perform(bool has_connections) = 0; |
| |
| // Called if a front-end signals that it is ignoring a "versionchange" |
| // event. This should result in firing a "blocked" event at the request. |
| virtual void OnVersionChangeIgnored() const = 0; |
| |
| // Called when a connection is closed; if it corresponds to this connection, |
| // need to do cleanup. |
| // Not called during a force close. |
| virtual void OnConnectionClosed(IndexedDBConnection* connection) = 0; |
| |
| // Called when there are no connections to the database. |
| virtual void OnNoConnections() = 0; |
| |
| // Called when the transaction should be bound. |
| virtual void CreateAndBindTransaction() = 0; |
| |
| // Called when the upgrade transaction has started executing. |
| virtual void UpgradeTransactionStarted(int64_t old_version) = 0; |
| |
| // Called when the upgrade transaction has finished. |
| virtual void UpgradeTransactionFinished(bool committed) = 0; |
| |
| // Called on all pending tasks during a force close. Returns if the task |
| // should be pruned (removed) from the task queue during the force close and |
| // any leveldb error that may have occurred while shutting down the connection |
| // request. |
| virtual std::tuple<bool, leveldb::Status> ShouldPruneForForceClose() = 0; |
| |
| RequestState state() const { return state_; } |
| |
| // Relevant if state() is kError. |
| leveldb::Status status() const { return saved_leveldb_status_; } |
| |
| protected: |
| RequestState state_ = RequestState::kNotStarted; |
| |
| IndexedDBOriginStateHandle origin_state_handle_; |
| // This is safe because IndexedDBDatabase owns this object. |
| IndexedDBDatabase* db_; |
| |
| // Rawptr safe because IndexedDBConnectionCoordinator owns this object. |
| IndexedDBConnectionCoordinator* connection_coordinator_; |
| |
| TasksAvailableCallback tasks_available_callback_; |
| |
| leveldb::Status saved_leveldb_status_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ConnectionRequest); |
| }; |
| |
| class IndexedDBConnectionCoordinator::OpenRequest |
| : public IndexedDBConnectionCoordinator::ConnectionRequest { |
| public: |
| OpenRequest(IndexedDBOriginStateHandle origin_state_handle, |
| IndexedDBDatabase* db, |
| std::unique_ptr<IndexedDBPendingConnection> pending_connection, |
| IndexedDBConnectionCoordinator* connection_coordinator, |
| |
| TasksAvailableCallback tasks_available_callback) |
| : ConnectionRequest(std::move(origin_state_handle), |
| db, |
| connection_coordinator, |
| |
| std::move(tasks_available_callback)), |
| pending_(std::move(pending_connection)) { |
| db_->metadata_.was_cold_open = pending_->was_cold_open; |
| } |
| |
| // Note: the |tasks_available_callback_| is NOT called here because the state |
| // is checked after this method. |
| void Perform(bool has_connections) override { |
| if (db_->metadata().id == kInvalidDatabaseId) { |
| // The database was deleted then immediately re-opened; Open() |
| // recreates it in the backing store. |
| saved_leveldb_status_ = db_->OpenInternal(); |
| if (!saved_leveldb_status_.ok()) { |
| // TODO(jsbell): Consider including sanitized leveldb status message. |
| base::string16 message; |
| if (pending_->version == IndexedDBDatabaseMetadata::NO_VERSION) { |
| message = ASCIIToUTF16( |
| "Internal error opening database with no version specified."); |
| } else { |
| message = |
| ASCIIToUTF16("Internal error opening database with version ") + |
| NumberToString16(pending_->version); |
| } |
| pending_->callbacks->OnError(IndexedDBDatabaseError( |
| blink::kWebIDBDatabaseExceptionUnknownError, message)); |
| state_ = RequestState::kError; |
| return; |
| } |
| |
| DCHECK_EQ(IndexedDBDatabaseMetadata::NO_VERSION, db_->metadata().version); |
| } |
| |
| const int64_t old_version = db_->metadata().version; |
| int64_t& new_version = pending_->version; |
| |
| bool is_new_database = old_version == IndexedDBDatabaseMetadata::NO_VERSION; |
| |
| if (new_version == IndexedDBDatabaseMetadata::DEFAULT_VERSION) { |
| // For unit tests only - skip upgrade steps. (Calling from script with |
| // DEFAULT_VERSION throws exception.) |
| DCHECK(is_new_database); |
| pending_->callbacks->OnSuccess( |
| db_->CreateConnection(std::move(origin_state_handle_), |
| pending_->database_callbacks, |
| pending_->execution_context), |
| db_->metadata_); |
| state_ = RequestState::kDone; |
| return; |
| } |
| |
| if (!is_new_database && |
| (new_version == old_version || |
| new_version == IndexedDBDatabaseMetadata::NO_VERSION)) { |
| pending_->callbacks->OnSuccess( |
| db_->CreateConnection(std::move(origin_state_handle_), |
| pending_->database_callbacks, |
| pending_->execution_context), |
| db_->metadata_); |
| state_ = RequestState::kDone; |
| return; |
| } |
| |
| if (new_version == IndexedDBDatabaseMetadata::NO_VERSION) { |
| // If no version is specified and no database exists, upgrade the |
| // database version to 1. |
| DCHECK(is_new_database); |
| new_version = 1; |
| } else if (new_version < old_version) { |
| // Requested version is lower than current version - fail the request. |
| DCHECK(!is_new_database); |
| pending_->callbacks->OnError(IndexedDBDatabaseError( |
| blink::kWebIDBDatabaseExceptionVersionError, |
| ASCIIToUTF16("The requested version (") + |
| NumberToString16(pending_->version) + |
| ASCIIToUTF16(") is less than the existing version (") + |
| NumberToString16(db_->metadata_.version) + ASCIIToUTF16(")."))); |
| state_ = RequestState::kDone; |
| return; |
| } |
| |
| // Requested version is higher than current version - upgrade needed. |
| DCHECK_GT(new_version, old_version); |
| |
| if (!has_connections) { |
| std::vector<ScopesLockManager::ScopeLockRequest> lock_requests = { |
| {kDatabaseRangeLockLevel, GetDatabaseLockRange(db_->metadata_.id), |
| ScopesLockManager::LockType::kExclusive}}; |
| state_ = RequestState::kPendingLocks; |
| db_->lock_manager_->AcquireLocks( |
| std::move(lock_requests), lock_receiver_.weak_factory.GetWeakPtr(), |
| base::BindOnce( |
| &IndexedDBConnectionCoordinator::OpenRequest::StartUpgrade, |
| weak_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| // There are outstanding connections - fire "versionchange" events and |
| // wait for the connections to close. Front end ensures the event is not |
| // fired at connections that have close_pending set. A "blocked" event |
| // will be fired at the request when one of the connections acks that the |
| // "versionchange" event was ignored. |
| DCHECK_NE(pending_->data_loss_info.status, |
| blink::mojom::IDBDataLoss::Total); |
| state_ = RequestState::kPendingNoConnections; |
| db_->SendVersionChangeToAllConnections(old_version, new_version); |
| |
| // When all connections have closed the upgrade can proceed. |
| } |
| |
| void OnVersionChangeIgnored() const override { |
| DCHECK(state_ == RequestState::kPendingNoConnections); |
| pending_->callbacks->OnBlocked(db_->metadata_.version); |
| } |
| |
| void OnConnectionClosed(IndexedDBConnection* connection) override { |
| // This connection closed prematurely; signal an error and complete. |
| if (connection == connection_ptr_for_close_comparision_) { |
| connection_ptr_for_close_comparision_ = nullptr; |
| if (!pending_->callbacks->is_complete()) { |
| pending_->callbacks->OnError( |
| IndexedDBDatabaseError(blink::kWebIDBDatabaseExceptionAbortError, |
| "The connection was closed.")); |
| } |
| state_ = RequestState::kDone; |
| tasks_available_callback_.Run(); |
| return; |
| } |
| } |
| |
| void OnNoConnections() override { |
| DCHECK(state_ == RequestState::kPendingNoConnections); |
| std::vector<ScopesLockManager::ScopeLockRequest> lock_requests = { |
| {kDatabaseRangeLockLevel, GetDatabaseLockRange(db_->metadata().id), |
| ScopesLockManager::LockType::kExclusive}}; |
| state_ = RequestState::kPendingLocks; |
| db_->lock_manager_->AcquireLocks( |
| std::move(lock_requests), lock_receiver_.weak_factory.GetWeakPtr(), |
| base::BindOnce( |
| &IndexedDBConnectionCoordinator::OpenRequest::StartUpgrade, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| // Initiate the upgrade. The bulk of the work actually happens in |
| // IndexedDBConnectionCoordinator::VersionChangeOperation in order to kick the |
| // transaction into the correct state. |
| void StartUpgrade() { |
| DCHECK(state_ == RequestState::kPendingLocks); |
| |
| DCHECK(!lock_receiver_.locks.empty()); |
| connection_ = db_->CreateConnection(std::move(origin_state_handle_), |
| pending_->database_callbacks, |
| pending_->execution_context); |
| DCHECK(!connection_ptr_for_close_comparision_); |
| connection_ptr_for_close_comparision_ = connection_.get(); |
| DCHECK_EQ(db_->connections().count(connection_.get()), 1UL); |
| |
| std::vector<int64_t> object_store_ids; |
| |
| state_ = RequestState::kPendingTransactionComplete; |
| IndexedDBTransaction* transaction = connection_->CreateTransaction( |
| pending_->transaction_id, |
| std::set<int64_t>(object_store_ids.begin(), object_store_ids.end()), |
| blink::mojom::IDBTransactionMode::VersionChange, |
| db_->backing_store() |
| ->CreateTransaction(blink::mojom::IDBTransactionDurability::Strict) |
| .release()); |
| |
| // Save a WeakPtr<IndexedDBTransaction> for the CreateAndBindTransaction |
| // function to use later. |
| pending_->transaction = transaction->AsWeakPtr(); |
| |
| transaction->ScheduleTask(BindWeakOperation( |
| &IndexedDBDatabase::VersionChangeOperation, db_->AsWeakPtr(), |
| pending_->version, pending_->callbacks)); |
| transaction->mutable_locks_receiver()->locks = |
| std::move(lock_receiver_.locks); |
| transaction->Start(); |
| } |
| |
| void CreateAndBindTransaction() override { |
| if (pending_->create_transaction_callback && pending_->transaction) { |
| std::move(pending_->create_transaction_callback) |
| .Run(std::move(pending_->transaction)); |
| } |
| } |
| |
| // Called when the upgrade transaction has started executing. |
| void UpgradeTransactionStarted(int64_t old_version) override { |
| DCHECK(state_ == RequestState::kPendingTransactionComplete); |
| DCHECK(connection_); |
| pending_->callbacks->OnUpgradeNeeded(old_version, std::move(connection_), |
| db_->metadata_, |
| pending_->data_loss_info); |
| } |
| |
| void UpgradeTransactionFinished(bool committed) override { |
| DCHECK(state_ == RequestState::kPendingTransactionComplete); |
| // Ownership of connection was already passed along in OnUpgradeNeeded. |
| if (committed) { |
| DCHECK_EQ(pending_->version, db_->metadata_.version); |
| pending_->callbacks->OnSuccess(nullptr, db_->metadata()); |
| } else { |
| DCHECK_NE(pending_->version, db_->metadata_.version); |
| pending_->callbacks->OnError( |
| IndexedDBDatabaseError(blink::kWebIDBDatabaseExceptionAbortError, |
| "Version change transaction was aborted in " |
| "upgradeneeded event handler.")); |
| } |
| state_ = RequestState::kDone; |
| tasks_available_callback_.Run(); |
| } |
| |
| std::tuple<bool, leveldb::Status> ShouldPruneForForceClose() override { |
| DCHECK(pending_); |
| if (!pending_->callbacks->is_complete()) { |
| pending_->callbacks->OnError( |
| IndexedDBDatabaseError(blink::kWebIDBDatabaseExceptionAbortError, |
| "The connection was closed.")); |
| } |
| if (state_ != RequestState::kError) |
| state_ = RequestState::kDone; |
| |
| leveldb::Status status; |
| if (connection_) { |
| // CloseAndReportForceClose calls OnForcedClose on the database callbacks, |
| // so we don't need to. |
| status = connection_->CloseAndReportForceClose(); |
| connection_.reset(); |
| } else { |
| pending_->database_callbacks->OnForcedClose(); |
| } |
| pending_.reset(); |
| // The tasks_available_callback_ is NOT run here, because we are assuming |
| // the caller is doing their own cleanup & execution for ForceClose. |
| return {true, status}; |
| } |
| |
| private: |
| ScopesLocksHolder lock_receiver_; |
| |
| std::unique_ptr<IndexedDBPendingConnection> pending_; |
| |
| // If an upgrade is needed, holds the pending connection until ownership is |
| // transferred to the IndexedDBDispatcherHost via OnUpgradeNeeded. |
| std::unique_ptr<IndexedDBConnection> connection_; |
| |
| // This raw pointer is stored solely for comparison to the connection in |
| // OnConnectionClosed. It is not guaranteed to be pointing to a live object. |
| IndexedDBConnection* connection_ptr_for_close_comparision_ = nullptr; |
| |
| base::WeakPtrFactory<OpenRequest> weak_factory_{this}; |
| DISALLOW_COPY_AND_ASSIGN(OpenRequest); |
| }; |
| |
| class IndexedDBConnectionCoordinator::DeleteRequest |
| : public IndexedDBConnectionCoordinator::ConnectionRequest { |
| public: |
| DeleteRequest(IndexedDBOriginStateHandle origin_state_handle, |
| IndexedDBDatabase* db, |
| scoped_refptr<IndexedDBCallbacks> callbacks, |
| base::OnceClosure on_database_deleted, |
| IndexedDBConnectionCoordinator* connection_coordinator, |
| TasksAvailableCallback tasks_available_callback) |
| : ConnectionRequest(std::move(origin_state_handle), |
| db, |
| connection_coordinator, |
| std::move(tasks_available_callback)), |
| callbacks_(callbacks), |
| on_database_deleted_(std::move(on_database_deleted)) {} |
| |
| void Perform(bool has_connections) override { |
| if (!has_connections) { |
| // No connections, so delete immediately. |
| std::vector<ScopesLockManager::ScopeLockRequest> lock_requests = { |
| {kDatabaseRangeLockLevel, GetDatabaseLockRange(db_->metadata().id), |
| ScopesLockManager::LockType::kExclusive}}; |
| state_ = RequestState::kPendingLocks; |
| db_->lock_manager_->AcquireLocks( |
| std::move(lock_requests), lock_receiver_.AsWeakPtr(), |
| base::BindOnce( |
| &IndexedDBConnectionCoordinator::DeleteRequest::DoDelete, |
| weak_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| // Front end ensures the event is not fired at connections that have |
| // close_pending set. |
| const int64_t old_version = db_->metadata().version; |
| const int64_t new_version = IndexedDBDatabaseMetadata::NO_VERSION; |
| db_->SendVersionChangeToAllConnections(old_version, new_version); |
| state_ = RequestState::kPendingNoConnections; |
| } |
| |
| void OnVersionChangeIgnored() const override { |
| DCHECK(state_ == RequestState::kPendingNoConnections); |
| callbacks_->OnBlocked(db_->metadata_.version); |
| } |
| |
| void OnConnectionClosed(IndexedDBConnection* connection) override {} |
| |
| void OnNoConnections() override { |
| DCHECK(state_ == RequestState::kPendingNoConnections); |
| std::vector<ScopesLockManager::ScopeLockRequest> lock_requests = { |
| {kDatabaseRangeLockLevel, GetDatabaseLockRange(db_->metadata().id), |
| ScopesLockManager::LockType::kExclusive}}; |
| state_ = RequestState::kPendingLocks; |
| db_->lock_manager_->AcquireLocks( |
| std::move(lock_requests), lock_receiver_.AsWeakPtr(), |
| base::BindOnce(&IndexedDBConnectionCoordinator::DeleteRequest::DoDelete, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void DoDelete() { |
| DCHECK(state_ == RequestState::kPendingLocks); |
| state_ = RequestState::kPendingTransactionComplete; |
| // This is used to check if this class is still alive after the destruction |
| // of the TransactionalLevelDBTransaction, which can synchronously cause the |
| // system to be shut down if the disk is really bad. |
| base::WeakPtr<DeleteRequest> weak_ptr = weak_factory_.GetWeakPtr(); |
| if (db_->backing_store_) { |
| scoped_refptr<TransactionalLevelDBTransaction> txn; |
| TransactionalLevelDBDatabase* db = db_->backing_store_->db(); |
| if (db) { |
| txn = db->leveldb_class_factory()->CreateLevelDBTransaction( |
| db, db->scopes()->CreateScope(std::move(lock_receiver_.locks), {})); |
| txn->set_commit_cleanup_complete_callback( |
| std::move(on_database_deleted_)); |
| } |
| saved_leveldb_status_ = |
| db_->backing_store_->DeleteDatabase(db_->metadata_.name, txn.get()); |
| } |
| if (!weak_ptr) |
| return; |
| |
| if (!saved_leveldb_status_.ok()) { |
| // TODO(jsbell): Consider including sanitized leveldb status message. |
| IndexedDBDatabaseError error(blink::kWebIDBDatabaseExceptionUnknownError, |
| "Internal error deleting database."); |
| callbacks_->OnError(error); |
| state_ = RequestState::kError; |
| tasks_available_callback_.Run(); |
| return; |
| } |
| |
| int64_t old_version = db_->metadata_.version; |
| db_->metadata_.id = kInvalidDatabaseId; |
| db_->metadata_.version = IndexedDBDatabaseMetadata::NO_VERSION; |
| db_->metadata_.max_object_store_id = |
| blink::IndexedDBObjectStoreMetadata::kInvalidId; |
| db_->metadata_.object_stores.clear(); |
| // Unittests (specifically the IndexedDBDatabase unittests) can have the |
| // backing store be a nullptr, so report deleted here. |
| if (on_database_deleted_) |
| std::move(on_database_deleted_).Run(); |
| callbacks_->OnSuccess(old_version); |
| |
| state_ = RequestState::kDone; |
| tasks_available_callback_.Run(); |
| } |
| |
| void CreateAndBindTransaction() override { NOTREACHED(); } |
| |
| void UpgradeTransactionStarted(int64_t old_version) override { NOTREACHED(); } |
| |
| void UpgradeTransactionFinished(bool committed) override { NOTREACHED(); } |
| |
| // The delete requests should always be run during force close. |
| std::tuple<bool, leveldb::Status> ShouldPruneForForceClose() override { |
| return {false, leveldb::Status::OK()}; |
| } |
| |
| private: |
| ScopesLocksHolder lock_receiver_; |
| scoped_refptr<IndexedDBCallbacks> callbacks_; |
| base::OnceClosure on_database_deleted_; |
| |
| base::WeakPtrFactory<DeleteRequest> weak_factory_{this}; |
| DISALLOW_COPY_AND_ASSIGN(DeleteRequest); |
| }; |
| |
| IndexedDBConnectionCoordinator::IndexedDBConnectionCoordinator( |
| IndexedDBDatabase* db, |
| TasksAvailableCallback tasks_available_callback) |
| : db_(db), tasks_available_callback_(std::move(tasks_available_callback)) {} |
| IndexedDBConnectionCoordinator::~IndexedDBConnectionCoordinator() = default; |
| |
| void IndexedDBConnectionCoordinator::ScheduleOpenConnection( |
| IndexedDBOriginStateHandle origin_state_handle, |
| std::unique_ptr<IndexedDBPendingConnection> connection) { |
| request_queue_.push(std::make_unique<OpenRequest>( |
| std::move(origin_state_handle), db_, std::move(connection), this, |
| tasks_available_callback_)); |
| tasks_available_callback_.Run(); |
| } |
| |
| void IndexedDBConnectionCoordinator::ScheduleDeleteDatabase( |
| IndexedDBOriginStateHandle origin_state_handle, |
| scoped_refptr<IndexedDBCallbacks> callbacks, |
| base::OnceClosure on_deletion_complete) { |
| request_queue_.push(std::make_unique<DeleteRequest>( |
| std::move(origin_state_handle), db_, callbacks, |
| std::move(on_deletion_complete), this, tasks_available_callback_)); |
| tasks_available_callback_.Run(); |
| } |
| |
| leveldb::Status IndexedDBConnectionCoordinator::PruneTasksForForceClose() { |
| // Remove all pending requests that don't want to execute during force close |
| // (open requests). |
| base::queue<std::unique_ptr<ConnectionRequest>> requests_to_still_run; |
| leveldb::Status last_error; |
| while (!request_queue_.empty()) { |
| std::unique_ptr<ConnectionRequest> request = |
| std::move(request_queue_.front()); |
| request_queue_.pop(); |
| bool prune; |
| leveldb::Status old_error = request->status(); |
| leveldb::Status status; |
| std::tie(prune, status) = request->ShouldPruneForForceClose(); |
| |
| if (prune) { |
| if (!old_error.ok()) |
| last_error = old_error; |
| request.reset(); |
| } else { |
| requests_to_still_run.push(std::move(request)); |
| } |
| |
| if (!status.ok()) |
| last_error = status; |
| } |
| |
| request_queue_ = std::move(requests_to_still_run); |
| return last_error; |
| } |
| |
| void IndexedDBConnectionCoordinator::OnConnectionClosed( |
| IndexedDBConnection* connection) { |
| DCHECK(connection->database().get() == db_); |
| |
| if (!request_queue_.empty()) |
| request_queue_.front()->OnConnectionClosed(connection); |
| } |
| |
| void IndexedDBConnectionCoordinator::OnNoConnections() { |
| if (request_queue_.empty() || |
| request_queue_.front()->state() != RequestState::kPendingNoConnections) { |
| return; |
| } |
| request_queue_.front()->OnNoConnections(); |
| } |
| |
| // TODO(dmurph): Attach an ID to the connection change events to prevent |
| // mis-propogation to the wrong connection request. |
| void IndexedDBConnectionCoordinator::OnVersionChangeIgnored() { |
| if (request_queue_.empty() || |
| request_queue_.front()->state() != RequestState::kPendingNoConnections) { |
| return; |
| } |
| request_queue_.front()->OnVersionChangeIgnored(); |
| } |
| |
| void IndexedDBConnectionCoordinator::OnUpgradeTransactionStarted( |
| int64_t old_version) { |
| if (request_queue_.empty() || request_queue_.front()->state() != |
| RequestState::kPendingTransactionComplete) { |
| return; |
| } |
| request_queue_.front()->UpgradeTransactionStarted(old_version); |
| } |
| |
| void IndexedDBConnectionCoordinator::CreateAndBindUpgradeTransaction() { |
| if (request_queue_.empty() || request_queue_.front()->state() != |
| RequestState::kPendingTransactionComplete) { |
| return; |
| } |
| request_queue_.front()->CreateAndBindTransaction(); |
| } |
| |
| void IndexedDBConnectionCoordinator::OnUpgradeTransactionFinished( |
| bool committed) { |
| if (request_queue_.empty() || request_queue_.front()->state() != |
| RequestState::kPendingTransactionComplete) { |
| return; |
| } |
| request_queue_.front()->UpgradeTransactionFinished(committed); |
| } |
| |
| std::tuple<IndexedDBConnectionCoordinator::ExecuteTaskResult, leveldb::Status> |
| IndexedDBConnectionCoordinator::ExecuteTask(bool has_connections) { |
| if (request_queue_.empty()) |
| return {ExecuteTaskResult::kDone, leveldb::Status()}; |
| |
| auto& request = request_queue_.front(); |
| if (request->state() == RequestState::kNotStarted) { |
| request->Perform(has_connections); |
| DCHECK(request->state() != RequestState::kNotStarted); |
| } |
| |
| switch (request->state()) { |
| case RequestState::kNotStarted: |
| NOTREACHED(); |
| return {ExecuteTaskResult::kError, leveldb::Status::OK()}; |
| case RequestState::kPendingNoConnections: |
| case RequestState::kPendingLocks: |
| case RequestState::kPendingTransactionComplete: |
| return {ExecuteTaskResult::kPendingAsyncWork, leveldb::Status()}; |
| case RequestState::kDone: { |
| // Move |request_to_discard| out of |request_queue_| then |
| // |request_queue_.pop()|. We do this because |request_to_discard|'s dtor |
| // calls OnConnectionClosed and OnNoConnections, which interact with |
| // |request_queue_| assuming the queue no longer holds |
| // |request_to_discard|. |
| auto request_to_discard = std::move(request_queue_.front()); |
| request_queue_.pop(); |
| request_to_discard.reset(); |
| return {request_queue_.empty() ? ExecuteTaskResult::kDone |
| : ExecuteTaskResult::kMoreTasks, |
| leveldb::Status::OK()}; |
| } |
| case RequestState::kError: { |
| leveldb::Status status = request->status(); |
| // Move |request_to_discard| out of |request_queue_| then |
| // |request_queue_.pop()|. We do this because |request_to_discard|'s dtor |
| // calls OnConnectionClosed and OnNoConnections, which interact with |
| // |request_queue_| assuming the queue no longer holds |
| // |request_to_discard|. |
| auto request_to_discard = std::move(request_queue_.front()); |
| request_queue_.pop(); |
| request_to_discard.reset(); |
| return {ExecuteTaskResult::kError, status}; |
| } |
| } |
| NOTREACHED(); |
| } |
| |
| size_t IndexedDBConnectionCoordinator::ActiveOpenDeleteCount() const { |
| return request_queue_.empty() ? 0 : 1; |
| } |
| |
| // Number of open/delete calls that are waiting their turn. |
| size_t IndexedDBConnectionCoordinator::PendingOpenDeleteCount() const { |
| if (request_queue_.empty()) |
| return 0; |
| if (request_queue_.front()->state() == RequestState::kNotStarted) |
| return request_queue_.size(); |
| return request_queue_.size() - 1; |
| } |
| |
| } // namespace content |