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(task_queue_.empty());
85  DCHECK(abort_task_stack_.empty());
86}
87
88void IndexedDBTransaction::ScheduleTask(Operation task, Operation abort_task) {
89  if (state_ == FINISHED)
90    return;
91
92  timeout_timer_.Stop();
93  used_ = true;
94  task_queue_.push(task);
95  ++diagnostics_.tasks_scheduled;
96  abort_task_stack_.push(abort_task);
97  RunTasksIfStarted();
98}
99
100void IndexedDBTransaction::ScheduleTask(IndexedDBDatabase::TaskType type,
101                                        Operation task) {
102  if (state_ == FINISHED)
103    return;
104
105  timeout_timer_.Stop();
106  used_ = true;
107  if (type == IndexedDBDatabase::NORMAL_TASK) {
108    task_queue_.push(task);
109    ++diagnostics_.tasks_scheduled;
110  } else {
111    preemptive_task_queue_.push(task);
112  }
113  RunTasksIfStarted();
114}
115
116void IndexedDBTransaction::RunTasksIfStarted() {
117  DCHECK(used_);
118
119  // Not started by the coordinator yet.
120  if (state_ != STARTED)
121    return;
122
123  // A task is already posted.
124  if (should_process_queue_)
125    return;
126
127  should_process_queue_ = true;
128  base::MessageLoop::current()->PostTask(
129      FROM_HERE, base::Bind(&IndexedDBTransaction::ProcessTaskQueue, this));
130}
131
132void IndexedDBTransaction::Abort() {
133  Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
134                               "Internal error (unknown cause)"));
135}
136
137void IndexedDBTransaction::Abort(const IndexedDBDatabaseError& error) {
138  IDB_TRACE("IndexedDBTransaction::Abort");
139  if (state_ == FINISHED)
140    return;
141
142  // The last reference to this object may be released while performing the
143  // abort steps below. We therefore take a self reference to keep ourselves
144  // alive while executing this method.
145  scoped_refptr<IndexedDBTransaction> protect(this);
146
147  timeout_timer_.Stop();
148
149  state_ = FINISHED;
150  should_process_queue_ = false;
151
152  if (backing_store_transaction_begun_)
153    transaction_->Rollback();
154
155  // Run the abort tasks, if any.
156  while (!abort_task_stack_.empty())
157    abort_task_stack_.pop().Run(0);
158
159  preemptive_task_queue_.clear();
160  task_queue_.clear();
161
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.
166  CloseOpenCursors();
167  transaction_->Reset();
168
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);
173#ifndef NDEBUG
174  DCHECK(!database_->transaction_coordinator().IsActive(this));
175#endif
176  database_->TransactionFinished(this);
177
178  if (callbacks_.get())
179    callbacks_->OnAbort(id_, error);
180
181  database_->TransactionFinishedAndAbortFired(this);
182
183  database_ = NULL;
184}
185
186bool IndexedDBTransaction::IsTaskQueueEmpty() const {
187  return preemptive_task_queue_.empty() && task_queue_.empty();
188}
189
190bool IndexedDBTransaction::HasPendingTasks() const {
191  return pending_preemptive_events_ || !IsTaskQueueEmpty();
192}
193
194void IndexedDBTransaction::RegisterOpenCursor(IndexedDBCursor* cursor) {
195  open_cursors_.insert(cursor);
196}
197
198void IndexedDBTransaction::UnregisterOpenCursor(IndexedDBCursor* cursor) {
199  open_cursors_.erase(cursor);
200}
201
202void IndexedDBTransaction::Start() {
203  // TransactionCoordinator has started this transaction.
204  DCHECK_EQ(CREATED, state_);
205  state_ = STARTED;
206  database_->TransactionStarted(this);
207  diagnostics_.start_time = base::Time::Now();
208
209  if (!used_)
210    return;
211
212  RunTasksIfStarted();
213}
214
215void IndexedDBTransaction::Commit() {
216  IDB_TRACE("IndexedDBTransaction::Commit");
217
218  // In multiprocess ports, front-end may have requested a commit but
219  // an abort has already been initiated asynchronously by the
220  // back-end.
221  if (state_ == FINISHED)
222    return;
223
224  DCHECK(!used_ || state_ == STARTED);
225  commit_pending_ = true;
226
227  // Front-end has requested a commit, but there may be tasks like
228  // create_index which are considered synchronous by the front-end
229  // but are processed asynchronously.
230  if (HasPendingTasks())
231    return;
232
233  // The last reference to this object may be released while performing the
234  // commit steps below. We therefore take a self reference to keep ourselves
235  // alive while executing this method.
236  scoped_refptr<IndexedDBTransaction> protect(this);
237
238  timeout_timer_.Stop();
239
240  state_ = FINISHED;
241
242  bool committed = !used_ || transaction_->Commit();
243
244  // Backing store resources (held via cursors) must be released
245  // before script callbacks are fired, as the script callbacks may
246  // release references and allow the backing store itself to be
247  // released, and order is critical.
248  CloseOpenCursors();
249  transaction_->Reset();
250
251  // Transactions must also be marked as completed before the
252  // front-end is notified, as the transaction completion unblocks
253  // operations like closing connections.
254  database_->transaction_coordinator().DidFinishTransaction(this);
255  database_->TransactionFinished(this);
256
257  if (committed) {
258    abort_task_stack_.clear();
259    callbacks_->OnComplete(id_);
260    database_->TransactionFinishedAndCompleteFired(this);
261  } else {
262    while (!abort_task_stack_.empty())
263      abort_task_stack_.pop().Run(0);
264
265    callbacks_->OnAbort(
266        id_,
267        IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
268                               "Internal error committing transaction."));
269    database_->TransactionFinishedAndAbortFired(this);
270    database_->TransactionCommitFailed();
271  }
272
273  database_ = NULL;
274}
275
276void IndexedDBTransaction::ProcessTaskQueue() {
277  IDB_TRACE("IndexedDBTransaction::ProcessTaskQueue");
278
279  // May have been aborted.
280  if (!should_process_queue_)
281    return;
282
283  DCHECK(!IsTaskQueueEmpty());
284  should_process_queue_ = false;
285
286  if (!backing_store_transaction_begun_) {
287    transaction_->Begin();
288    backing_store_transaction_begun_ = true;
289  }
290
291  // The last reference to this object may be released while performing the
292  // tasks. Take take a self reference to keep this object alive so that
293  // the loop termination conditions can be checked.
294  scoped_refptr<IndexedDBTransaction> protect(this);
295
296  TaskQueue* task_queue =
297      pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
298  while (!task_queue->empty() && state_ != FINISHED) {
299    DCHECK_EQ(STARTED, state_);
300    Operation task(task_queue->pop());
301    task.Run(this);
302    if (!pending_preemptive_events_) {
303      DCHECK(diagnostics_.tasks_completed < diagnostics_.tasks_scheduled);
304      ++diagnostics_.tasks_completed;
305    }
306
307    // Event itself may change which queue should be processed next.
308    task_queue =
309        pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
310  }
311
312  // If there are no pending tasks, we haven't already committed/aborted,
313  // and the front-end requested a commit, it is now safe to do so.
314  if (!HasPendingTasks() && state_ != FINISHED && commit_pending_) {
315    Commit();
316    return;
317  }
318
319  // The transaction may have been aborted while processing tasks.
320  if (state_ == FINISHED)
321    return;
322
323  // Otherwise, start a timer in case the front-end gets wedged and
324  // never requests further activity.
325  timeout_timer_.Start(
326      FROM_HERE,
327      base::TimeDelta::FromSeconds(kInactivityTimeoutPeriodSeconds),
328      base::Bind(&IndexedDBTransaction::Timeout, this));
329}
330
331void IndexedDBTransaction::Timeout() {
332  Abort(IndexedDBDatabaseError(
333      blink::WebIDBDatabaseExceptionTimeoutError,
334      ASCIIToUTF16("Transaction timed out due to inactivity.")));
335}
336
337void IndexedDBTransaction::CloseOpenCursors() {
338  for (std::set<IndexedDBCursor*>::iterator i = open_cursors_.begin();
339       i != open_cursors_.end();
340       ++i)
341    (*i)->Close();
342  open_cursors_.clear();
343}
344
345}  // namespace content
346