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"
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/modules/indexeddb/WebIDBDatabaseException.h"
18 #include "third_party/leveldatabase/env_chromium.h"
22 const int64 kInactivityTimeoutPeriodSeconds
= 60;
24 IndexedDBTransaction::TaskQueue::TaskQueue() {}
25 IndexedDBTransaction::TaskQueue::~TaskQueue() { clear(); }
27 void IndexedDBTransaction::TaskQueue::clear() {
28 while (!queue_
.empty())
32 IndexedDBTransaction::Operation
IndexedDBTransaction::TaskQueue::pop() {
33 DCHECK(!queue_
.empty());
34 Operation
task(queue_
.front());
39 IndexedDBTransaction::TaskStack::TaskStack() {}
40 IndexedDBTransaction::TaskStack::~TaskStack() { clear(); }
42 void IndexedDBTransaction::TaskStack::clear() {
43 while (!stack_
.empty())
47 IndexedDBTransaction::Operation
IndexedDBTransaction::TaskStack::pop() {
48 DCHECK(!stack_
.empty());
49 Operation
task(stack_
.top());
54 IndexedDBTransaction::IndexedDBTransaction(
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
)
62 object_store_ids_(object_store_ids
),
66 commit_pending_(false),
67 callbacks_(callbacks
),
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
,
92 DCHECK_NE(state_
, COMMITTING
);
93 if (state_
== FINISHED
)
96 timeout_timer_
.Stop();
98 if (type
== blink::WebIDBTaskTypeNormal
) {
99 task_queue_
.push(task
);
100 ++diagnostics_
.tasks_scheduled
;
102 preemptive_task_queue_
.push(task
);
107 void IndexedDBTransaction::ScheduleAbortTask(Operation abort_task
) {
108 DCHECK_NE(FINISHED
, state_
);
110 abort_task_stack_
.push(abort_task
);
113 void IndexedDBTransaction::RunTasksIfStarted() {
116 // Not started by the coordinator yet.
117 if (state_
!= STARTED
)
120 // A task is already posted.
121 if (should_process_queue_
)
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
)
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();
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;
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.
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);
172 DCHECK(!database_
->transaction_coordinator().IsActive(this));
175 if (callbacks_
.get())
176 callbacks_
->OnAbort(id_
, error
);
178 database_
->TransactionFinished(this, false);
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_
);
203 diagnostics_
.start_time
= base::Time::Now();
211 class BlobWriteCallbackImpl
: public IndexedDBBackingStore::BlobWriteCallback
{
213 explicit BlobWriteCallbackImpl(
214 scoped_refptr
<IndexedDBTransaction
> transaction
)
215 : transaction_(transaction
) {}
216 void Run(bool succeeded
) override
{
217 transaction_
->BlobWriteComplete(succeeded
);
221 ~BlobWriteCallbackImpl() override
{}
224 scoped_refptr
<IndexedDBTransaction
> transaction_
;
227 void IndexedDBTransaction::BlobWriteComplete(bool success
) {
228 IDB_TRACE("IndexedDBTransaction::BlobWriteComplete");
229 if (state_
== FINISHED
) // aborted
231 DCHECK_EQ(state_
, COMMITTING
);
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
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();
264 s
= CommitPhaseTwo();
266 scoped_refptr
<IndexedDBBackingStore::BlobWriteCallback
> callback(
267 new BlobWriteCallbackImpl(this));
268 // CommitPhaseOne will call the callback synchronously if there are no blobs
270 s
= transaction_
->CommitPhaseOne(callback
);
272 Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionDataError
,
273 "Error processing blob journal."));
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);
298 s
= transaction_
->CommitPhaseTwo();
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.
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);
315 abort_task_stack_
.clear();
316 callbacks_
->OnComplete(id_
);
317 database_
->TransactionFinished(this, true);
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.");
328 error
= IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError
,
329 "Internal error committing transaction.");
331 callbacks_
->OnAbort(id_
, error
);
333 database_
->TransactionFinished(this, false);
334 database_
->TransactionCommitFailed(s
);
341 void IndexedDBTransaction::ProcessTaskQueue() {
342 IDB_TRACE1("IndexedDBTransaction::ProcessTaskQueue", "txn.id", id());
344 // May have been aborted.
345 if (!should_process_queue_
)
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());
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.
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_
) {
384 // The transaction may have been aborted while processing tasks.
385 if (state_
== FINISHED
)
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(
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_
)
410 open_cursors_
.clear();
413 } // namespace content