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.
4
5 #include "content/browser/indexed_db/indexed_db_transaction.h"
6
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
19 namespace content {
20
21 const int64 kInactivityTimeoutPeriodSeconds = 60;
22
TaskQueue()23 IndexedDBTransaction::TaskQueue::TaskQueue() {}
~TaskQueue()24 IndexedDBTransaction::TaskQueue::~TaskQueue() { clear(); }
25
clear()26 void IndexedDBTransaction::TaskQueue::clear() {
27 while (!queue_.empty())
28 queue_.pop();
29 }
30
pop()31 IndexedDBTransaction::Operation IndexedDBTransaction::TaskQueue::pop() {
32 DCHECK(!queue_.empty());
33 Operation task(queue_.front());
34 queue_.pop();
35 return task;
36 }
37
TaskStack()38 IndexedDBTransaction::TaskStack::TaskStack() {}
~TaskStack()39 IndexedDBTransaction::TaskStack::~TaskStack() { clear(); }
40
clear()41 void IndexedDBTransaction::TaskStack::clear() {
42 while (!stack_.empty())
43 stack_.pop();
44 }
45
pop()46 IndexedDBTransaction::Operation IndexedDBTransaction::TaskStack::pop() {
47 DCHECK(!stack_.empty());
48 Operation task(stack_.top());
49 stack_.pop();
50 return task;
51 }
52
IndexedDBTransaction(int64 id,scoped_refptr<IndexedDBDatabaseCallbacks> callbacks,const std::set<int64> & object_store_ids,indexed_db::TransactionMode mode,IndexedDBDatabase * database,IndexedDBBackingStore::Transaction * backing_store_transaction)53 IndexedDBTransaction::IndexedDBTransaction(
54 int64 id,
55 scoped_refptr<IndexedDBDatabaseCallbacks> callbacks,
56 const std::set<int64>& object_store_ids,
57 indexed_db::TransactionMode mode,
58 IndexedDBDatabase* database,
59 IndexedDBBackingStore::Transaction* backing_store_transaction)
60 : id_(id),
61 object_store_ids_(object_store_ids),
62 mode_(mode),
63 used_(false),
64 state_(CREATED),
65 commit_pending_(false),
66 callbacks_(callbacks),
67 database_(database),
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);
73
74 diagnostics_.tasks_scheduled = 0;
75 diagnostics_.tasks_completed = 0;
76 diagnostics_.creation_time = base::Time::Now();
77 }
78
~IndexedDBTransaction()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());
87 }
88
ScheduleTask(IndexedDBDatabase::TaskType type,Operation task)89 void IndexedDBTransaction::ScheduleTask(IndexedDBDatabase::TaskType type,
90 Operation task) {
91 DCHECK_NE(state_, COMMITTING);
92 if (state_ == FINISHED)
93 return;
94
95 timeout_timer_.Stop();
96 used_ = true;
97 if (type == IndexedDBDatabase::NORMAL_TASK) {
98 task_queue_.push(task);
99 ++diagnostics_.tasks_scheduled;
100 } else {
101 preemptive_task_queue_.push(task);
102 }
103 RunTasksIfStarted();
104 }
105
ScheduleAbortTask(Operation abort_task)106 void IndexedDBTransaction::ScheduleAbortTask(Operation abort_task) {
107 DCHECK_NE(FINISHED, state_);
108 DCHECK(used_);
109 abort_task_stack_.push(abort_task);
110 }
111
RunTasksIfStarted()112 void IndexedDBTransaction::RunTasksIfStarted() {
113 DCHECK(used_);
114
115 // Not started by the coordinator yet.
116 if (state_ != STARTED)
117 return;
118
119 // A task is already posted.
120 if (should_process_queue_)
121 return;
122
123 should_process_queue_ = true;
124 base::MessageLoop::current()->PostTask(
125 FROM_HERE, base::Bind(&IndexedDBTransaction::ProcessTaskQueue, this));
126 }
127
Abort()128 void IndexedDBTransaction::Abort() {
129 Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
130 "Internal error (unknown cause)"));
131 }
132
Abort(const IndexedDBDatabaseError & error)133 void IndexedDBTransaction::Abort(const IndexedDBDatabaseError& error) {
134 IDB_TRACE1("IndexedDBTransaction::Abort", "txn.id", id());
135 if (state_ == FINISHED)
136 return;
137
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);
142
143 timeout_timer_.Stop();
144
145 state_ = FINISHED;
146 should_process_queue_ = false;
147
148 if (backing_store_transaction_begun_)
149 transaction_->Rollback();
150
151 // Run the abort tasks, if any.
152 while (!abort_task_stack_.empty())
153 abort_task_stack_.pop().Run(NULL);
154
155 preemptive_task_queue_.clear();
156 pending_preemptive_events_ = 0;
157 task_queue_.clear();
158
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.
163 CloseOpenCursors();
164 transaction_->Reset();
165
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);
170 #ifndef NDEBUG
171 DCHECK(!database_->transaction_coordinator().IsActive(this));
172 #endif
173
174 if (callbacks_.get())
175 callbacks_->OnAbort(id_, error);
176
177 database_->TransactionFinished(this, false);
178
179 database_ = NULL;
180 }
181
IsTaskQueueEmpty() const182 bool IndexedDBTransaction::IsTaskQueueEmpty() const {
183 return preemptive_task_queue_.empty() && task_queue_.empty();
184 }
185
HasPendingTasks() const186 bool IndexedDBTransaction::HasPendingTasks() const {
187 return pending_preemptive_events_ || !IsTaskQueueEmpty();
188 }
189
RegisterOpenCursor(IndexedDBCursor * cursor)190 void IndexedDBTransaction::RegisterOpenCursor(IndexedDBCursor* cursor) {
191 open_cursors_.insert(cursor);
192 }
193
UnregisterOpenCursor(IndexedDBCursor * cursor)194 void IndexedDBTransaction::UnregisterOpenCursor(IndexedDBCursor* cursor) {
195 open_cursors_.erase(cursor);
196 }
197
Start()198 void IndexedDBTransaction::Start() {
199 // TransactionCoordinator has started this transaction.
200 DCHECK_EQ(CREATED, state_);
201 state_ = STARTED;
202 diagnostics_.start_time = base::Time::Now();
203
204 if (!used_)
205 return;
206
207 RunTasksIfStarted();
208 }
209
210 class BlobWriteCallbackImpl : public IndexedDBBackingStore::BlobWriteCallback {
211 public:
BlobWriteCallbackImpl(scoped_refptr<IndexedDBTransaction> transaction)212 explicit BlobWriteCallbackImpl(
213 scoped_refptr<IndexedDBTransaction> transaction)
214 : transaction_(transaction) {}
Run(bool succeeded)215 virtual void Run(bool succeeded) OVERRIDE {
216 transaction_->BlobWriteComplete(succeeded);
217 }
218
219 protected:
~BlobWriteCallbackImpl()220 virtual ~BlobWriteCallbackImpl() {}
221
222 private:
223 scoped_refptr<IndexedDBTransaction> transaction_;
224 };
225
BlobWriteComplete(bool success)226 void IndexedDBTransaction::BlobWriteComplete(bool success) {
227 IDB_TRACE("IndexedDBTransaction::BlobWriteComplete");
228 if (state_ == FINISHED) // aborted
229 return;
230 DCHECK_EQ(state_, COMMITTING);
231 if (success)
232 CommitPhaseTwo();
233 else
234 Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionDataError,
235 "Failed to write blobs."));
236 }
237
Commit()238 void IndexedDBTransaction::Commit() {
239 IDB_TRACE1("IndexedDBTransaction::Commit", "txn.id", id());
240
241 // In multiprocess ports, front-end may have requested a commit but
242 // an abort has already been initiated asynchronously by the
243 // back-end.
244 if (state_ == FINISHED)
245 return;
246 DCHECK_NE(state_, COMMITTING);
247
248 DCHECK(!used_ || state_ == STARTED);
249 commit_pending_ = true;
250
251 // Front-end has requested a commit, but there may be tasks like
252 // create_index which are considered synchronous by the front-end
253 // but are processed asynchronously.
254 if (HasPendingTasks())
255 return;
256
257 state_ = COMMITTING;
258
259 if (!used_) {
260 CommitPhaseTwo();
261 } else {
262 scoped_refptr<IndexedDBBackingStore::BlobWriteCallback> callback(
263 new BlobWriteCallbackImpl(this));
264 // CommitPhaseOne will call the callback synchronously if there are no blobs
265 // to write.
266 if (!transaction_->CommitPhaseOne(callback).ok())
267 Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionDataError,
268 "Error processing blob journal."));
269 }
270 }
271
CommitPhaseTwo()272 void IndexedDBTransaction::CommitPhaseTwo() {
273 // Abort may have been called just as the blob write completed.
274 if (state_ == FINISHED)
275 return;
276
277 DCHECK_EQ(state_, COMMITTING);
278
279 // The last reference to this object may be released while performing the
280 // commit steps below. We therefore take a self reference to keep ourselves
281 // alive while executing this method.
282 scoped_refptr<IndexedDBTransaction> protect(this);
283
284 timeout_timer_.Stop();
285
286 state_ = FINISHED;
287
288 bool committed = !used_ || transaction_->CommitPhaseTwo().ok();
289
290 // Backing store resources (held via cursors) must be released
291 // before script callbacks are fired, as the script callbacks may
292 // release references and allow the backing store itself to be
293 // released, and order is critical.
294 CloseOpenCursors();
295 transaction_->Reset();
296
297 // Transactions must also be marked as completed before the
298 // front-end is notified, as the transaction completion unblocks
299 // operations like closing connections.
300 database_->transaction_coordinator().DidFinishTransaction(this);
301
302 if (committed) {
303 abort_task_stack_.clear();
304 callbacks_->OnComplete(id_);
305 database_->TransactionFinished(this, true);
306 } else {
307 while (!abort_task_stack_.empty())
308 abort_task_stack_.pop().Run(NULL);
309
310 callbacks_->OnAbort(
311 id_,
312 IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
313 "Internal error committing transaction."));
314 database_->TransactionFinished(this, false);
315 database_->TransactionCommitFailed();
316 }
317
318 database_ = NULL;
319 }
320
ProcessTaskQueue()321 void IndexedDBTransaction::ProcessTaskQueue() {
322 IDB_TRACE1("IndexedDBTransaction::ProcessTaskQueue", "txn.id", id());
323
324 // May have been aborted.
325 if (!should_process_queue_)
326 return;
327
328 DCHECK(!IsTaskQueueEmpty());
329 should_process_queue_ = false;
330
331 if (!backing_store_transaction_begun_) {
332 transaction_->Begin();
333 backing_store_transaction_begun_ = true;
334 }
335
336 // The last reference to this object may be released while performing the
337 // tasks. Take take a self reference to keep this object alive so that
338 // the loop termination conditions can be checked.
339 scoped_refptr<IndexedDBTransaction> protect(this);
340
341 TaskQueue* task_queue =
342 pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
343 while (!task_queue->empty() && state_ != FINISHED) {
344 DCHECK_EQ(state_, STARTED);
345 Operation task(task_queue->pop());
346 task.Run(this);
347 if (!pending_preemptive_events_) {
348 DCHECK(diagnostics_.tasks_completed < diagnostics_.tasks_scheduled);
349 ++diagnostics_.tasks_completed;
350 }
351
352 // Event itself may change which queue should be processed next.
353 task_queue =
354 pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
355 }
356
357 // If there are no pending tasks, we haven't already committed/aborted,
358 // and the front-end requested a commit, it is now safe to do so.
359 if (!HasPendingTasks() && state_ != FINISHED && commit_pending_) {
360 Commit();
361 return;
362 }
363
364 // The transaction may have been aborted while processing tasks.
365 if (state_ == FINISHED)
366 return;
367
368 DCHECK(state_ == STARTED);
369
370 // Otherwise, start a timer in case the front-end gets wedged and
371 // never requests further activity. Read-only transactions don't
372 // block other transactions, so don't time those out.
373 if (mode_ != indexed_db::TRANSACTION_READ_ONLY) {
374 timeout_timer_.Start(
375 FROM_HERE,
376 base::TimeDelta::FromSeconds(kInactivityTimeoutPeriodSeconds),
377 base::Bind(&IndexedDBTransaction::Timeout, this));
378 }
379 }
380
Timeout()381 void IndexedDBTransaction::Timeout() {
382 Abort(IndexedDBDatabaseError(
383 blink::WebIDBDatabaseExceptionTimeoutError,
384 base::ASCIIToUTF16("Transaction timed out due to inactivity.")));
385 }
386
CloseOpenCursors()387 void IndexedDBTransaction::CloseOpenCursors() {
388 for (std::set<IndexedDBCursor*>::iterator i = open_cursors_.begin();
389 i != open_cursors_.end();
390 ++i)
391 (*i)->Close();
392 open_cursors_.clear();
393 }
394
395 } // namespace content
396