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/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"
24 const int64 kInactivityTimeoutPeriodSeconds
= 60;
26 IndexedDBTransaction::TaskQueue::TaskQueue() {}
27 IndexedDBTransaction::TaskQueue::~TaskQueue() { clear(); }
29 void IndexedDBTransaction::TaskQueue::clear() {
30 while (!queue_
.empty())
34 IndexedDBTransaction::Operation
IndexedDBTransaction::TaskQueue::pop() {
35 DCHECK(!queue_
.empty());
36 Operation
task(queue_
.front());
41 IndexedDBTransaction::TaskStack::TaskStack() {}
42 IndexedDBTransaction::TaskStack::~TaskStack() { clear(); }
44 void IndexedDBTransaction::TaskStack::clear() {
45 while (!stack_
.empty())
49 IndexedDBTransaction::Operation
IndexedDBTransaction::TaskStack::pop() {
50 DCHECK(!stack_
.empty());
51 Operation
task(stack_
.top());
56 IndexedDBTransaction::IndexedDBTransaction(
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
)
64 object_store_ids_(object_store_ids
),
68 commit_pending_(false),
69 callbacks_(callbacks
),
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
,
94 DCHECK_NE(state_
, COMMITTING
);
95 if (state_
== FINISHED
)
98 timeout_timer_
.Stop();
100 if (type
== blink::WebIDBTaskTypeNormal
) {
101 task_queue_
.push(task
);
102 ++diagnostics_
.tasks_scheduled
;
104 preemptive_task_queue_
.push(task
);
109 void IndexedDBTransaction::ScheduleAbortTask(Operation abort_task
) {
110 DCHECK_NE(FINISHED
, state_
);
112 abort_task_stack_
.push(abort_task
);
115 void IndexedDBTransaction::RunTasksIfStarted() {
118 // Not started by the coordinator yet.
119 if (state_
!= STARTED
)
122 // A task is already posted.
123 if (should_process_queue_
)
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
)
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();
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;
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.
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);
174 DCHECK(!database_
->transaction_coordinator().IsActive(this));
177 if (callbacks_
.get())
178 callbacks_
->OnAbort(id_
, error
);
180 database_
->TransactionFinished(this, false);
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_
);
205 diagnostics_
.start_time
= base::Time::Now();
213 class BlobWriteCallbackImpl
: public IndexedDBBackingStore::BlobWriteCallback
{
215 explicit BlobWriteCallbackImpl(
216 scoped_refptr
<IndexedDBTransaction
> transaction
)
217 : transaction_(transaction
) {}
218 void Run(bool succeeded
) override
{
219 transaction_
->BlobWriteComplete(succeeded
);
223 ~BlobWriteCallbackImpl() override
{}
226 scoped_refptr
<IndexedDBTransaction
> transaction_
;
229 void IndexedDBTransaction::BlobWriteComplete(bool success
) {
230 IDB_TRACE("IndexedDBTransaction::BlobWriteComplete");
231 if (state_
== FINISHED
) // aborted
233 DCHECK_EQ(state_
, COMMITTING
);
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
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();
266 s
= CommitPhaseTwo();
268 scoped_refptr
<IndexedDBBackingStore::BlobWriteCallback
> callback(
269 new BlobWriteCallbackImpl(this));
270 // CommitPhaseOne will call the callback synchronously if there are no blobs
272 s
= transaction_
->CommitPhaseOne(callback
);
274 Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionDataError
,
275 "Error processing blob journal."));
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);
300 s
= transaction_
->CommitPhaseTwo();
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.
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);
317 abort_task_stack_
.clear();
318 callbacks_
->OnComplete(id_
);
319 database_
->TransactionFinished(this, true);
321 while (!abort_task_stack_
.empty())
322 abort_task_stack_
.pop().Run(NULL
);
324 IndexedDBDatabaseError error
;
325 if (leveldb_env::IndicatesDiskFull(s
)) {
326 error
= IndexedDBDatabaseError(
327 blink::WebIDBDatabaseExceptionQuotaError
,
328 "Encountered disk full while committing transaction.");
330 error
= IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError
,
331 "Internal error committing transaction.");
333 callbacks_
->OnAbort(id_
, error
);
335 database_
->TransactionFinished(this, false);
336 database_
->TransactionCommitFailed(s
);
343 void IndexedDBTransaction::ProcessTaskQueue() {
344 IDB_TRACE1("IndexedDBTransaction::ProcessTaskQueue", "txn.id", id());
346 // May have been aborted.
347 if (!should_process_queue_
)
350 DCHECK(!IsTaskQueueEmpty());
351 should_process_queue_
= false;
353 if (!backing_store_transaction_begun_
) {
354 transaction_
->Begin();
355 backing_store_transaction_begun_
= true;
358 // The last reference to this object may be released while performing the
359 // tasks. Take take a self reference to keep this object alive so that
360 // the loop termination conditions can be checked.
361 scoped_refptr
<IndexedDBTransaction
> protect(this);
363 TaskQueue
* task_queue
=
364 pending_preemptive_events_
? &preemptive_task_queue_
: &task_queue_
;
365 while (!task_queue
->empty() && state_
!= FINISHED
) {
366 DCHECK_EQ(state_
, STARTED
);
367 Operation
task(task_queue
->pop());
369 if (!pending_preemptive_events_
) {
370 DCHECK(diagnostics_
.tasks_completed
< diagnostics_
.tasks_scheduled
);
371 ++diagnostics_
.tasks_completed
;
374 // Event itself may change which queue should be processed next.
376 pending_preemptive_events_
? &preemptive_task_queue_
: &task_queue_
;
379 // If there are no pending tasks, we haven't already committed/aborted,
380 // and the front-end requested a commit, it is now safe to do so.
381 if (!HasPendingTasks() && state_
!= FINISHED
&& commit_pending_
) {
386 // The transaction may have been aborted while processing tasks.
387 if (state_
== FINISHED
)
390 DCHECK(state_
== STARTED
);
392 // Otherwise, start a timer in case the front-end gets wedged and
393 // never requests further activity. Read-only transactions don't
394 // block other transactions, so don't time those out.
395 if (mode_
!= blink::WebIDBTransactionModeReadOnly
) {
396 timeout_timer_
.Start(FROM_HERE
, GetInactivityTimeout(),
397 base::Bind(&IndexedDBTransaction::Timeout
, this));
401 base::TimeDelta
IndexedDBTransaction::GetInactivityTimeout() const {
402 return base::TimeDelta::FromSeconds(kInactivityTimeoutPeriodSeconds
);
405 void IndexedDBTransaction::Timeout() {
406 Abort(IndexedDBDatabaseError(
407 blink::WebIDBDatabaseExceptionTimeoutError
,
408 base::ASCIIToUTF16("Transaction timed out due to inactivity.")));
411 void IndexedDBTransaction::CloseOpenCursors() {
412 for (auto* cursor
: open_cursors_
)
414 open_cursors_
.clear();
417 } // namespace content