Home | History | Annotate | Download | only in indexed_db
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/browser/indexed_db/indexed_db_transaction.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/logging.h"
      9 #include "base/message_loop/message_loop.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #include "content/browser/indexed_db/indexed_db_backing_store.h"
     12 #include "content/browser/indexed_db/indexed_db_cursor.h"
     13 #include "content/browser/indexed_db/indexed_db_database.h"
     14 #include "content/browser/indexed_db/indexed_db_database_callbacks.h"
     15 #include "content/browser/indexed_db/indexed_db_tracing.h"
     16 #include "content/browser/indexed_db/indexed_db_transaction_coordinator.h"
     17 #include "third_party/WebKit/public/platform/WebIDBDatabaseException.h"
     18 
     19 namespace content {
     20 
     21 IndexedDBTransaction::TaskQueue::TaskQueue() {}
     22 IndexedDBTransaction::TaskQueue::~TaskQueue() { clear(); }
     23 
     24 void IndexedDBTransaction::TaskQueue::clear() {
     25   while (!queue_.empty())
     26     scoped_ptr<Operation> task(pop());
     27 }
     28 
     29 scoped_ptr<IndexedDBTransaction::Operation>
     30 IndexedDBTransaction::TaskQueue::pop() {
     31   DCHECK(!queue_.empty());
     32   scoped_ptr<Operation> task(queue_.front());
     33   queue_.pop();
     34   return task.Pass();
     35 }
     36 
     37 IndexedDBTransaction::TaskStack::TaskStack() {}
     38 IndexedDBTransaction::TaskStack::~TaskStack() { clear(); }
     39 
     40 void IndexedDBTransaction::TaskStack::clear() {
     41   while (!stack_.empty())
     42     scoped_ptr<Operation> task(pop());
     43 }
     44 
     45 scoped_ptr<IndexedDBTransaction::Operation>
     46 IndexedDBTransaction::TaskStack::pop() {
     47   DCHECK(!stack_.empty());
     48   scoped_ptr<Operation> task(stack_.top());
     49   stack_.pop();
     50   return task.Pass();
     51 }
     52 
     53 IndexedDBTransaction::IndexedDBTransaction(
     54     int64 id,
     55     scoped_refptr<IndexedDBDatabaseCallbacks> callbacks,
     56     const std::set<int64>& object_store_ids,
     57     indexed_db::TransactionMode mode,
     58     IndexedDBDatabase* database)
     59     : id_(id),
     60       object_store_ids_(object_store_ids),
     61       mode_(mode),
     62       state_(UNUSED),
     63       commit_pending_(false),
     64       callbacks_(callbacks),
     65       database_(database),
     66       transaction_(database->BackingStore().get()),
     67       should_process_queue_(false),
     68       pending_preemptive_events_(0) {
     69   database_->transaction_coordinator().DidCreateTransaction(this);
     70 }
     71 
     72 IndexedDBTransaction::~IndexedDBTransaction() {
     73   // It shouldn't be possible for this object to get deleted until it's either
     74   // complete or aborted.
     75   DCHECK_EQ(state_, FINISHED);
     76   DCHECK(preemptive_task_queue_.empty());
     77   DCHECK(task_queue_.empty());
     78   DCHECK(abort_task_stack_.empty());
     79 }
     80 
     81 void IndexedDBTransaction::ScheduleTask(IndexedDBDatabase::TaskType type,
     82                                         Operation* task,
     83                                         Operation* abort_task) {
     84   if (state_ == FINISHED)
     85     return;
     86 
     87   if (type == IndexedDBDatabase::NORMAL_TASK)
     88     task_queue_.push(task);
     89   else
     90     preemptive_task_queue_.push(task);
     91 
     92   if (abort_task)
     93     abort_task_stack_.push(abort_task);
     94 
     95   if (state_ == UNUSED) {
     96     Start();
     97   } else if (state_ == RUNNING && !should_process_queue_) {
     98     should_process_queue_ = true;
     99     base::MessageLoop::current()->PostTask(
    100         FROM_HERE, base::Bind(&IndexedDBTransaction::ProcessTaskQueue, this));
    101   }
    102 }
    103 
    104 void IndexedDBTransaction::Abort() {
    105   Abort(IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
    106                                "Internal error (unknown cause)"));
    107 }
    108 
    109 void IndexedDBTransaction::Abort(const IndexedDBDatabaseError& error) {
    110   IDB_TRACE("IndexedDBTransaction::Abort");
    111   if (state_ == FINISHED)
    112     return;
    113 
    114   bool was_running = state_ == RUNNING;
    115 
    116   // The last reference to this object may be released while performing the
    117   // abort steps below. We therefore take a self reference to keep ourselves
    118   // alive while executing this method.
    119   scoped_refptr<IndexedDBTransaction> protect(this);
    120 
    121   state_ = FINISHED;
    122   should_process_queue_ = false;
    123 
    124   if (was_running)
    125     transaction_.Rollback();
    126 
    127   // Run the abort tasks, if any.
    128   while (!abort_task_stack_.empty()) {
    129     scoped_ptr<Operation> task(abort_task_stack_.pop());
    130     task->Perform(0);
    131   }
    132   preemptive_task_queue_.clear();
    133   task_queue_.clear();
    134 
    135   // Backing store resources (held via cursors) must be released
    136   // before script callbacks are fired, as the script callbacks may
    137   // release references and allow the backing store itself to be
    138   // released, and order is critical.
    139   CloseOpenCursors();
    140   transaction_.Reset();
    141 
    142   // Transactions must also be marked as completed before the
    143   // front-end is notified, as the transaction completion unblocks
    144   // operations like closing connections.
    145   database_->transaction_coordinator().DidFinishTransaction(this);
    146 #ifndef NDEBUG
    147   DCHECK(!database_->transaction_coordinator().IsActive(this));
    148 #endif
    149   database_->TransactionFinished(this);
    150 
    151   if (callbacks_.get())
    152     callbacks_->OnAbort(id_, error);
    153 
    154   database_->TransactionFinishedAndAbortFired(this);
    155 
    156   database_ = NULL;
    157 }
    158 
    159 bool IndexedDBTransaction::IsTaskQueueEmpty() const {
    160   return preemptive_task_queue_.empty() && task_queue_.empty();
    161 }
    162 
    163 bool IndexedDBTransaction::HasPendingTasks() const {
    164   return pending_preemptive_events_ || !IsTaskQueueEmpty();
    165 }
    166 
    167 void IndexedDBTransaction::RegisterOpenCursor(IndexedDBCursor* cursor) {
    168   open_cursors_.insert(cursor);
    169 }
    170 
    171 void IndexedDBTransaction::UnregisterOpenCursor(IndexedDBCursor* cursor) {
    172   open_cursors_.erase(cursor);
    173 }
    174 
    175 void IndexedDBTransaction::Run() {
    176   // TransactionCoordinator has started this transaction.
    177   DCHECK(state_ == START_PENDING || state_ == RUNNING);
    178   DCHECK(!should_process_queue_);
    179 
    180   should_process_queue_ = true;
    181   base::MessageLoop::current()->PostTask(
    182       FROM_HERE, base::Bind(&IndexedDBTransaction::ProcessTaskQueue, this));
    183 }
    184 
    185 void IndexedDBTransaction::Start() {
    186   DCHECK_EQ(state_, UNUSED);
    187 
    188   state_ = START_PENDING;
    189   database_->transaction_coordinator().DidStartTransaction(this);
    190   database_->TransactionStarted(this);
    191 }
    192 
    193 void IndexedDBTransaction::Commit() {
    194   IDB_TRACE("IndexedDBTransaction::Commit");
    195 
    196   // In multiprocess ports, front-end may have requested a commit but
    197   // an abort has already been initiated asynchronously by the
    198   // back-end.
    199   if (state_ == FINISHED)
    200     return;
    201 
    202   DCHECK(state_ == UNUSED || state_ == RUNNING);
    203   commit_pending_ = true;
    204 
    205   // Front-end has requested a commit, but there may be tasks like
    206   // create_index which are considered synchronous by the front-end
    207   // but are processed asynchronously.
    208   if (HasPendingTasks())
    209     return;
    210 
    211   // The last reference to this object may be released while performing the
    212   // commit steps below. We therefore take a self reference to keep ourselves
    213   // alive while executing this method.
    214   scoped_refptr<IndexedDBTransaction> protect(this);
    215 
    216   // TODO(jsbell): Run abort tasks if commit fails? http://crbug.com/241843
    217   abort_task_stack_.clear();
    218 
    219   bool unused = state_ == UNUSED;
    220   state_ = FINISHED;
    221 
    222   bool committed = unused || transaction_.Commit();
    223 
    224   // Backing store resources (held via cursors) must be released
    225   // before script callbacks are fired, as the script callbacks may
    226   // release references and allow the backing store itself to be
    227   // released, and order is critical.
    228   CloseOpenCursors();
    229   transaction_.Reset();
    230 
    231   // Transactions must also be marked as completed before the
    232   // front-end is notified, as the transaction completion unblocks
    233   // operations like closing connections.
    234   database_->transaction_coordinator().DidFinishTransaction(this);
    235   database_->TransactionFinished(this);
    236 
    237   if (committed) {
    238     callbacks_->OnComplete(id_);
    239     database_->TransactionFinishedAndCompleteFired(this);
    240   } else {
    241     callbacks_->OnAbort(
    242         id_,
    243         IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
    244                                "Internal error committing transaction."));
    245     database_->TransactionFinishedAndAbortFired(this);
    246   }
    247 
    248   database_ = NULL;
    249 }
    250 
    251 void IndexedDBTransaction::ProcessTaskQueue() {
    252   IDB_TRACE("IndexedDBTransaction::ProcessTaskQueue");
    253 
    254   // May have been aborted.
    255   if (!should_process_queue_)
    256     return;
    257 
    258   DCHECK(!IsTaskQueueEmpty());
    259   should_process_queue_ = false;
    260 
    261   if (state_ == START_PENDING) {
    262     transaction_.Begin();
    263     state_ = RUNNING;
    264   }
    265 
    266   // The last reference to this object may be released while performing the
    267   // tasks. Take take a self reference to keep this object alive so that
    268   // the loop termination conditions can be checked.
    269   scoped_refptr<IndexedDBTransaction> protect(this);
    270 
    271   TaskQueue* task_queue =
    272       pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
    273   while (!task_queue->empty() && state_ != FINISHED) {
    274     DCHECK_EQ(state_, RUNNING);
    275     scoped_ptr<Operation> task(task_queue->pop());
    276     task->Perform(this);
    277 
    278     // Event itself may change which queue should be processed next.
    279     task_queue =
    280         pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
    281   }
    282 
    283   // If there are no pending tasks, we haven't already committed/aborted,
    284   // and the front-end requested a commit, it is now safe to do so.
    285   if (!HasPendingTasks() && state_ != FINISHED && commit_pending_)
    286     Commit();
    287 }
    288 
    289 void IndexedDBTransaction::CloseOpenCursors() {
    290   for (std::set<IndexedDBCursor*>::iterator i = open_cursors_.begin();
    291        i != open_cursors_.end();
    292        ++i)
    293     (*i)->Close();
    294   open_cursors_.clear();
    295 }
    296 
    297 }  // namespace content
    298