Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / browser / indexed_db / indexed_db_transaction.cc
blob88491dee201bfeb8158b9502dd1a74119754b9c8
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"
18 #include "third_party/leveldatabase/env_chromium.h"
20 namespace content {
22 const int64 kInactivityTimeoutPeriodSeconds = 60;
24 IndexedDBTransaction::TaskQueue::TaskQueue() {}
25 IndexedDBTransaction::TaskQueue::~TaskQueue() { clear(); }
27 void IndexedDBTransaction::TaskQueue::clear() {
28 while (!queue_.empty())
29 queue_.pop();
32 IndexedDBTransaction::Operation IndexedDBTransaction::TaskQueue::pop() {
33 DCHECK(!queue_.empty());
34 Operation task(queue_.front());
35 queue_.pop();
36 return task;
39 IndexedDBTransaction::TaskStack::TaskStack() {}
40 IndexedDBTransaction::TaskStack::~TaskStack() { clear(); }
42 void IndexedDBTransaction::TaskStack::clear() {
43 while (!stack_.empty())
44 stack_.pop();
47 IndexedDBTransaction::Operation IndexedDBTransaction::TaskStack::pop() {
48 DCHECK(!stack_.empty());
49 Operation task(stack_.top());
50 stack_.pop();
51 return task;
54 IndexedDBTransaction::IndexedDBTransaction(
55 int64 id,
56 scoped_refptr<IndexedDBDatabaseCallbacks> callbacks,
57 const std::set<int64>& object_store_ids,
58 blink::WebIDBTransactionMode mode,
59 IndexedDBDatabase* database,
60 IndexedDBBackingStore::Transaction* backing_store_transaction)
61 : id_(id),
62 object_store_ids_(object_store_ids),
63 mode_(mode),
64 used_(false),
65 state_(CREATED),
66 commit_pending_(false),
67 callbacks_(callbacks),
68 database_(database),
69 transaction_(backing_store_transaction),
70 backing_store_transaction_begun_(false),
71 should_process_queue_(false),
72 pending_preemptive_events_(0) {
73 database_->transaction_coordinator().DidCreateTransaction(this);
75 diagnostics_.tasks_scheduled = 0;
76 diagnostics_.tasks_completed = 0;
77 diagnostics_.creation_time = base::Time::Now();
80 IndexedDBTransaction::~IndexedDBTransaction() {
81 // It shouldn't be possible for this object to get deleted until it's either
82 // complete or aborted.
83 DCHECK_EQ(state_, FINISHED);
84 DCHECK(preemptive_task_queue_.empty());
85 DCHECK_EQ(pending_preemptive_events_, 0);
86 DCHECK(task_queue_.empty());
87 DCHECK(abort_task_stack_.empty());
90 void IndexedDBTransaction::ScheduleTask(blink::WebIDBTaskType type,
91 Operation task) {
92 DCHECK_NE(state_, COMMITTING);
93 if (state_ == FINISHED)
94 return;
96 timeout_timer_.Stop();
97 used_ = true;
98 if (type == blink::WebIDBTaskTypeNormal) {
99 task_queue_.push(task);
100 ++diagnostics_.tasks_scheduled;
101 } else {
102 preemptive_task_queue_.push(task);
104 RunTasksIfStarted();
107 void IndexedDBTransaction::ScheduleAbortTask(Operation abort_task) {
108 DCHECK_NE(FINISHED, state_);
109 DCHECK(used_);
110 abort_task_stack_.push(abort_task);
113 void IndexedDBTransaction::RunTasksIfStarted() {
114 DCHECK(used_);
116 // Not started by the coordinator yet.
117 if (state_ != STARTED)
118 return;
120 // A task is already posted.
121 if (should_process_queue_)
122 return;
124 should_process_queue_ = true;
125 base::MessageLoop::current()->PostTask(
126 FROM_HERE, base::Bind(&IndexedDBTransaction::ProcessTaskQueue, this));
129 void IndexedDBTransaction::Abort() {
130 Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
131 "Internal error (unknown cause)"));
134 void IndexedDBTransaction::Abort(const IndexedDBDatabaseError& error) {
135 IDB_TRACE1("IndexedDBTransaction::Abort", "txn.id", id());
136 if (state_ == FINISHED)
137 return;
139 // The last reference to this object may be released while performing the
140 // abort steps below. We therefore take a self reference to keep ourselves
141 // alive while executing this method.
142 scoped_refptr<IndexedDBTransaction> protect(this);
144 timeout_timer_.Stop();
146 state_ = FINISHED;
147 should_process_queue_ = false;
149 if (backing_store_transaction_begun_)
150 transaction_->Rollback();
152 // Run the abort tasks, if any.
153 while (!abort_task_stack_.empty())
154 abort_task_stack_.pop().Run(NULL);
156 preemptive_task_queue_.clear();
157 pending_preemptive_events_ = 0;
158 task_queue_.clear();
160 // Backing store resources (held via cursors) must be released
161 // before script callbacks are fired, as the script callbacks may
162 // release references and allow the backing store itself to be
163 // released, and order is critical.
164 CloseOpenCursors();
165 transaction_->Reset();
167 // Transactions must also be marked as completed before the
168 // front-end is notified, as the transaction completion unblocks
169 // operations like closing connections.
170 database_->transaction_coordinator().DidFinishTransaction(this);
171 #ifndef NDEBUG
172 DCHECK(!database_->transaction_coordinator().IsActive(this));
173 #endif
175 if (callbacks_.get())
176 callbacks_->OnAbort(id_, error);
178 database_->TransactionFinished(this, false);
180 database_ = NULL;
183 bool IndexedDBTransaction::IsTaskQueueEmpty() const {
184 return preemptive_task_queue_.empty() && task_queue_.empty();
187 bool IndexedDBTransaction::HasPendingTasks() const {
188 return pending_preemptive_events_ || !IsTaskQueueEmpty();
191 void IndexedDBTransaction::RegisterOpenCursor(IndexedDBCursor* cursor) {
192 open_cursors_.insert(cursor);
195 void IndexedDBTransaction::UnregisterOpenCursor(IndexedDBCursor* cursor) {
196 open_cursors_.erase(cursor);
199 void IndexedDBTransaction::Start() {
200 // TransactionCoordinator has started this transaction.
201 DCHECK_EQ(CREATED, state_);
202 state_ = STARTED;
203 diagnostics_.start_time = base::Time::Now();
205 if (!used_)
206 return;
208 RunTasksIfStarted();
211 class BlobWriteCallbackImpl : public IndexedDBBackingStore::BlobWriteCallback {
212 public:
213 explicit BlobWriteCallbackImpl(
214 scoped_refptr<IndexedDBTransaction> transaction)
215 : transaction_(transaction) {}
216 void Run(bool succeeded) override {
217 transaction_->BlobWriteComplete(succeeded);
220 protected:
221 ~BlobWriteCallbackImpl() override {}
223 private:
224 scoped_refptr<IndexedDBTransaction> transaction_;
227 void IndexedDBTransaction::BlobWriteComplete(bool success) {
228 IDB_TRACE("IndexedDBTransaction::BlobWriteComplete");
229 if (state_ == FINISHED) // aborted
230 return;
231 DCHECK_EQ(state_, COMMITTING);
232 if (success)
233 CommitPhaseTwo();
234 else
235 Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionDataError,
236 "Failed to write blobs."));
239 leveldb::Status IndexedDBTransaction::Commit() {
240 IDB_TRACE1("IndexedDBTransaction::Commit", "txn.id", id());
242 timeout_timer_.Stop();
244 // In multiprocess ports, front-end may have requested a commit but
245 // an abort has already been initiated asynchronously by the
246 // back-end.
247 if (state_ == FINISHED)
248 return leveldb::Status::OK();
249 DCHECK_NE(state_, COMMITTING);
251 DCHECK(!used_ || state_ == STARTED);
252 commit_pending_ = true;
254 // Front-end has requested a commit, but there may be tasks like
255 // create_index which are considered synchronous by the front-end
256 // but are processed asynchronously.
257 if (HasPendingTasks())
258 return leveldb::Status::OK();
260 state_ = COMMITTING;
262 leveldb::Status s;
263 if (!used_) {
264 s = CommitPhaseTwo();
265 } else {
266 scoped_refptr<IndexedDBBackingStore::BlobWriteCallback> callback(
267 new BlobWriteCallbackImpl(this));
268 // CommitPhaseOne will call the callback synchronously if there are no blobs
269 // to write.
270 s = transaction_->CommitPhaseOne(callback);
271 if (!s.ok())
272 Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionDataError,
273 "Error processing blob journal."));
276 return s;
279 leveldb::Status IndexedDBTransaction::CommitPhaseTwo() {
280 // Abort may have been called just as the blob write completed.
281 if (state_ == FINISHED)
282 return leveldb::Status::OK();
284 DCHECK_EQ(state_, COMMITTING);
286 // The last reference to this object may be released while performing the
287 // commit steps below. We therefore take a self reference to keep ourselves
288 // alive while executing this method.
289 scoped_refptr<IndexedDBTransaction> protect(this);
291 state_ = FINISHED;
293 leveldb::Status s;
294 bool committed;
295 if (!used_) {
296 committed = true;
297 } else {
298 s = transaction_->CommitPhaseTwo();
299 committed = s.ok();
302 // Backing store resources (held via cursors) must be released
303 // before script callbacks are fired, as the script callbacks may
304 // release references and allow the backing store itself to be
305 // released, and order is critical.
306 CloseOpenCursors();
307 transaction_->Reset();
309 // Transactions must also be marked as completed before the
310 // front-end is notified, as the transaction completion unblocks
311 // operations like closing connections.
312 database_->transaction_coordinator().DidFinishTransaction(this);
314 if (committed) {
315 abort_task_stack_.clear();
316 callbacks_->OnComplete(id_);
317 database_->TransactionFinished(this, true);
318 } else {
319 while (!abort_task_stack_.empty())
320 abort_task_stack_.pop().Run(NULL);
322 IndexedDBDatabaseError error;
323 if (leveldb_env::IndicatesDiskFull(s)) {
324 error = IndexedDBDatabaseError(
325 blink::WebIDBDatabaseExceptionQuotaError,
326 "Encountered disk full while committing transaction.");
327 } else {
328 error = IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
329 "Internal error committing transaction.");
331 callbacks_->OnAbort(id_, error);
333 database_->TransactionFinished(this, false);
334 database_->TransactionCommitFailed(s);
337 database_ = NULL;
338 return s;
341 void IndexedDBTransaction::ProcessTaskQueue() {
342 IDB_TRACE1("IndexedDBTransaction::ProcessTaskQueue", "txn.id", id());
344 // May have been aborted.
345 if (!should_process_queue_)
346 return;
348 DCHECK(!IsTaskQueueEmpty());
349 should_process_queue_ = false;
351 if (!backing_store_transaction_begun_) {
352 transaction_->Begin();
353 backing_store_transaction_begun_ = true;
356 // The last reference to this object may be released while performing the
357 // tasks. Take take a self reference to keep this object alive so that
358 // the loop termination conditions can be checked.
359 scoped_refptr<IndexedDBTransaction> protect(this);
361 TaskQueue* task_queue =
362 pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
363 while (!task_queue->empty() && state_ != FINISHED) {
364 DCHECK_EQ(state_, STARTED);
365 Operation task(task_queue->pop());
366 task.Run(this);
367 if (!pending_preemptive_events_) {
368 DCHECK(diagnostics_.tasks_completed < diagnostics_.tasks_scheduled);
369 ++diagnostics_.tasks_completed;
372 // Event itself may change which queue should be processed next.
373 task_queue =
374 pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
377 // If there are no pending tasks, we haven't already committed/aborted,
378 // and the front-end requested a commit, it is now safe to do so.
379 if (!HasPendingTasks() && state_ != FINISHED && commit_pending_) {
380 Commit();
381 return;
384 // The transaction may have been aborted while processing tasks.
385 if (state_ == FINISHED)
386 return;
388 DCHECK(state_ == STARTED);
390 // Otherwise, start a timer in case the front-end gets wedged and
391 // never requests further activity. Read-only transactions don't
392 // block other transactions, so don't time those out.
393 if (mode_ != blink::WebIDBTransactionModeReadOnly) {
394 timeout_timer_.Start(
395 FROM_HERE,
396 base::TimeDelta::FromSeconds(kInactivityTimeoutPeriodSeconds),
397 base::Bind(&IndexedDBTransaction::Timeout, this));
401 void IndexedDBTransaction::Timeout() {
402 Abort(IndexedDBDatabaseError(
403 blink::WebIDBDatabaseExceptionTimeoutError,
404 base::ASCIIToUTF16("Transaction timed out due to inactivity.")));
407 void IndexedDBTransaction::CloseOpenCursors() {
408 for (auto* cursor : open_cursors_)
409 cursor->Close();
410 open_cursors_.clear();
413 } // namespace content