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     blink::WebIDBTransactionMode 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_EQ(pending_preemptive_events_, 0);
     85   DCHECK(task_queue_.empty());
     86   DCHECK(abort_task_stack_.empty());
     87 }
     88 
     89 void IndexedDBTransaction::ScheduleTask(blink::WebIDBTaskType type,
     90                                         Operation task) {
     91   DCHECK_NE(state_, COMMITTING);
     92   if (state_ == FINISHED)
     93     return;
     94 
     95   timeout_timer_.Stop();
     96   used_ = true;
     97   if (type == blink::WebIDBTaskTypeNormal) {
     98     task_queue_.push(task);
     99     ++diagnostics_.tasks_scheduled;
    100   } else {
    101     preemptive_task_queue_.push(task);
    102   }
    103   RunTasksIfStarted();
    104 }
    105 
    106 void IndexedDBTransaction::ScheduleAbortTask(Operation abort_task) {
    107   DCHECK_NE(FINISHED, state_);
    108   DCHECK(used_);
    109   abort_task_stack_.push(abort_task);
    110 }
    111 
    112 void IndexedDBTransaction::RunTasksIfStarted() {
    113   DCHECK(used_);
    114 
    115   // Not started by the coordinator yet.
    116   if (state_ != STARTED)
    117     return;
    118 
    119   // A task is already posted.
    120   if (should_process_queue_)
    121     return;
    122 
    123   should_process_queue_ = true;
    124   base::MessageLoop::current()->PostTask(
    125       FROM_HERE, base::Bind(&IndexedDBTransaction::ProcessTaskQueue, this));
    126 }
    127 
    128 void IndexedDBTransaction::Abort() {
    129   Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
    130                                "Internal error (unknown cause)"));
    131 }
    132 
    133 void IndexedDBTransaction::Abort(const IndexedDBDatabaseError& error) {
    134   IDB_TRACE1("IndexedDBTransaction::Abort", "txn.id", id());
    135   if (state_ == FINISHED)
    136     return;
    137 
    138   // The last reference to this object may be released while performing the
    139   // abort steps below. We therefore take a self reference to keep ourselves
    140   // alive while executing this method.
    141   scoped_refptr<IndexedDBTransaction> protect(this);
    142 
    143   timeout_timer_.Stop();
    144 
    145   state_ = FINISHED;
    146   should_process_queue_ = false;
    147 
    148   if (backing_store_transaction_begun_)
    149     transaction_->Rollback();
    150 
    151   // Run the abort tasks, if any.
    152   while (!abort_task_stack_.empty())
    153     abort_task_stack_.pop().Run(NULL);
    154 
    155   preemptive_task_queue_.clear();
    156   pending_preemptive_events_ = 0;
    157   task_queue_.clear();
    158 
    159   // Backing store resources (held via cursors) must be released
    160   // before script callbacks are fired, as the script callbacks may
    161   // release references and allow the backing store itself to be
    162   // released, and order is critical.
    163   CloseOpenCursors();
    164   transaction_->Reset();
    165 
    166   // Transactions must also be marked as completed before the
    167   // front-end is notified, as the transaction completion unblocks
    168   // operations like closing connections.
    169   database_->transaction_coordinator().DidFinishTransaction(this);
    170 #ifndef NDEBUG
    171   DCHECK(!database_->transaction_coordinator().IsActive(this));
    172 #endif
    173 
    174   if (callbacks_.get())
    175     callbacks_->OnAbort(id_, error);
    176 
    177   database_->TransactionFinished(this, false);
    178 
    179   database_ = NULL;
    180 }
    181 
    182 bool IndexedDBTransaction::IsTaskQueueEmpty() const {
    183   return preemptive_task_queue_.empty() && task_queue_.empty();
    184 }
    185 
    186 bool IndexedDBTransaction::HasPendingTasks() const {
    187   return pending_preemptive_events_ || !IsTaskQueueEmpty();
    188 }
    189 
    190 void IndexedDBTransaction::RegisterOpenCursor(IndexedDBCursor* cursor) {
    191   open_cursors_.insert(cursor);
    192 }
    193 
    194 void IndexedDBTransaction::UnregisterOpenCursor(IndexedDBCursor* cursor) {
    195   open_cursors_.erase(cursor);
    196 }
    197 
    198 void IndexedDBTransaction::Start() {
    199   // TransactionCoordinator has started this transaction.
    200   DCHECK_EQ(CREATED, state_);
    201   state_ = STARTED;
    202   diagnostics_.start_time = base::Time::Now();
    203 
    204   if (!used_)
    205     return;
    206 
    207   RunTasksIfStarted();
    208 }
    209 
    210 class BlobWriteCallbackImpl : public IndexedDBBackingStore::BlobWriteCallback {
    211  public:
    212   explicit BlobWriteCallbackImpl(
    213       scoped_refptr<IndexedDBTransaction> transaction)
    214       : transaction_(transaction) {}
    215   virtual void Run(bool succeeded) OVERRIDE {
    216     transaction_->BlobWriteComplete(succeeded);
    217   }
    218 
    219  protected:
    220   virtual ~BlobWriteCallbackImpl() {}
    221 
    222  private:
    223   scoped_refptr<IndexedDBTransaction> transaction_;
    224 };
    225 
    226 void IndexedDBTransaction::BlobWriteComplete(bool success) {
    227   IDB_TRACE("IndexedDBTransaction::BlobWriteComplete");
    228   if (state_ == FINISHED)  // aborted
    229     return;
    230   DCHECK_EQ(state_, COMMITTING);
    231   if (success)
    232     CommitPhaseTwo();
    233   else
    234     Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionDataError,
    235                                  "Failed to write blobs."));
    236 }
    237 
    238 leveldb::Status IndexedDBTransaction::Commit() {
    239   IDB_TRACE1("IndexedDBTransaction::Commit", "txn.id", id());
    240 
    241   timeout_timer_.Stop();
    242 
    243   // In multiprocess ports, front-end may have requested a commit but
    244   // an abort has already been initiated asynchronously by the
    245   // back-end.
    246   if (state_ == FINISHED)
    247     return leveldb::Status::OK();
    248   DCHECK_NE(state_, COMMITTING);
    249 
    250   DCHECK(!used_ || state_ == STARTED);
    251   commit_pending_ = true;
    252 
    253   // Front-end has requested a commit, but there may be tasks like
    254   // create_index which are considered synchronous by the front-end
    255   // but are processed asynchronously.
    256   if (HasPendingTasks())
    257     return leveldb::Status::OK();
    258 
    259   state_ = COMMITTING;
    260 
    261   leveldb::Status s;
    262   if (!used_) {
    263     s = CommitPhaseTwo();
    264   } else {
    265     scoped_refptr<IndexedDBBackingStore::BlobWriteCallback> callback(
    266         new BlobWriteCallbackImpl(this));
    267     // CommitPhaseOne will call the callback synchronously if there are no blobs
    268     // to write.
    269     s = transaction_->CommitPhaseOne(callback);
    270     if (!s.ok())
    271       Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionDataError,
    272                                    "Error processing blob journal."));
    273   }
    274 
    275   return s;
    276 }
    277 
    278 leveldb::Status IndexedDBTransaction::CommitPhaseTwo() {
    279   // Abort may have been called just as the blob write completed.
    280   if (state_ == FINISHED)
    281     return leveldb::Status::OK();
    282 
    283   DCHECK_EQ(state_, COMMITTING);
    284 
    285   // The last reference to this object may be released while performing the
    286   // commit steps below. We therefore take a self reference to keep ourselves
    287   // alive while executing this method.
    288   scoped_refptr<IndexedDBTransaction> protect(this);
    289 
    290   state_ = FINISHED;
    291 
    292   leveldb::Status s;
    293   bool committed;
    294   if (!used_) {
    295     committed = true;
    296   } else {
    297     s = transaction_->CommitPhaseTwo();
    298     committed = s.ok();
    299   }
    300 
    301   // Backing store resources (held via cursors) must be released
    302   // before script callbacks are fired, as the script callbacks may
    303   // release references and allow the backing store itself to be
    304   // released, and order is critical.
    305   CloseOpenCursors();
    306   transaction_->Reset();
    307 
    308   // Transactions must also be marked as completed before the
    309   // front-end is notified, as the transaction completion unblocks
    310   // operations like closing connections.
    311   database_->transaction_coordinator().DidFinishTransaction(this);
    312 
    313   if (committed) {
    314     abort_task_stack_.clear();
    315     callbacks_->OnComplete(id_);
    316     database_->TransactionFinished(this, true);
    317   } else {
    318     while (!abort_task_stack_.empty())
    319       abort_task_stack_.pop().Run(NULL);
    320 
    321     callbacks_->OnAbort(
    322         id_,
    323         IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
    324                                "Internal error committing transaction."));
    325     database_->TransactionFinished(this, false);
    326     database_->TransactionCommitFailed(s);
    327   }
    328 
    329   database_ = NULL;
    330   return s;
    331 }
    332 
    333 void IndexedDBTransaction::ProcessTaskQueue() {
    334   IDB_TRACE1("IndexedDBTransaction::ProcessTaskQueue", "txn.id", id());
    335 
    336   // May have been aborted.
    337   if (!should_process_queue_)
    338     return;
    339 
    340   DCHECK(!IsTaskQueueEmpty());
    341   should_process_queue_ = false;
    342 
    343   if (!backing_store_transaction_begun_) {
    344     transaction_->Begin();
    345     backing_store_transaction_begun_ = true;
    346   }
    347 
    348   // The last reference to this object may be released while performing the
    349   // tasks. Take take a self reference to keep this object alive so that
    350   // the loop termination conditions can be checked.
    351   scoped_refptr<IndexedDBTransaction> protect(this);
    352 
    353   TaskQueue* task_queue =
    354       pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
    355   while (!task_queue->empty() && state_ != FINISHED) {
    356     DCHECK_EQ(state_, STARTED);
    357     Operation task(task_queue->pop());
    358     task.Run(this);
    359     if (!pending_preemptive_events_) {
    360       DCHECK(diagnostics_.tasks_completed < diagnostics_.tasks_scheduled);
    361       ++diagnostics_.tasks_completed;
    362     }
    363 
    364     // Event itself may change which queue should be processed next.
    365     task_queue =
    366         pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
    367   }
    368 
    369   // If there are no pending tasks, we haven't already committed/aborted,
    370   // and the front-end requested a commit, it is now safe to do so.
    371   if (!HasPendingTasks() && state_ != FINISHED && commit_pending_) {
    372     Commit();
    373     return;
    374   }
    375 
    376   // The transaction may have been aborted while processing tasks.
    377   if (state_ == FINISHED)
    378     return;
    379 
    380   DCHECK(state_ == STARTED);
    381 
    382   // Otherwise, start a timer in case the front-end gets wedged and
    383   // never requests further activity. Read-only transactions don't
    384   // block other transactions, so don't time those out.
    385   if (mode_ != blink::WebIDBTransactionModeReadOnly) {
    386     timeout_timer_.Start(
    387         FROM_HERE,
    388         base::TimeDelta::FromSeconds(kInactivityTimeoutPeriodSeconds),
    389         base::Bind(&IndexedDBTransaction::Timeout, this));
    390   }
    391 }
    392 
    393 void IndexedDBTransaction::Timeout() {
    394   Abort(IndexedDBDatabaseError(
    395       blink::WebIDBDatabaseExceptionTimeoutError,
    396       base::ASCIIToUTF16("Transaction timed out due to inactivity.")));
    397 }
    398 
    399 void IndexedDBTransaction::CloseOpenCursors() {
    400   for (std::set<IndexedDBCursor*>::iterator i = open_cursors_.begin();
    401        i != open_cursors_.end();
    402        ++i)
    403     (*i)->Close();
    404   open_cursors_.clear();
    405 }
    406 
    407 }  // namespace content
    408