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 19namespace content { 20 21const int64 kInactivityTimeoutPeriodSeconds = 60; 22 23IndexedDBTransaction::TaskQueue::TaskQueue() {} 24IndexedDBTransaction::TaskQueue::~TaskQueue() { clear(); } 25 26void IndexedDBTransaction::TaskQueue::clear() { 27 while (!queue_.empty()) 28 queue_.pop(); 29} 30 31IndexedDBTransaction::Operation IndexedDBTransaction::TaskQueue::pop() { 32 DCHECK(!queue_.empty()); 33 Operation task(queue_.front()); 34 queue_.pop(); 35 return task; 36} 37 38IndexedDBTransaction::TaskStack::TaskStack() {} 39IndexedDBTransaction::TaskStack::~TaskStack() { clear(); } 40 41void IndexedDBTransaction::TaskStack::clear() { 42 while (!stack_.empty()) 43 stack_.pop(); 44} 45 46IndexedDBTransaction::Operation IndexedDBTransaction::TaskStack::pop() { 47 DCHECK(!stack_.empty()); 48 Operation task(stack_.top()); 49 stack_.pop(); 50 return task; 51} 52 53IndexedDBTransaction::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 79IndexedDBTransaction::~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 88void 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 100void 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 116void 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 132void IndexedDBTransaction::Abort() { 133 Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError, 134 "Internal error (unknown cause)")); 135} 136 137void 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 186bool IndexedDBTransaction::IsTaskQueueEmpty() const { 187 return preemptive_task_queue_.empty() && task_queue_.empty(); 188} 189 190bool IndexedDBTransaction::HasPendingTasks() const { 191 return pending_preemptive_events_ || !IsTaskQueueEmpty(); 192} 193 194void IndexedDBTransaction::RegisterOpenCursor(IndexedDBCursor* cursor) { 195 open_cursors_.insert(cursor); 196} 197 198void IndexedDBTransaction::UnregisterOpenCursor(IndexedDBCursor* cursor) { 199 open_cursors_.erase(cursor); 200} 201 202void 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 215void 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 276void 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 331void IndexedDBTransaction::Timeout() { 332 Abort(IndexedDBDatabaseError( 333 blink::WebIDBDatabaseExceptionTimeoutError, 334 ASCIIToUTF16("Transaction timed out due to inactivity."))); 335} 336 337void 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