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