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