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
21IndexedDBTransaction::TaskQueue::TaskQueue() {}
22IndexedDBTransaction::TaskQueue::~TaskQueue() { clear(); }
23
24void IndexedDBTransaction::TaskQueue::clear() {
25  while (!queue_.empty())
26    scoped_ptr<Operation> task(pop());
27}
28
29scoped_ptr<IndexedDBTransaction::Operation>
30IndexedDBTransaction::TaskQueue::pop() {
31  DCHECK(!queue_.empty());
32  scoped_ptr<Operation> task(queue_.front());
33  queue_.pop();
34  return task.Pass();
35}
36
37IndexedDBTransaction::TaskStack::TaskStack() {}
38IndexedDBTransaction::TaskStack::~TaskStack() { clear(); }
39
40void IndexedDBTransaction::TaskStack::clear() {
41  while (!stack_.empty())
42    scoped_ptr<Operation> task(pop());
43}
44
45scoped_ptr<IndexedDBTransaction::Operation>
46IndexedDBTransaction::TaskStack::pop() {
47  DCHECK(!stack_.empty());
48  scoped_ptr<Operation> task(stack_.top());
49  stack_.pop();
50  return task.Pass();
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    : id_(id),
60      object_store_ids_(object_store_ids),
61      mode_(mode),
62      state_(UNUSED),
63      commit_pending_(false),
64      callbacks_(callbacks),
65      database_(database),
66      transaction_(database->BackingStore().get()),
67      should_process_queue_(false),
68      pending_preemptive_events_(0) {
69  database_->transaction_coordinator().DidCreateTransaction(this);
70}
71
72IndexedDBTransaction::~IndexedDBTransaction() {
73  // It shouldn't be possible for this object to get deleted until it's either
74  // complete or aborted.
75  DCHECK_EQ(state_, FINISHED);
76  DCHECK(preemptive_task_queue_.empty());
77  DCHECK(task_queue_.empty());
78  DCHECK(abort_task_stack_.empty());
79}
80
81void IndexedDBTransaction::ScheduleTask(IndexedDBDatabase::TaskType type,
82                                        Operation* task,
83                                        Operation* abort_task) {
84  if (state_ == FINISHED)
85    return;
86
87  if (type == IndexedDBDatabase::NORMAL_TASK)
88    task_queue_.push(task);
89  else
90    preemptive_task_queue_.push(task);
91
92  if (abort_task)
93    abort_task_stack_.push(abort_task);
94
95  if (state_ == UNUSED) {
96    Start();
97  } else if (state_ == RUNNING && !should_process_queue_) {
98    should_process_queue_ = true;
99    base::MessageLoop::current()->PostTask(
100        FROM_HERE, base::Bind(&IndexedDBTransaction::ProcessTaskQueue, this));
101  }
102}
103
104void IndexedDBTransaction::Abort() {
105  Abort(IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
106                               "Internal error (unknown cause)"));
107}
108
109void IndexedDBTransaction::Abort(const IndexedDBDatabaseError& error) {
110  IDB_TRACE("IndexedDBTransaction::Abort");
111  if (state_ == FINISHED)
112    return;
113
114  bool was_running = state_ == RUNNING;
115
116  // The last reference to this object may be released while performing the
117  // abort steps below. We therefore take a self reference to keep ourselves
118  // alive while executing this method.
119  scoped_refptr<IndexedDBTransaction> protect(this);
120
121  state_ = FINISHED;
122  should_process_queue_ = false;
123
124  if (was_running)
125    transaction_.Rollback();
126
127  // Run the abort tasks, if any.
128  while (!abort_task_stack_.empty()) {
129    scoped_ptr<Operation> task(abort_task_stack_.pop());
130    task->Perform(0);
131  }
132  preemptive_task_queue_.clear();
133  task_queue_.clear();
134
135  // Backing store resources (held via cursors) must be released
136  // before script callbacks are fired, as the script callbacks may
137  // release references and allow the backing store itself to be
138  // released, and order is critical.
139  CloseOpenCursors();
140  transaction_.Reset();
141
142  // Transactions must also be marked as completed before the
143  // front-end is notified, as the transaction completion unblocks
144  // operations like closing connections.
145  database_->transaction_coordinator().DidFinishTransaction(this);
146#ifndef NDEBUG
147  DCHECK(!database_->transaction_coordinator().IsActive(this));
148#endif
149  database_->TransactionFinished(this);
150
151  if (callbacks_.get())
152    callbacks_->OnAbort(id_, error);
153
154  database_->TransactionFinishedAndAbortFired(this);
155
156  database_ = NULL;
157}
158
159bool IndexedDBTransaction::IsTaskQueueEmpty() const {
160  return preemptive_task_queue_.empty() && task_queue_.empty();
161}
162
163bool IndexedDBTransaction::HasPendingTasks() const {
164  return pending_preemptive_events_ || !IsTaskQueueEmpty();
165}
166
167void IndexedDBTransaction::RegisterOpenCursor(IndexedDBCursor* cursor) {
168  open_cursors_.insert(cursor);
169}
170
171void IndexedDBTransaction::UnregisterOpenCursor(IndexedDBCursor* cursor) {
172  open_cursors_.erase(cursor);
173}
174
175void IndexedDBTransaction::Run() {
176  // TransactionCoordinator has started this transaction.
177  DCHECK(state_ == START_PENDING || state_ == RUNNING);
178  DCHECK(!should_process_queue_);
179
180  should_process_queue_ = true;
181  base::MessageLoop::current()->PostTask(
182      FROM_HERE, base::Bind(&IndexedDBTransaction::ProcessTaskQueue, this));
183}
184
185void IndexedDBTransaction::Start() {
186  DCHECK_EQ(state_, UNUSED);
187
188  state_ = START_PENDING;
189  database_->transaction_coordinator().DidStartTransaction(this);
190  database_->TransactionStarted(this);
191}
192
193void IndexedDBTransaction::Commit() {
194  IDB_TRACE("IndexedDBTransaction::Commit");
195
196  // In multiprocess ports, front-end may have requested a commit but
197  // an abort has already been initiated asynchronously by the
198  // back-end.
199  if (state_ == FINISHED)
200    return;
201
202  DCHECK(state_ == UNUSED || state_ == RUNNING);
203  commit_pending_ = true;
204
205  // Front-end has requested a commit, but there may be tasks like
206  // create_index which are considered synchronous by the front-end
207  // but are processed asynchronously.
208  if (HasPendingTasks())
209    return;
210
211  // The last reference to this object may be released while performing the
212  // commit steps below. We therefore take a self reference to keep ourselves
213  // alive while executing this method.
214  scoped_refptr<IndexedDBTransaction> protect(this);
215
216  // TODO(jsbell): Run abort tasks if commit fails? http://crbug.com/241843
217  abort_task_stack_.clear();
218
219  bool unused = state_ == UNUSED;
220  state_ = FINISHED;
221
222  bool committed = unused || transaction_.Commit();
223
224  // Backing store resources (held via cursors) must be released
225  // before script callbacks are fired, as the script callbacks may
226  // release references and allow the backing store itself to be
227  // released, and order is critical.
228  CloseOpenCursors();
229  transaction_.Reset();
230
231  // Transactions must also be marked as completed before the
232  // front-end is notified, as the transaction completion unblocks
233  // operations like closing connections.
234  database_->transaction_coordinator().DidFinishTransaction(this);
235  database_->TransactionFinished(this);
236
237  if (committed) {
238    callbacks_->OnComplete(id_);
239    database_->TransactionFinishedAndCompleteFired(this);
240  } else {
241    callbacks_->OnAbort(
242        id_,
243        IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
244                               "Internal error committing transaction."));
245    database_->TransactionFinishedAndAbortFired(this);
246  }
247
248  database_ = NULL;
249}
250
251void IndexedDBTransaction::ProcessTaskQueue() {
252  IDB_TRACE("IndexedDBTransaction::ProcessTaskQueue");
253
254  // May have been aborted.
255  if (!should_process_queue_)
256    return;
257
258  DCHECK(!IsTaskQueueEmpty());
259  should_process_queue_ = false;
260
261  if (state_ == START_PENDING) {
262    transaction_.Begin();
263    state_ = RUNNING;
264  }
265
266  // The last reference to this object may be released while performing the
267  // tasks. Take take a self reference to keep this object alive so that
268  // the loop termination conditions can be checked.
269  scoped_refptr<IndexedDBTransaction> protect(this);
270
271  TaskQueue* task_queue =
272      pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
273  while (!task_queue->empty() && state_ != FINISHED) {
274    DCHECK_EQ(state_, RUNNING);
275    scoped_ptr<Operation> task(task_queue->pop());
276    task->Perform(this);
277
278    // Event itself may change which queue should be processed next.
279    task_queue =
280        pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
281  }
282
283  // If there are no pending tasks, we haven't already committed/aborted,
284  // and the front-end requested a commit, it is now safe to do so.
285  if (!HasPendingTasks() && state_ != FINISHED && commit_pending_)
286    Commit();
287}
288
289void IndexedDBTransaction::CloseOpenCursors() {
290  for (std::set<IndexedDBCursor*>::iterator i = open_cursors_.begin();
291       i != open_cursors_.end();
292       ++i)
293    (*i)->Close();
294  open_cursors_.clear();
295}
296
297}  // namespace content
298