Revert 268405 "Make sure that ScratchBuffer::Allocate() always r..."
[chromium-blink-merge.git] / content / browser / indexed_db / indexed_db_transaction.cc
blobbb7fc03c5bf07f4461db299134a56c5a417d4694
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.
5 #include "content/browser/indexed_db/indexed_db_transaction.h"
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"
19 namespace content {
21 const int64 kInactivityTimeoutPeriodSeconds = 60;
23 IndexedDBTransaction::TaskQueue::TaskQueue() {}
24 IndexedDBTransaction::TaskQueue::~TaskQueue() { clear(); }
26 void IndexedDBTransaction::TaskQueue::clear() {
27 while (!queue_.empty())
28 queue_.pop();
31 IndexedDBTransaction::Operation IndexedDBTransaction::TaskQueue::pop() {
32 DCHECK(!queue_.empty());
33 Operation task(queue_.front());
34 queue_.pop();
35 return task;
38 IndexedDBTransaction::TaskStack::TaskStack() {}
39 IndexedDBTransaction::TaskStack::~TaskStack() { clear(); }
41 void IndexedDBTransaction::TaskStack::clear() {
42 while (!stack_.empty())
43 stack_.pop();
46 IndexedDBTransaction::Operation IndexedDBTransaction::TaskStack::pop() {
47 DCHECK(!stack_.empty());
48 Operation task(stack_.top());
49 stack_.pop();
50 return task;
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);
74 diagnostics_.tasks_scheduled = 0;
75 diagnostics_.tasks_completed = 0;
76 diagnostics_.creation_time = base::Time::Now();
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());
89 void IndexedDBTransaction::ScheduleTask(Operation task, Operation abort_task) {
90 if (state_ == FINISHED)
91 return;
93 timeout_timer_.Stop();
94 used_ = true;
95 task_queue_.push(task);
96 ++diagnostics_.tasks_scheduled;
97 abort_task_stack_.push(abort_task);
98 RunTasksIfStarted();
101 void IndexedDBTransaction::ScheduleTask(IndexedDBDatabase::TaskType type,
102 Operation task) {
103 if (state_ == FINISHED)
104 return;
106 timeout_timer_.Stop();
107 used_ = true;
108 if (type == IndexedDBDatabase::NORMAL_TASK) {
109 task_queue_.push(task);
110 ++diagnostics_.tasks_scheduled;
111 } else {
112 preemptive_task_queue_.push(task);
114 RunTasksIfStarted();
117 void IndexedDBTransaction::RunTasksIfStarted() {
118 DCHECK(used_);
120 // Not started by the coordinator yet.
121 if (state_ != STARTED)
122 return;
124 // A task is already posted.
125 if (should_process_queue_)
126 return;
128 should_process_queue_ = true;
129 base::MessageLoop::current()->PostTask(
130 FROM_HERE, base::Bind(&IndexedDBTransaction::ProcessTaskQueue, this));
133 void IndexedDBTransaction::Abort() {
134 Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
135 "Internal error (unknown cause)"));
138 void IndexedDBTransaction::Abort(const IndexedDBDatabaseError& error) {
139 IDB_TRACE("IndexedDBTransaction::Abort");
140 if (state_ == FINISHED)
141 return;
143 // The last reference to this object may be released while performing the
144 // abort steps below. We therefore take a self reference to keep ourselves
145 // alive while executing this method.
146 scoped_refptr<IndexedDBTransaction> protect(this);
148 timeout_timer_.Stop();
150 state_ = FINISHED;
151 should_process_queue_ = false;
153 if (backing_store_transaction_begun_)
154 transaction_->Rollback();
156 // Run the abort tasks, if any.
157 while (!abort_task_stack_.empty())
158 abort_task_stack_.pop().Run(NULL);
160 preemptive_task_queue_.clear();
161 pending_preemptive_events_ = 0;
162 task_queue_.clear();
164 // Backing store resources (held via cursors) must be released
165 // before script callbacks are fired, as the script callbacks may
166 // release references and allow the backing store itself to be
167 // released, and order is critical.
168 CloseOpenCursors();
169 transaction_->Reset();
171 // Transactions must also be marked as completed before the
172 // front-end is notified, as the transaction completion unblocks
173 // operations like closing connections.
174 database_->transaction_coordinator().DidFinishTransaction(this);
175 #ifndef NDEBUG
176 DCHECK(!database_->transaction_coordinator().IsActive(this));
177 #endif
179 if (callbacks_.get())
180 callbacks_->OnAbort(id_, error);
182 database_->TransactionFinished(this, false);
184 database_ = NULL;
187 bool IndexedDBTransaction::IsTaskQueueEmpty() const {
188 return preemptive_task_queue_.empty() && task_queue_.empty();
191 bool IndexedDBTransaction::HasPendingTasks() const {
192 return pending_preemptive_events_ || !IsTaskQueueEmpty();
195 void IndexedDBTransaction::RegisterOpenCursor(IndexedDBCursor* cursor) {
196 open_cursors_.insert(cursor);
199 void IndexedDBTransaction::UnregisterOpenCursor(IndexedDBCursor* cursor) {
200 open_cursors_.erase(cursor);
203 void IndexedDBTransaction::Start() {
204 // TransactionCoordinator has started this transaction.
205 DCHECK_EQ(CREATED, state_);
206 state_ = STARTED;
207 diagnostics_.start_time = base::Time::Now();
209 if (!used_)
210 return;
212 RunTasksIfStarted();
215 void IndexedDBTransaction::Commit() {
216 IDB_TRACE("IndexedDBTransaction::Commit");
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;
224 DCHECK(!used_ || state_ == STARTED);
225 commit_pending_ = true;
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;
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);
238 timeout_timer_.Stop();
240 state_ = FINISHED;
242 bool committed = !used_ || transaction_->Commit().ok();
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();
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);
256 if (committed) {
257 abort_task_stack_.clear();
258 callbacks_->OnComplete(id_);
259 database_->TransactionFinished(this, true);
260 } else {
261 while (!abort_task_stack_.empty())
262 abort_task_stack_.pop().Run(NULL);
264 callbacks_->OnAbort(
265 id_,
266 IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
267 "Internal error committing transaction."));
268 database_->TransactionFinished(this, false);
269 database_->TransactionCommitFailed();
272 database_ = NULL;
275 void IndexedDBTransaction::ProcessTaskQueue() {
276 IDB_TRACE("IndexedDBTransaction::ProcessTaskQueue");
278 // May have been aborted.
279 if (!should_process_queue_)
280 return;
282 DCHECK(!IsTaskQueueEmpty());
283 should_process_queue_ = false;
285 if (!backing_store_transaction_begun_) {
286 transaction_->Begin();
287 backing_store_transaction_begun_ = true;
290 // The last reference to this object may be released while performing the
291 // tasks. Take take a self reference to keep this object alive so that
292 // the loop termination conditions can be checked.
293 scoped_refptr<IndexedDBTransaction> protect(this);
295 TaskQueue* task_queue =
296 pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
297 while (!task_queue->empty() && state_ != FINISHED) {
298 DCHECK_EQ(STARTED, state_);
299 Operation task(task_queue->pop());
300 task.Run(this);
301 if (!pending_preemptive_events_) {
302 DCHECK(diagnostics_.tasks_completed < diagnostics_.tasks_scheduled);
303 ++diagnostics_.tasks_completed;
306 // Event itself may change which queue should be processed next.
307 task_queue =
308 pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
311 // If there are no pending tasks, we haven't already committed/aborted,
312 // and the front-end requested a commit, it is now safe to do so.
313 if (!HasPendingTasks() && state_ != FINISHED && commit_pending_) {
314 Commit();
315 return;
318 // The transaction may have been aborted while processing tasks.
319 if (state_ == FINISHED)
320 return;
322 // Otherwise, start a timer in case the front-end gets wedged and
323 // never requests further activity. Read-only transactions don't
324 // block other transactions, so don't time those out.
325 if (mode_ != indexed_db::TRANSACTION_READ_ONLY) {
326 timeout_timer_.Start(
327 FROM_HERE,
328 base::TimeDelta::FromSeconds(kInactivityTimeoutPeriodSeconds),
329 base::Bind(&IndexedDBTransaction::Timeout, this));
333 void IndexedDBTransaction::Timeout() {
334 Abort(IndexedDBDatabaseError(
335 blink::WebIDBDatabaseExceptionTimeoutError,
336 base::ASCIIToUTF16("Transaction timed out due to inactivity.")));
339 void IndexedDBTransaction::CloseOpenCursors() {
340 for (std::set<IndexedDBCursor*>::iterator i = open_cursors_.begin();
341 i != open_cursors_.end();
342 ++i)
343 (*i)->Close();
344 open_cursors_.clear();
347 } // namespace content