indexed_db_transaction.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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
19namespace content {
20
21const int64 kInactivityTimeoutPeriodSeconds = 60;
22
23IndexedDBTransaction::TaskQueue::TaskQueue() {}
24IndexedDBTransaction::TaskQueue::~TaskQueue() { clear(); }
25
26void IndexedDBTransaction::TaskQueue::clear() {
27  while (!queue_.empty())
28    queue_.pop();
29}
30
31IndexedDBTransaction::Operation IndexedDBTransaction::TaskQueue::pop() {
32  DCHECK(!queue_.empty());
33  Operation task(queue_.front());
34  queue_.pop();
35  return task;
36}
37
38IndexedDBTransaction::TaskStack::TaskStack() {}
39IndexedDBTransaction::TaskStack::~TaskStack() { clear(); }
40
41void IndexedDBTransaction::TaskStack::clear() {
42  while (!stack_.empty())
43    stack_.pop();
44}
45
46IndexedDBTransaction::Operation IndexedDBTransaction::TaskStack::pop() {
47  DCHECK(!stack_.empty());
48  Operation task(stack_.top());
49  stack_.pop();
50  return task;
51}
52
53IndexedDBTransaction::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
79IndexedDBTransaction::~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
89void 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
106void IndexedDBTransaction::ScheduleAbortTask(Operation abort_task) {
107  DCHECK_NE(FINISHED, state_);
108  DCHECK(used_);
109  abort_task_stack_.push(abort_task);
110}
111
112void 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
128void IndexedDBTransaction::Abort() {
129  Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
130                               "Internal error (unknown cause)"));
131}
132
133void 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
182bool IndexedDBTransaction::IsTaskQueueEmpty() const {
183  return preemptive_task_queue_.empty() && task_queue_.empty();
184}
185
186bool IndexedDBTransaction::HasPendingTasks() const {
187  return pending_preemptive_events_ || !IsTaskQueueEmpty();
188}
189
190void IndexedDBTransaction::RegisterOpenCursor(IndexedDBCursor* cursor) {
191  open_cursors_.insert(cursor);
192}
193
194void IndexedDBTransaction::UnregisterOpenCursor(IndexedDBCursor* cursor) {
195  open_cursors_.erase(cursor);
196}
197
198void 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
210class BlobWriteCallbackImpl : public IndexedDBBackingStore::BlobWriteCallback {
211 public:
212  explicit BlobWriteCallbackImpl(
213      scoped_refptr<IndexedDBTransaction> transaction)
214      : transaction_(transaction) {}
215  virtual void Run(bool succeeded) OVERRIDE {
216    transaction_->BlobWriteComplete(succeeded);
217  }
218
219 protected:
220  virtual ~BlobWriteCallbackImpl() {}
221
222 private:
223  scoped_refptr<IndexedDBTransaction> transaction_;
224};
225
226void 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
238void 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
272void 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
321void 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
381void IndexedDBTransaction::Timeout() {
382  Abort(IndexedDBDatabaseError(
383      blink::WebIDBDatabaseExceptionTimeoutError,
384      base::ASCIIToUTF16("Transaction timed out due to inactivity.")));
385}
386
387void 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