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_EQ(pending_preemptive_events_, 0); 85 DCHECK(task_queue_.empty()); 86 DCHECK(abort_task_stack_.empty()); 87 } 88 89 void IndexedDBTransaction::ScheduleTask(IndexedDBDatabase::TaskType 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 == IndexedDBDatabase::NORMAL_TASK) { 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 void IndexedDBTransaction::Commit() { 239 IDB_TRACE1("IndexedDBTransaction::Commit", "txn.id", id()); 240 241 // In multiprocess ports, front-end may have requested a commit but 242 // an abort has already been initiated asynchronously by the 243 // back-end. 244 if (state_ == FINISHED) 245 return; 246 DCHECK_NE(state_, COMMITTING); 247 248 DCHECK(!used_ || state_ == STARTED); 249 commit_pending_ = true; 250 251 // Front-end has requested a commit, but there may be tasks like 252 // create_index which are considered synchronous by the front-end 253 // but are processed asynchronously. 254 if (HasPendingTasks()) 255 return; 256 257 state_ = COMMITTING; 258 259 if (!used_) { 260 CommitPhaseTwo(); 261 } else { 262 scoped_refptr<IndexedDBBackingStore::BlobWriteCallback> callback( 263 new BlobWriteCallbackImpl(this)); 264 // CommitPhaseOne will call the callback synchronously if there are no blobs 265 // to write. 266 if (!transaction_->CommitPhaseOne(callback).ok()) 267 Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionDataError, 268 "Error processing blob journal.")); 269 } 270 } 271 272 void IndexedDBTransaction::CommitPhaseTwo() { 273 // Abort may have been called just as the blob write completed. 274 if (state_ == FINISHED) 275 return; 276 277 DCHECK_EQ(state_, COMMITTING); 278 279 // The last reference to this object may be released while performing the 280 // commit steps below. We therefore take a self reference to keep ourselves 281 // alive while executing this method. 282 scoped_refptr<IndexedDBTransaction> protect(this); 283 284 timeout_timer_.Stop(); 285 286 state_ = FINISHED; 287 288 bool committed = !used_ || transaction_->CommitPhaseTwo().ok(); 289 290 // Backing store resources (held via cursors) must be released 291 // before script callbacks are fired, as the script callbacks may 292 // release references and allow the backing store itself to be 293 // released, and order is critical. 294 CloseOpenCursors(); 295 transaction_->Reset(); 296 297 // Transactions must also be marked as completed before the 298 // front-end is notified, as the transaction completion unblocks 299 // operations like closing connections. 300 database_->transaction_coordinator().DidFinishTransaction(this); 301 302 if (committed) { 303 abort_task_stack_.clear(); 304 callbacks_->OnComplete(id_); 305 database_->TransactionFinished(this, true); 306 } else { 307 while (!abort_task_stack_.empty()) 308 abort_task_stack_.pop().Run(NULL); 309 310 callbacks_->OnAbort( 311 id_, 312 IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError, 313 "Internal error committing transaction.")); 314 database_->TransactionFinished(this, false); 315 database_->TransactionCommitFailed(); 316 } 317 318 database_ = NULL; 319 } 320 321 void IndexedDBTransaction::ProcessTaskQueue() { 322 IDB_TRACE1("IndexedDBTransaction::ProcessTaskQueue", "txn.id", id()); 323 324 // May have been aborted. 325 if (!should_process_queue_) 326 return; 327 328 DCHECK(!IsTaskQueueEmpty()); 329 should_process_queue_ = false; 330 331 if (!backing_store_transaction_begun_) { 332 transaction_->Begin(); 333 backing_store_transaction_begun_ = true; 334 } 335 336 // The last reference to this object may be released while performing the 337 // tasks. Take take a self reference to keep this object alive so that 338 // the loop termination conditions can be checked. 339 scoped_refptr<IndexedDBTransaction> protect(this); 340 341 TaskQueue* task_queue = 342 pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_; 343 while (!task_queue->empty() && state_ != FINISHED) { 344 DCHECK_EQ(state_, STARTED); 345 Operation task(task_queue->pop()); 346 task.Run(this); 347 if (!pending_preemptive_events_) { 348 DCHECK(diagnostics_.tasks_completed < diagnostics_.tasks_scheduled); 349 ++diagnostics_.tasks_completed; 350 } 351 352 // Event itself may change which queue should be processed next. 353 task_queue = 354 pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_; 355 } 356 357 // If there are no pending tasks, we haven't already committed/aborted, 358 // and the front-end requested a commit, it is now safe to do so. 359 if (!HasPendingTasks() && state_ != FINISHED && commit_pending_) { 360 Commit(); 361 return; 362 } 363 364 // The transaction may have been aborted while processing tasks. 365 if (state_ == FINISHED) 366 return; 367 368 DCHECK(state_ == STARTED); 369 370 // Otherwise, start a timer in case the front-end gets wedged and 371 // never requests further activity. Read-only transactions don't 372 // block other transactions, so don't time those out. 373 if (mode_ != indexed_db::TRANSACTION_READ_ONLY) { 374 timeout_timer_.Start( 375 FROM_HERE, 376 base::TimeDelta::FromSeconds(kInactivityTimeoutPeriodSeconds), 377 base::Bind(&IndexedDBTransaction::Timeout, this)); 378 } 379 } 380 381 void IndexedDBTransaction::Timeout() { 382 Abort(IndexedDBDatabaseError( 383 blink::WebIDBDatabaseExceptionTimeoutError, 384 base::ASCIIToUTF16("Transaction timed out due to inactivity."))); 385 } 386 387 void IndexedDBTransaction::CloseOpenCursors() { 388 for (std::set<IndexedDBCursor*>::iterator i = open_cursors_.begin(); 389 i != open_cursors_.end(); 390 ++i) 391 (*i)->Close(); 392 open_cursors_.clear(); 393 } 394 395 } // namespace content 396