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