• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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