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 const int64 kInactivityTimeoutPeriodSeconds = 60;
     22 
     23 IndexedDBTransaction::TaskQueue::TaskQueue() {}
     24 IndexedDBTransaction::TaskQueue::~TaskQueue() { clear(); }
     25 
     26 void IndexedDBTransaction::TaskQueue::clear() {
     27   while (!queue_.empty())
     28     queue_.pop();
     29 }
     30 
     31 IndexedDBTransaction::Operation IndexedDBTransaction::TaskQueue::pop() {
     32   DCHECK(!queue_.empty());
     33   Operation task(queue_.front());
     34   queue_.pop();
     35   return task;
     36 }
     37 
     38 IndexedDBTransaction::TaskStack::TaskStack() {}
     39 IndexedDBTransaction::TaskStack::~TaskStack() { clear(); }
     40 
     41 void IndexedDBTransaction::TaskStack::clear() {
     42   while (!stack_.empty())
     43     stack_.pop();
     44 }
     45 
     46 IndexedDBTransaction::Operation IndexedDBTransaction::TaskStack::pop() {
     47   DCHECK(!stack_.empty());
     48   Operation task(stack_.top());
     49   stack_.pop();
     50   return task;
     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     IndexedDBBackingStore::Transaction* backing_store_transaction)
     60     : id_(id),
     61       object_store_ids_(object_store_ids),
     62       mode_(mode),
     63       used_(false),
     64       state_(CREATED),
     65       commit_pending_(false),
     66       callbacks_(callbacks),
     67       database_(database),
     68       transaction_(backing_store_transaction),
     69       backing_store_transaction_begun_(false),
     70       should_process_queue_(false),
     71       pending_preemptive_events_(0) {
     72   database_->transaction_coordinator().DidCreateTransaction(this);
     73 
     74   diagnostics_.tasks_scheduled = 0;
     75   diagnostics_.tasks_completed = 0;
     76   diagnostics_.creation_time = base::Time::Now();
     77 }
     78 
     79 IndexedDBTransaction::~IndexedDBTransaction() {
     80   // It shouldn't be possible for this object to get deleted until it's either
     81   // complete or aborted.
     82   DCHECK_EQ(state_, FINISHED);
     83   DCHECK(preemptive_task_queue_.empty());
     84   DCHECK(task_queue_.empty());
     85   DCHECK(abort_task_stack_.empty());
     86 }
     87 
     88 void IndexedDBTransaction::ScheduleTask(Operation task, Operation abort_task) {
     89   if (state_ == FINISHED)
     90     return;
     91 
     92   timeout_timer_.Stop();
     93   used_ = true;
     94   task_queue_.push(task);
     95   ++diagnostics_.tasks_scheduled;
     96   abort_task_stack_.push(abort_task);
     97   RunTasksIfStarted();
     98 }
     99 
    100 void IndexedDBTransaction::ScheduleTask(IndexedDBDatabase::TaskType type,
    101                                         Operation task) {
    102   if (state_ == FINISHED)
    103     return;
    104 
    105   timeout_timer_.Stop();
    106   used_ = true;
    107   if (type == IndexedDBDatabase::NORMAL_TASK) {
    108     task_queue_.push(task);
    109     ++diagnostics_.tasks_scheduled;
    110   } else {
    111     preemptive_task_queue_.push(task);
    112   }
    113   RunTasksIfStarted();
    114 }
    115 
    116 void IndexedDBTransaction::RunTasksIfStarted() {
    117   DCHECK(used_);
    118 
    119   // Not started by the coordinator yet.
    120   if (state_ != STARTED)
    121     return;
    122 
    123   // A task is already posted.
    124   if (should_process_queue_)
    125     return;
    126 
    127   should_process_queue_ = true;
    128   base::MessageLoop::current()->PostTask(
    129       FROM_HERE, base::Bind(&IndexedDBTransaction::ProcessTaskQueue, this));
    130 }
    131 
    132 void IndexedDBTransaction::Abort() {
    133   Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
    134                                "Internal error (unknown cause)"));
    135 }
    136 
    137 void IndexedDBTransaction::Abort(const IndexedDBDatabaseError& error) {
    138   IDB_TRACE("IndexedDBTransaction::Abort");
    139   if (state_ == FINISHED)
    140     return;
    141 
    142   // The last reference to this object may be released while performing the
    143   // abort steps below. We therefore take a self reference to keep ourselves
    144   // alive while executing this method.
    145   scoped_refptr<IndexedDBTransaction> protect(this);
    146 
    147   timeout_timer_.Stop();
    148 
    149   state_ = FINISHED;
    150   should_process_queue_ = false;
    151 
    152   if (backing_store_transaction_begun_)
    153     transaction_->Rollback();
    154 
    155   // Run the abort tasks, if any.
    156   while (!abort_task_stack_.empty())
    157     abort_task_stack_.pop().Run(0);
    158 
    159   preemptive_task_queue_.clear();
    160   task_queue_.clear();
    161 
    162   // Backing store resources (held via cursors) must be released
    163   // before script callbacks are fired, as the script callbacks may
    164   // release references and allow the backing store itself to be
    165   // released, and order is critical.
    166   CloseOpenCursors();
    167   transaction_->Reset();
    168 
    169   // Transactions must also be marked as completed before the
    170   // front-end is notified, as the transaction completion unblocks
    171   // operations like closing connections.
    172   database_->transaction_coordinator().DidFinishTransaction(this);
    173 #ifndef NDEBUG
    174   DCHECK(!database_->transaction_coordinator().IsActive(this));
    175 #endif
    176   database_->TransactionFinished(this);
    177 
    178   if (callbacks_.get())
    179     callbacks_->OnAbort(id_, error);
    180 
    181   database_->TransactionFinishedAndAbortFired(this);
    182 
    183   database_ = NULL;
    184 }
    185 
    186 bool IndexedDBTransaction::IsTaskQueueEmpty() const {
    187   return preemptive_task_queue_.empty() && task_queue_.empty();
    188 }
    189 
    190 bool IndexedDBTransaction::HasPendingTasks() const {
    191   return pending_preemptive_events_ || !IsTaskQueueEmpty();
    192 }
    193 
    194 void IndexedDBTransaction::RegisterOpenCursor(IndexedDBCursor* cursor) {
    195   open_cursors_.insert(cursor);
    196 }
    197 
    198 void IndexedDBTransaction::UnregisterOpenCursor(IndexedDBCursor* cursor) {
    199   open_cursors_.erase(cursor);
    200 }
    201 
    202 void IndexedDBTransaction::Start() {
    203   // TransactionCoordinator has started this transaction.
    204   DCHECK_EQ(CREATED, state_);
    205   state_ = STARTED;
    206   database_->TransactionStarted(this);
    207   diagnostics_.start_time = base::Time::Now();
    208 
    209   if (!used_)
    210     return;
    211 
    212   RunTasksIfStarted();
    213 }
    214 
    215 void IndexedDBTransaction::Commit() {
    216   IDB_TRACE("IndexedDBTransaction::Commit");
    217 
    218   // In multiprocess ports, front-end may have requested a commit but
    219   // an abort has already been initiated asynchronously by the
    220   // back-end.
    221   if (state_ == FINISHED)
    222     return;
    223 
    224   DCHECK(!used_ || state_ == STARTED);
    225   commit_pending_ = true;
    226 
    227   // Front-end has requested a commit, but there may be tasks like
    228   // create_index which are considered synchronous by the front-end
    229   // but are processed asynchronously.
    230   if (HasPendingTasks())
    231     return;
    232 
    233   // The last reference to this object may be released while performing the
    234   // commit steps below. We therefore take a self reference to keep ourselves
    235   // alive while executing this method.
    236   scoped_refptr<IndexedDBTransaction> protect(this);
    237 
    238   timeout_timer_.Stop();
    239 
    240   state_ = FINISHED;
    241 
    242   bool committed = !used_ || transaction_->Commit();
    243 
    244   // Backing store resources (held via cursors) must be released
    245   // before script callbacks are fired, as the script callbacks may
    246   // release references and allow the backing store itself to be
    247   // released, and order is critical.
    248   CloseOpenCursors();
    249   transaction_->Reset();
    250 
    251   // Transactions must also be marked as completed before the
    252   // front-end is notified, as the transaction completion unblocks
    253   // operations like closing connections.
    254   database_->transaction_coordinator().DidFinishTransaction(this);
    255   database_->TransactionFinished(this);
    256 
    257   if (committed) {
    258     abort_task_stack_.clear();
    259     callbacks_->OnComplete(id_);
    260     database_->TransactionFinishedAndCompleteFired(this);
    261   } else {
    262     while (!abort_task_stack_.empty())
    263       abort_task_stack_.pop().Run(0);
    264 
    265     callbacks_->OnAbort(
    266         id_,
    267         IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
    268                                "Internal error committing transaction."));
    269     database_->TransactionFinishedAndAbortFired(this);
    270     database_->TransactionCommitFailed();
    271   }
    272 
    273   database_ = NULL;
    274 }
    275 
    276 void IndexedDBTransaction::ProcessTaskQueue() {
    277   IDB_TRACE("IndexedDBTransaction::ProcessTaskQueue");
    278 
    279   // May have been aborted.
    280   if (!should_process_queue_)
    281     return;
    282 
    283   DCHECK(!IsTaskQueueEmpty());
    284   should_process_queue_ = false;
    285 
    286   if (!backing_store_transaction_begun_) {
    287     transaction_->Begin();
    288     backing_store_transaction_begun_ = true;
    289   }
    290 
    291   // The last reference to this object may be released while performing the
    292   // tasks. Take take a self reference to keep this object alive so that
    293   // the loop termination conditions can be checked.
    294   scoped_refptr<IndexedDBTransaction> protect(this);
    295 
    296   TaskQueue* task_queue =
    297       pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
    298   while (!task_queue->empty() && state_ != FINISHED) {
    299     DCHECK_EQ(STARTED, state_);
    300     Operation task(task_queue->pop());
    301     task.Run(this);
    302     if (!pending_preemptive_events_) {
    303       DCHECK(diagnostics_.tasks_completed < diagnostics_.tasks_scheduled);
    304       ++diagnostics_.tasks_completed;
    305     }
    306 
    307     // Event itself may change which queue should be processed next.
    308     task_queue =
    309         pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
    310   }
    311 
    312   // If there are no pending tasks, we haven't already committed/aborted,
    313   // and the front-end requested a commit, it is now safe to do so.
    314   if (!HasPendingTasks() && state_ != FINISHED && commit_pending_) {
    315     Commit();
    316     return;
    317   }
    318 
    319   // The transaction may have been aborted while processing tasks.
    320   if (state_ == FINISHED)
    321     return;
    322 
    323   // Otherwise, start a timer in case the front-end gets wedged and
    324   // never requests further activity.
    325   timeout_timer_.Start(
    326       FROM_HERE,
    327       base::TimeDelta::FromSeconds(kInactivityTimeoutPeriodSeconds),
    328       base::Bind(&IndexedDBTransaction::Timeout, this));
    329 }
    330 
    331 void IndexedDBTransaction::Timeout() {
    332   Abort(IndexedDBDatabaseError(
    333       blink::WebIDBDatabaseExceptionTimeoutError,
    334       ASCIIToUTF16("Transaction timed out due to inactivity.")));
    335 }
    336 
    337 void IndexedDBTransaction::CloseOpenCursors() {
    338   for (std::set<IndexedDBCursor*>::iterator i = open_cursors_.begin();
    339        i != open_cursors_.end();
    340        ++i)
    341     (*i)->Close();
    342   open_cursors_.clear();
    343 }
    344 
    345 }  // namespace content
    346