| Index: content/browser/indexed_db/indexed_db_transaction.cc
|
| diff --git a/content/browser/indexed_db/indexed_db_transaction.cc b/content/browser/indexed_db/indexed_db_transaction.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..e9d1294a383bf2d96ce7a3a2bd00614df92e95a0
|
| --- /dev/null
|
| +++ b/content/browser/indexed_db/indexed_db_transaction.cc
|
| @@ -0,0 +1,306 @@
|
| +// Copyright (c) 2013 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_transaction.h"
|
| +
|
| +#include <vector>
|
| +#include "base/logging.h"
|
| +#include "base/utf_string_conversions.h"
|
| +#include "content/browser/indexed_db/indexed_db_backing_store.h"
|
| +#include "content/browser/indexed_db/indexed_db_cursor_impl.h"
|
| +#include "content/browser/indexed_db/indexed_db_database_callbacks_wrapper.h"
|
| +#include "content/browser/indexed_db/indexed_db_database_impl.h"
|
| +#include "content/browser/indexed_db/indexed_db_tracing.h"
|
| +#include "content/browser/indexed_db/indexed_db_transaction_coordinator.h"
|
| +#include "third_party/WebKit/Source/Platform/chromium/public/WebIDBDatabaseException.h"
|
| +
|
| +namespace content {
|
| +
|
| +IndexedDBTransaction::TaskQueue::TaskQueue() {}
|
| +IndexedDBTransaction::TaskQueue::~TaskQueue() { clear(); }
|
| +
|
| +void IndexedDBTransaction::TaskQueue::clear() {
|
| + while (!queue_.empty())
|
| + scoped_ptr<Operation> task(pop());
|
| +}
|
| +
|
| +scoped_ptr<IndexedDBTransaction::Operation>
|
| +IndexedDBTransaction::TaskQueue::pop() {
|
| + DCHECK(!queue_.empty());
|
| + scoped_ptr<Operation> task(queue_.front());
|
| + queue_.pop();
|
| + return task.Pass();
|
| +}
|
| +
|
| +IndexedDBTransaction::TaskStack::TaskStack() {}
|
| +IndexedDBTransaction::TaskStack::~TaskStack() { clear(); }
|
| +
|
| +void IndexedDBTransaction::TaskStack::clear() {
|
| + while (!stack_.empty())
|
| + scoped_ptr<Operation> task(pop());
|
| +}
|
| +
|
| +scoped_ptr<IndexedDBTransaction::Operation>
|
| +IndexedDBTransaction::TaskStack::pop() {
|
| + DCHECK(!stack_.empty());
|
| + scoped_ptr<Operation> task(stack_.top());
|
| + stack_.pop();
|
| + return task.Pass();
|
| +}
|
| +
|
| +scoped_refptr<IndexedDBTransaction> IndexedDBTransaction::Create(
|
| + int64_t id,
|
| + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> callbacks,
|
| + const std::vector<int64_t>& object_store_ids,
|
| + indexed_db::TransactionMode mode,
|
| + IndexedDBDatabaseImpl* database) {
|
| + std::set<int64_t> object_store_hash_set;
|
| + for (size_t i = 0; i < object_store_ids.size(); ++i)
|
| + object_store_hash_set.insert(object_store_ids[i]);
|
| +
|
| + return make_scoped_refptr(new IndexedDBTransaction(
|
| + id, callbacks, object_store_hash_set, mode, database));
|
| +}
|
| +
|
| +IndexedDBTransaction::IndexedDBTransaction(
|
| + int64_t id,
|
| + scoped_refptr<IndexedDBDatabaseCallbacksWrapper> callbacks,
|
| + const std::set<int64_t>& object_store_ids,
|
| + indexed_db::TransactionMode mode,
|
| + IndexedDBDatabaseImpl* database)
|
| + : id_(id),
|
| + object_store_ids_(object_store_ids),
|
| + mode_(mode),
|
| + state_(UNUSED),
|
| + commit_pending_(false),
|
| + callbacks_(callbacks),
|
| + database_(database),
|
| + transaction_(database->BackingStore().get()),
|
| + pending_preemptive_events_(0) {
|
| + database_->transaction_coordinator().DidCreateTransaction(this);
|
| +}
|
| +
|
| +IndexedDBTransaction::~IndexedDBTransaction() {
|
| + // It shouldn't be possible for this object to get deleted until it's either
|
| + // complete or aborted.
|
| + DCHECK_EQ(state_, FINISHED);
|
| + DCHECK(preemptive_task_queue_.empty());
|
| + DCHECK(task_queue_.empty());
|
| + DCHECK(abort_task_stack_.empty());
|
| +}
|
| +
|
| +void IndexedDBTransaction::ScheduleTask(IndexedDBDatabase::TaskType type,
|
| + Operation* task,
|
| + Operation* abort_task) {
|
| + if (state_ == FINISHED)
|
| + return;
|
| +
|
| + if (type == IndexedDBDatabase::NORMAL_TASK)
|
| + task_queue_.push(task);
|
| + else
|
| + preemptive_task_queue_.push(task);
|
| +
|
| + if (abort_task)
|
| + abort_task_stack_.push(abort_task);
|
| +
|
| + if (state_ == UNUSED)
|
| + Start();
|
| + else if (state_ == RUNNING && !task_timer_.IsRunning())
|
| + task_timer_.Start(FROM_HERE,
|
| + base::TimeDelta::FromSeconds(0),
|
| + this,
|
| + &IndexedDBTransaction::TaskTimerFired);
|
| +}
|
| +
|
| +void IndexedDBTransaction::Abort() {
|
| + Abort(IndexedDBDatabaseError::Create(
|
| + WebKit::WebIDBDatabaseExceptionUnknownError,
|
| + ASCIIToUTF16("Internal error (unknown cause)")));
|
| +}
|
| +
|
| +void IndexedDBTransaction::Abort(scoped_refptr<IndexedDBDatabaseError> error) {
|
| + IDB_TRACE("IndexedDBTransaction::abort");
|
| + if (state_ == FINISHED)
|
| + return;
|
| +
|
| + bool was_running = state_ == RUNNING;
|
| +
|
| + // The last reference to this object may be released while performing the
|
| + // abort steps below. We therefore take a self reference to keep ourselves
|
| + // alive while executing this method.
|
| + scoped_refptr<IndexedDBTransaction> protect(this);
|
| +
|
| + state_ = FINISHED;
|
| + task_timer_.Stop();
|
| +
|
| + if (was_running)
|
| + transaction_.Rollback();
|
| +
|
| + // Run the abort tasks, if any.
|
| + while (!abort_task_stack_.empty()) {
|
| + scoped_ptr<Operation> task(abort_task_stack_.pop());
|
| + task->Perform(0);
|
| + }
|
| + preemptive_task_queue_.clear();
|
| + task_queue_.clear();
|
| +
|
| + // Backing store resources (held via cursors) must be released
|
| + // before script callbacks are fired, as the script callbacks may
|
| + // release references and allow the backing store itself to be
|
| + // released, and order is critical.
|
| + CloseOpenCursors();
|
| + transaction_.Reset();
|
| +
|
| + // Transactions must also be marked as completed before the
|
| + // front-end is notified, as the transaction completion unblocks
|
| + // operations like closing connections.
|
| + database_->transaction_coordinator().DidFinishTransaction(this);
|
| + DCHECK(!database_->transaction_coordinator().IsActive(this));
|
| + database_->TransactionFinished(this);
|
| +
|
| + if (callbacks_)
|
| + callbacks_->OnAbort(id_, error);
|
| +
|
| + database_->TransactionFinishedAndAbortFired(this);
|
| +
|
| + database_ = NULL;
|
| +}
|
| +
|
| +bool IndexedDBTransaction::IsTaskQueueEmpty() const {
|
| + return preemptive_task_queue_.empty() && task_queue_.empty();
|
| +}
|
| +
|
| +bool IndexedDBTransaction::HasPendingTasks() const {
|
| + return pending_preemptive_events_ || !IsTaskQueueEmpty();
|
| +}
|
| +
|
| +void IndexedDBTransaction::RegisterOpenCursor(IndexedDBCursorImpl* cursor) {
|
| + open_cursors_.insert(cursor);
|
| +}
|
| +
|
| +void IndexedDBTransaction::UnregisterOpenCursor(IndexedDBCursorImpl* cursor) {
|
| + open_cursors_.erase(cursor);
|
| +}
|
| +
|
| +void IndexedDBTransaction::Run() {
|
| + // TransactionCoordinator has started this transaction. Schedule a timer
|
| + // to process the first task.
|
| + DCHECK_EQ(state_ == START_PENDING || state_, RUNNING);
|
| + DCHECK(!task_timer_.IsRunning());
|
| +
|
| + task_timer_.Start(FROM_HERE,
|
| + base::TimeDelta::FromSeconds(0),
|
| + this,
|
| + &IndexedDBTransaction::TaskTimerFired);
|
| +}
|
| +
|
| +void IndexedDBTransaction::Start() {
|
| + DCHECK_EQ(state_, UNUSED);
|
| +
|
| + state_ = START_PENDING;
|
| + database_->transaction_coordinator().DidStartTransaction(this);
|
| + database_->TransactionStarted(this);
|
| +}
|
| +
|
| +void IndexedDBTransaction::Commit() {
|
| + IDB_TRACE("IndexedDBTransaction::commit");
|
| +
|
| + // In multiprocess ports, front-end may have requested a commit but
|
| + // an abort has already been initiated asynchronously by the
|
| + // back-end.
|
| + if (state_ == FINISHED)
|
| + return;
|
| +
|
| + DCHECK_EQ(state_ == UNUSED || state_, RUNNING);
|
| + commit_pending_ = true;
|
| +
|
| + // Front-end has requested a commit, but there may be tasks like
|
| + // create_index which are considered synchronous by the front-end
|
| + // but are processed asynchronously.
|
| + if (HasPendingTasks())
|
| + return;
|
| +
|
| + // The last reference to this object may be released while performing the
|
| + // commit steps below. We therefore take a self reference to keep ourselves
|
| + // alive while executing this method.
|
| + scoped_refptr<IndexedDBTransaction> protect(this);
|
| +
|
| + // TODO(jsbell): Run abort tasks if commit fails? http://crbug.com/241843
|
| + abort_task_stack_.clear();
|
| +
|
| + bool unused = state_ == UNUSED;
|
| + state_ = FINISHED;
|
| +
|
| + bool committed = unused || transaction_.Commit();
|
| +
|
| + // Backing store resources (held via cursors) must be released
|
| + // before script callbacks are fired, as the script callbacks may
|
| + // release references and allow the backing store itself to be
|
| + // released, and order is critical.
|
| + CloseOpenCursors();
|
| + transaction_.Reset();
|
| +
|
| + // Transactions must also be marked as completed before the
|
| + // front-end is notified, as the transaction completion unblocks
|
| + // operations like closing connections.
|
| + if (!unused)
|
| + database_->transaction_coordinator().DidFinishTransaction(this);
|
| + database_->TransactionFinished(this);
|
| +
|
| + if (committed) {
|
| + callbacks_->OnComplete(id_);
|
| + database_->TransactionFinishedAndCompleteFired(this);
|
| + } else {
|
| + callbacks_->OnAbort(
|
| + id_,
|
| + IndexedDBDatabaseError::Create(
|
| + WebKit::WebIDBDatabaseExceptionUnknownError,
|
| + ASCIIToUTF16("Internal error committing transaction.")));
|
| + database_->TransactionFinishedAndAbortFired(this);
|
| + }
|
| +
|
| + database_ = NULL;
|
| +}
|
| +
|
| +void IndexedDBTransaction::TaskTimerFired() {
|
| + IDB_TRACE("IndexedDBTransaction::task_timer_fired");
|
| + DCHECK(!IsTaskQueueEmpty());
|
| +
|
| + if (state_ == START_PENDING) {
|
| + transaction_.begin();
|
| + state_ = RUNNING;
|
| + }
|
| +
|
| + // The last reference to this object may be released while performing the
|
| + // tasks. Take take a self reference to keep this object alive so that
|
| + // the loop termination conditions can be checked.
|
| + scoped_refptr<IndexedDBTransaction> protect(this);
|
| +
|
| + TaskQueue* task_queue =
|
| + pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
|
| + while (!task_queue->empty() && state_ != FINISHED) {
|
| + DCHECK_EQ(state_, RUNNING);
|
| + scoped_ptr<Operation> task(task_queue->pop());
|
| + task->Perform(this);
|
| +
|
| + // Event itself may change which queue should be processed next.
|
| + task_queue =
|
| + pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
|
| + }
|
| +
|
| + // If there are no pending tasks, we haven't already committed/aborted,
|
| + // and the front-end requested a commit, it is now safe to do so.
|
| + if (!HasPendingTasks() && state_ != FINISHED && commit_pending_)
|
| + Commit();
|
| +}
|
| +
|
| +void IndexedDBTransaction::CloseOpenCursors() {
|
| + for (std::set<IndexedDBCursorImpl*>::iterator i = open_cursors_.begin();
|
| + i != open_cursors_.end();
|
| + ++i)
|
| + (*i)->Close();
|
| + open_cursors_.clear();
|
| +}
|
| +
|
| +} // namespace content
|
|
|