Linux: Depend on liberation-fonts package for RPMs.
[chromium-blink-merge.git] / content / browser / indexed_db / indexed_db_transaction.cc
blob36faa51da0f10ae60d4cf1f1ced48e0e20be3a80
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/location.h"
9 #include "base/logging.h"
10 #include "base/single_thread_task_runner.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/thread_task_runner_handle.h"
13 #include "content/browser/indexed_db/indexed_db_backing_store.h"
14 #include "content/browser/indexed_db/indexed_db_cursor.h"
15 #include "content/browser/indexed_db/indexed_db_database.h"
16 #include "content/browser/indexed_db/indexed_db_database_callbacks.h"
17 #include "content/browser/indexed_db/indexed_db_tracing.h"
18 #include "content/browser/indexed_db/indexed_db_transaction_coordinator.h"
19 #include "third_party/WebKit/public/platform/modules/indexeddb/WebIDBDatabaseException.h"
20 #include "third_party/leveldatabase/env_chromium.h"
22 namespace content {
24 const int64 kInactivityTimeoutPeriodSeconds = 60;
26 IndexedDBTransaction::TaskQueue::TaskQueue() {}
27 IndexedDBTransaction::TaskQueue::~TaskQueue() { clear(); }
29 void IndexedDBTransaction::TaskQueue::clear() {
30 while (!queue_.empty())
31 queue_.pop();
34 IndexedDBTransaction::Operation IndexedDBTransaction::TaskQueue::pop() {
35 DCHECK(!queue_.empty());
36 Operation task(queue_.front());
37 queue_.pop();
38 return task;
41 IndexedDBTransaction::TaskStack::TaskStack() {}
42 IndexedDBTransaction::TaskStack::~TaskStack() { clear(); }
44 void IndexedDBTransaction::TaskStack::clear() {
45 while (!stack_.empty())
46 stack_.pop();
49 IndexedDBTransaction::Operation IndexedDBTransaction::TaskStack::pop() {
50 DCHECK(!stack_.empty());
51 Operation task(stack_.top());
52 stack_.pop();
53 return task;
56 IndexedDBTransaction::IndexedDBTransaction(
57 int64 id,
58 scoped_refptr<IndexedDBDatabaseCallbacks> callbacks,
59 const std::set<int64>& object_store_ids,
60 blink::WebIDBTransactionMode mode,
61 IndexedDBDatabase* database,
62 IndexedDBBackingStore::Transaction* backing_store_transaction)
63 : id_(id),
64 object_store_ids_(object_store_ids),
65 mode_(mode),
66 used_(false),
67 state_(CREATED),
68 commit_pending_(false),
69 callbacks_(callbacks),
70 database_(database),
71 transaction_(backing_store_transaction),
72 backing_store_transaction_begun_(false),
73 should_process_queue_(false),
74 pending_preemptive_events_(0) {
75 database_->transaction_coordinator().DidCreateTransaction(this);
77 diagnostics_.tasks_scheduled = 0;
78 diagnostics_.tasks_completed = 0;
79 diagnostics_.creation_time = base::Time::Now();
82 IndexedDBTransaction::~IndexedDBTransaction() {
83 // It shouldn't be possible for this object to get deleted until it's either
84 // complete or aborted.
85 DCHECK_EQ(state_, FINISHED);
86 DCHECK(preemptive_task_queue_.empty());
87 DCHECK_EQ(pending_preemptive_events_, 0);
88 DCHECK(task_queue_.empty());
89 DCHECK(abort_task_stack_.empty());
92 void IndexedDBTransaction::ScheduleTask(blink::WebIDBTaskType type,
93 Operation task) {
94 DCHECK_NE(state_, COMMITTING);
95 if (state_ == FINISHED)
96 return;
98 timeout_timer_.Stop();
99 used_ = true;
100 if (type == blink::WebIDBTaskTypeNormal) {
101 task_queue_.push(task);
102 ++diagnostics_.tasks_scheduled;
103 } else {
104 preemptive_task_queue_.push(task);
106 RunTasksIfStarted();
109 void IndexedDBTransaction::ScheduleAbortTask(Operation abort_task) {
110 DCHECK_NE(FINISHED, state_);
111 DCHECK(used_);
112 abort_task_stack_.push(abort_task);
115 void IndexedDBTransaction::RunTasksIfStarted() {
116 DCHECK(used_);
118 // Not started by the coordinator yet.
119 if (state_ != STARTED)
120 return;
122 // A task is already posted.
123 if (should_process_queue_)
124 return;
126 should_process_queue_ = true;
127 base::ThreadTaskRunnerHandle::Get()->PostTask(
128 FROM_HERE, base::Bind(&IndexedDBTransaction::ProcessTaskQueue, this));
131 void IndexedDBTransaction::Abort() {
132 Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
133 "Internal error (unknown cause)"));
136 void IndexedDBTransaction::Abort(const IndexedDBDatabaseError& error) {
137 IDB_TRACE1("IndexedDBTransaction::Abort", "txn.id", id());
138 if (state_ == FINISHED)
139 return;
141 // The last reference to this object may be released while performing the
142 // abort steps below. We therefore take a self reference to keep ourselves
143 // alive while executing this method.
144 scoped_refptr<IndexedDBTransaction> protect(this);
146 timeout_timer_.Stop();
148 state_ = FINISHED;
149 should_process_queue_ = false;
151 if (backing_store_transaction_begun_)
152 transaction_->Rollback();
154 // Run the abort tasks, if any.
155 while (!abort_task_stack_.empty())
156 abort_task_stack_.pop().Run(NULL);
158 preemptive_task_queue_.clear();
159 pending_preemptive_events_ = 0;
160 task_queue_.clear();
162 // Backing store resources (held via cursors) must be released
163 // before script callbacks are fired, as the script callbacks may
164 // release references and allow the backing store itself to be
165 // released, and order is critical.
166 CloseOpenCursors();
167 transaction_->Reset();
169 // Transactions must also be marked as completed before the
170 // front-end is notified, as the transaction completion unblocks
171 // operations like closing connections.
172 database_->transaction_coordinator().DidFinishTransaction(this);
173 #ifndef NDEBUG
174 DCHECK(!database_->transaction_coordinator().IsActive(this));
175 #endif
177 if (callbacks_.get())
178 callbacks_->OnAbort(id_, error);
180 database_->TransactionFinished(this, false);
182 database_ = NULL;
185 bool IndexedDBTransaction::IsTaskQueueEmpty() const {
186 return preemptive_task_queue_.empty() && task_queue_.empty();
189 bool IndexedDBTransaction::HasPendingTasks() const {
190 return pending_preemptive_events_ || !IsTaskQueueEmpty();
193 void IndexedDBTransaction::RegisterOpenCursor(IndexedDBCursor* cursor) {
194 open_cursors_.insert(cursor);
197 void IndexedDBTransaction::UnregisterOpenCursor(IndexedDBCursor* cursor) {
198 open_cursors_.erase(cursor);
201 void IndexedDBTransaction::Start() {
202 // TransactionCoordinator has started this transaction.
203 DCHECK_EQ(CREATED, state_);
204 state_ = STARTED;
205 diagnostics_.start_time = base::Time::Now();
207 if (!used_)
208 return;
210 RunTasksIfStarted();
213 class BlobWriteCallbackImpl : public IndexedDBBackingStore::BlobWriteCallback {
214 public:
215 explicit BlobWriteCallbackImpl(
216 scoped_refptr<IndexedDBTransaction> transaction)
217 : transaction_(transaction) {}
218 void Run(bool succeeded) override {
219 transaction_->BlobWriteComplete(succeeded);
222 protected:
223 ~BlobWriteCallbackImpl() override {}
225 private:
226 scoped_refptr<IndexedDBTransaction> transaction_;
229 void IndexedDBTransaction::BlobWriteComplete(bool success) {
230 IDB_TRACE("IndexedDBTransaction::BlobWriteComplete");
231 if (state_ == FINISHED) // aborted
232 return;
233 DCHECK_EQ(state_, COMMITTING);
234 if (success)
235 CommitPhaseTwo();
236 else
237 Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionDataError,
238 "Failed to write blobs."));
241 leveldb::Status IndexedDBTransaction::Commit() {
242 IDB_TRACE1("IndexedDBTransaction::Commit", "txn.id", id());
244 timeout_timer_.Stop();
246 // In multiprocess ports, front-end may have requested a commit but
247 // an abort has already been initiated asynchronously by the
248 // back-end.
249 if (state_ == FINISHED)
250 return leveldb::Status::OK();
251 DCHECK_NE(state_, COMMITTING);
253 DCHECK(!used_ || state_ == STARTED);
254 commit_pending_ = true;
256 // Front-end has requested a commit, but there may be tasks like
257 // create_index which are considered synchronous by the front-end
258 // but are processed asynchronously.
259 if (HasPendingTasks())
260 return leveldb::Status::OK();
262 state_ = COMMITTING;
264 leveldb::Status s;
265 if (!used_) {
266 s = CommitPhaseTwo();
267 } else {
268 scoped_refptr<IndexedDBBackingStore::BlobWriteCallback> callback(
269 new BlobWriteCallbackImpl(this));
270 // CommitPhaseOne will call the callback synchronously if there are no blobs
271 // to write.
272 s = transaction_->CommitPhaseOne(callback);
273 if (!s.ok())
274 Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionDataError,
275 "Error processing blob journal."));
278 return s;
281 leveldb::Status IndexedDBTransaction::CommitPhaseTwo() {
282 // Abort may have been called just as the blob write completed.
283 if (state_ == FINISHED)
284 return leveldb::Status::OK();
286 DCHECK_EQ(state_, COMMITTING);
288 // The last reference to this object may be released while performing the
289 // commit steps below. We therefore take a self reference to keep ourselves
290 // alive while executing this method.
291 scoped_refptr<IndexedDBTransaction> protect(this);
293 state_ = FINISHED;
295 leveldb::Status s;
296 bool committed;
297 if (!used_) {
298 committed = true;
299 } else {
300 s = transaction_->CommitPhaseTwo();
301 committed = s.ok();
304 // Backing store resources (held via cursors) must be released
305 // before script callbacks are fired, as the script callbacks may
306 // release references and allow the backing store itself to be
307 // released, and order is critical.
308 CloseOpenCursors();
309 transaction_->Reset();
311 // Transactions must also be marked as completed before the
312 // front-end is notified, as the transaction completion unblocks
313 // operations like closing connections.
314 database_->transaction_coordinator().DidFinishTransaction(this);
316 if (committed) {
317 abort_task_stack_.clear();
319 IDB_TRACE1(
320 "IndexedDBTransaction::CommitPhaseTwo.TransactionCompleteCallbacks",
321 "txn.id", id());
322 callbacks_->OnComplete(id_);
324 database_->TransactionFinished(this, true);
325 } else {
326 while (!abort_task_stack_.empty())
327 abort_task_stack_.pop().Run(NULL);
329 IndexedDBDatabaseError error;
330 if (leveldb_env::IndicatesDiskFull(s)) {
331 error = IndexedDBDatabaseError(
332 blink::WebIDBDatabaseExceptionQuotaError,
333 "Encountered disk full while committing transaction.");
334 } else {
335 error = IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
336 "Internal error committing transaction.");
338 callbacks_->OnAbort(id_, error);
340 database_->TransactionFinished(this, false);
341 database_->TransactionCommitFailed(s);
344 database_ = NULL;
345 return s;
348 void IndexedDBTransaction::ProcessTaskQueue() {
349 IDB_TRACE1("IndexedDBTransaction::ProcessTaskQueue", "txn.id", id());
351 // May have been aborted.
352 if (!should_process_queue_)
353 return;
355 DCHECK(!IsTaskQueueEmpty());
356 should_process_queue_ = false;
358 if (!backing_store_transaction_begun_) {
359 transaction_->Begin();
360 backing_store_transaction_begun_ = true;
363 // The last reference to this object may be released while performing the
364 // tasks. Take take a self reference to keep this object alive so that
365 // the loop termination conditions can be checked.
366 scoped_refptr<IndexedDBTransaction> protect(this);
368 TaskQueue* task_queue =
369 pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
370 while (!task_queue->empty() && state_ != FINISHED) {
371 DCHECK_EQ(state_, STARTED);
372 Operation task(task_queue->pop());
373 task.Run(this);
374 if (!pending_preemptive_events_) {
375 DCHECK(diagnostics_.tasks_completed < diagnostics_.tasks_scheduled);
376 ++diagnostics_.tasks_completed;
379 // Event itself may change which queue should be processed next.
380 task_queue =
381 pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
384 // If there are no pending tasks, we haven't already committed/aborted,
385 // and the front-end requested a commit, it is now safe to do so.
386 if (!HasPendingTasks() && state_ != FINISHED && commit_pending_) {
387 Commit();
388 return;
391 // The transaction may have been aborted while processing tasks.
392 if (state_ == FINISHED)
393 return;
395 DCHECK(state_ == STARTED);
397 // Otherwise, start a timer in case the front-end gets wedged and
398 // never requests further activity. Read-only transactions don't
399 // block other transactions, so don't time those out.
400 if (mode_ != blink::WebIDBTransactionModeReadOnly) {
401 timeout_timer_.Start(FROM_HERE, GetInactivityTimeout(),
402 base::Bind(&IndexedDBTransaction::Timeout, this));
406 base::TimeDelta IndexedDBTransaction::GetInactivityTimeout() const {
407 return base::TimeDelta::FromSeconds(kInactivityTimeoutPeriodSeconds);
410 void IndexedDBTransaction::Timeout() {
411 Abort(IndexedDBDatabaseError(
412 blink::WebIDBDatabaseExceptionTimeoutError,
413 base::ASCIIToUTF16("Transaction timed out due to inactivity.")));
416 void IndexedDBTransaction::CloseOpenCursors() {
417 IDB_TRACE1("IndexedDBTransaction::CloseOpenCursors", "txn.id", id());
418 for (auto* cursor : open_cursors_)
419 cursor->Close();
420 open_cursors_.clear();
423 } // namespace content