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/WebIDBDatabaseException.h"
21 const int64 kInactivityTimeoutPeriodSeconds
= 60;
23 IndexedDBTransaction::TaskQueue::TaskQueue() {}
24 IndexedDBTransaction::TaskQueue::~TaskQueue() { clear(); }
26 void IndexedDBTransaction::TaskQueue::clear() {
27 while (!queue_
.empty())
31 IndexedDBTransaction::Operation
IndexedDBTransaction::TaskQueue::pop() {
32 DCHECK(!queue_
.empty());
33 Operation
task(queue_
.front());
38 IndexedDBTransaction::TaskStack::TaskStack() {}
39 IndexedDBTransaction::TaskStack::~TaskStack() { clear(); }
41 void IndexedDBTransaction::TaskStack::clear() {
42 while (!stack_
.empty())
46 IndexedDBTransaction::Operation
IndexedDBTransaction::TaskStack::pop() {
47 DCHECK(!stack_
.empty());
48 Operation
task(stack_
.top());
53 IndexedDBTransaction::IndexedDBTransaction(
55 scoped_refptr
<IndexedDBDatabaseCallbacks
> callbacks
,
56 const std::set
<int64
>& object_store_ids
,
57 blink::WebIDBTransactionMode mode
,
58 IndexedDBDatabase
* database
,
59 IndexedDBBackingStore::Transaction
* backing_store_transaction
)
61 object_store_ids_(object_store_ids
),
65 commit_pending_(false),
66 callbacks_(callbacks
),
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(blink::WebIDBTaskType type
,
91 DCHECK_NE(state_
, COMMITTING
);
92 if (state_
== FINISHED
)
95 timeout_timer_
.Stop();
97 if (type
== blink::WebIDBTaskTypeNormal
) {
98 task_queue_
.push(task
);
99 ++diagnostics_
.tasks_scheduled
;
101 preemptive_task_queue_
.push(task
);
106 void IndexedDBTransaction::ScheduleAbortTask(Operation abort_task
) {
107 DCHECK_NE(FINISHED
, state_
);
109 abort_task_stack_
.push(abort_task
);
112 void IndexedDBTransaction::RunTasksIfStarted() {
115 // Not started by the coordinator yet.
116 if (state_
!= STARTED
)
119 // A task is already posted.
120 if (should_process_queue_
)
123 should_process_queue_
= true;
124 base::MessageLoop::current()->PostTask(
125 FROM_HERE
, base::Bind(&IndexedDBTransaction::ProcessTaskQueue
, this));
128 void IndexedDBTransaction::Abort() {
129 Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError
,
130 "Internal error (unknown cause)"));
133 void IndexedDBTransaction::Abort(const IndexedDBDatabaseError
& error
) {
134 IDB_TRACE1("IndexedDBTransaction::Abort", "txn.id", id());
135 if (state_
== FINISHED
)
138 // The last reference to this object may be released while performing the
139 // abort steps below. We therefore take a self reference to keep ourselves
140 // alive while executing this method.
141 scoped_refptr
<IndexedDBTransaction
> protect(this);
143 timeout_timer_
.Stop();
146 should_process_queue_
= false;
148 if (backing_store_transaction_begun_
)
149 transaction_
->Rollback();
151 // Run the abort tasks, if any.
152 while (!abort_task_stack_
.empty())
153 abort_task_stack_
.pop().Run(NULL
);
155 preemptive_task_queue_
.clear();
156 pending_preemptive_events_
= 0;
159 // Backing store resources (held via cursors) must be released
160 // before script callbacks are fired, as the script callbacks may
161 // release references and allow the backing store itself to be
162 // released, and order is critical.
164 transaction_
->Reset();
166 // Transactions must also be marked as completed before the
167 // front-end is notified, as the transaction completion unblocks
168 // operations like closing connections.
169 database_
->transaction_coordinator().DidFinishTransaction(this);
171 DCHECK(!database_
->transaction_coordinator().IsActive(this));
174 if (callbacks_
.get())
175 callbacks_
->OnAbort(id_
, error
);
177 database_
->TransactionFinished(this, false);
182 bool IndexedDBTransaction::IsTaskQueueEmpty() const {
183 return preemptive_task_queue_
.empty() && task_queue_
.empty();
186 bool IndexedDBTransaction::HasPendingTasks() const {
187 return pending_preemptive_events_
|| !IsTaskQueueEmpty();
190 void IndexedDBTransaction::RegisterOpenCursor(IndexedDBCursor
* cursor
) {
191 open_cursors_
.insert(cursor
);
194 void IndexedDBTransaction::UnregisterOpenCursor(IndexedDBCursor
* cursor
) {
195 open_cursors_
.erase(cursor
);
198 void IndexedDBTransaction::Start() {
199 // TransactionCoordinator has started this transaction.
200 DCHECK_EQ(CREATED
, state_
);
202 diagnostics_
.start_time
= base::Time::Now();
210 class BlobWriteCallbackImpl
: public IndexedDBBackingStore::BlobWriteCallback
{
212 explicit BlobWriteCallbackImpl(
213 scoped_refptr
<IndexedDBTransaction
> transaction
)
214 : transaction_(transaction
) {}
215 void Run(bool succeeded
) override
{
216 transaction_
->BlobWriteComplete(succeeded
);
220 ~BlobWriteCallbackImpl() override
{}
223 scoped_refptr
<IndexedDBTransaction
> transaction_
;
226 void IndexedDBTransaction::BlobWriteComplete(bool success
) {
227 IDB_TRACE("IndexedDBTransaction::BlobWriteComplete");
228 if (state_
== FINISHED
) // aborted
230 DCHECK_EQ(state_
, COMMITTING
);
234 Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionDataError
,
235 "Failed to write blobs."));
238 leveldb::Status
IndexedDBTransaction::Commit() {
239 IDB_TRACE1("IndexedDBTransaction::Commit", "txn.id", id());
241 timeout_timer_
.Stop();
243 // In multiprocess ports, front-end may have requested a commit but
244 // an abort has already been initiated asynchronously by the
246 if (state_
== FINISHED
)
247 return leveldb::Status::OK();
248 DCHECK_NE(state_
, COMMITTING
);
250 DCHECK(!used_
|| state_
== STARTED
);
251 commit_pending_
= true;
253 // Front-end has requested a commit, but there may be tasks like
254 // create_index which are considered synchronous by the front-end
255 // but are processed asynchronously.
256 if (HasPendingTasks())
257 return leveldb::Status::OK();
263 s
= CommitPhaseTwo();
265 scoped_refptr
<IndexedDBBackingStore::BlobWriteCallback
> callback(
266 new BlobWriteCallbackImpl(this));
267 // CommitPhaseOne will call the callback synchronously if there are no blobs
269 s
= transaction_
->CommitPhaseOne(callback
);
271 Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionDataError
,
272 "Error processing blob journal."));
278 leveldb::Status
IndexedDBTransaction::CommitPhaseTwo() {
279 // Abort may have been called just as the blob write completed.
280 if (state_
== FINISHED
)
281 return leveldb::Status::OK();
283 DCHECK_EQ(state_
, COMMITTING
);
285 // The last reference to this object may be released while performing the
286 // commit steps below. We therefore take a self reference to keep ourselves
287 // alive while executing this method.
288 scoped_refptr
<IndexedDBTransaction
> protect(this);
297 s
= transaction_
->CommitPhaseTwo();
301 // Backing store resources (held via cursors) must be released
302 // before script callbacks are fired, as the script callbacks may
303 // release references and allow the backing store itself to be
304 // released, and order is critical.
306 transaction_
->Reset();
308 // Transactions must also be marked as completed before the
309 // front-end is notified, as the transaction completion unblocks
310 // operations like closing connections.
311 database_
->transaction_coordinator().DidFinishTransaction(this);
314 abort_task_stack_
.clear();
315 callbacks_
->OnComplete(id_
);
316 database_
->TransactionFinished(this, true);
318 while (!abort_task_stack_
.empty())
319 abort_task_stack_
.pop().Run(NULL
);
323 IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError
,
324 "Internal error committing transaction."));
325 database_
->TransactionFinished(this, false);
326 database_
->TransactionCommitFailed(s
);
333 void IndexedDBTransaction::ProcessTaskQueue() {
334 IDB_TRACE1("IndexedDBTransaction::ProcessTaskQueue", "txn.id", id());
336 // May have been aborted.
337 if (!should_process_queue_
)
340 DCHECK(!IsTaskQueueEmpty());
341 should_process_queue_
= false;
343 if (!backing_store_transaction_begun_
) {
344 transaction_
->Begin();
345 backing_store_transaction_begun_
= true;
348 // The last reference to this object may be released while performing the
349 // tasks. Take take a self reference to keep this object alive so that
350 // the loop termination conditions can be checked.
351 scoped_refptr
<IndexedDBTransaction
> protect(this);
353 TaskQueue
* task_queue
=
354 pending_preemptive_events_
? &preemptive_task_queue_
: &task_queue_
;
355 while (!task_queue
->empty() && state_
!= FINISHED
) {
356 DCHECK_EQ(state_
, STARTED
);
357 Operation
task(task_queue
->pop());
359 if (!pending_preemptive_events_
) {
360 DCHECK(diagnostics_
.tasks_completed
< diagnostics_
.tasks_scheduled
);
361 ++diagnostics_
.tasks_completed
;
364 // Event itself may change which queue should be processed next.
366 pending_preemptive_events_
? &preemptive_task_queue_
: &task_queue_
;
369 // If there are no pending tasks, we haven't already committed/aborted,
370 // and the front-end requested a commit, it is now safe to do so.
371 if (!HasPendingTasks() && state_
!= FINISHED
&& commit_pending_
) {
376 // The transaction may have been aborted while processing tasks.
377 if (state_
== FINISHED
)
380 DCHECK(state_
== STARTED
);
382 // Otherwise, start a timer in case the front-end gets wedged and
383 // never requests further activity. Read-only transactions don't
384 // block other transactions, so don't time those out.
385 if (mode_
!= blink::WebIDBTransactionModeReadOnly
) {
386 timeout_timer_
.Start(
388 base::TimeDelta::FromSeconds(kInactivityTimeoutPeriodSeconds
),
389 base::Bind(&IndexedDBTransaction::Timeout
, this));
393 void IndexedDBTransaction::Timeout() {
394 Abort(IndexedDBDatabaseError(
395 blink::WebIDBDatabaseExceptionTimeoutError
,
396 base::ASCIIToUTF16("Transaction timed out due to inactivity.")));
399 void IndexedDBTransaction::CloseOpenCursors() {
400 for (auto* cursor
: open_cursors_
)
402 open_cursors_
.clear();
405 } // namespace content