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    blink::WebIDBTransactionMode 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(blink::WebIDBTaskType 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 == blink::WebIDBTaskTypeNormal) {
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
238leveldb::Status IndexedDBTransaction::Commit() {
239  IDB_TRACE1("IndexedDBTransaction::Commit", "txn.id", id());
240
241  timeout_timer_.Stop();
242
243  // In multiprocess ports, front-end may have requested a commit but
244  // an abort has already been initiated asynchronously by the
245  // back-end.
246  if (state_ == FINISHED)
247    return leveldb::Status::OK();
248  DCHECK_NE(state_, COMMITTING);
249
250  DCHECK(!used_ || state_ == STARTED);
251  commit_pending_ = true;
252
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();
258
259  state_ = COMMITTING;
260
261  leveldb::Status s;
262  if (!used_) {
263    s = CommitPhaseTwo();
264  } else {
265    scoped_refptr<IndexedDBBackingStore::BlobWriteCallback> callback(
266        new BlobWriteCallbackImpl(this));
267    // CommitPhaseOne will call the callback synchronously if there are no blobs
268    // to write.
269    s = transaction_->CommitPhaseOne(callback);
270    if (!s.ok())
271      Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionDataError,
272                                   "Error processing blob journal."));
273  }
274
275  return s;
276}
277
278leveldb::Status IndexedDBTransaction::CommitPhaseTwo() {
279  // Abort may have been called just as the blob write completed.
280  if (state_ == FINISHED)
281    return leveldb::Status::OK();
282
283  DCHECK_EQ(state_, COMMITTING);
284
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);
289
290  state_ = FINISHED;
291
292  leveldb::Status s;
293  bool committed;
294  if (!used_) {
295    committed = true;
296  } else {
297    s = transaction_->CommitPhaseTwo();
298    committed = s.ok();
299  }
300
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.
305  CloseOpenCursors();
306  transaction_->Reset();
307
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);
312
313  if (committed) {
314    abort_task_stack_.clear();
315    callbacks_->OnComplete(id_);
316    database_->TransactionFinished(this, true);
317  } else {
318    while (!abort_task_stack_.empty())
319      abort_task_stack_.pop().Run(NULL);
320
321    callbacks_->OnAbort(
322        id_,
323        IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
324                               "Internal error committing transaction."));
325    database_->TransactionFinished(this, false);
326    database_->TransactionCommitFailed(s);
327  }
328
329  database_ = NULL;
330  return s;
331}
332
333void IndexedDBTransaction::ProcessTaskQueue() {
334  IDB_TRACE1("IndexedDBTransaction::ProcessTaskQueue", "txn.id", id());
335
336  // May have been aborted.
337  if (!should_process_queue_)
338    return;
339
340  DCHECK(!IsTaskQueueEmpty());
341  should_process_queue_ = false;
342
343  if (!backing_store_transaction_begun_) {
344    transaction_->Begin();
345    backing_store_transaction_begun_ = true;
346  }
347
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);
352
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());
358    task.Run(this);
359    if (!pending_preemptive_events_) {
360      DCHECK(diagnostics_.tasks_completed < diagnostics_.tasks_scheduled);
361      ++diagnostics_.tasks_completed;
362    }
363
364    // Event itself may change which queue should be processed next.
365    task_queue =
366        pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
367  }
368
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_) {
372    Commit();
373    return;
374  }
375
376  // The transaction may have been aborted while processing tasks.
377  if (state_ == FINISHED)
378    return;
379
380  DCHECK(state_ == STARTED);
381
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(
387        FROM_HERE,
388        base::TimeDelta::FromSeconds(kInactivityTimeoutPeriodSeconds),
389        base::Bind(&IndexedDBTransaction::Timeout, this));
390  }
391}
392
393void IndexedDBTransaction::Timeout() {
394  Abort(IndexedDBDatabaseError(
395      blink::WebIDBDatabaseExceptionTimeoutError,
396      base::ASCIIToUTF16("Transaction timed out due to inactivity.")));
397}
398
399void IndexedDBTransaction::CloseOpenCursors() {
400  for (std::set<IndexedDBCursor*>::iterator i = open_cursors_.begin();
401       i != open_cursors_.end();
402       ++i)
403    (*i)->Close();
404  open_cursors_.clear();
405}
406
407}  // namespace content
408