Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / content / browser / indexed_db / indexed_db_transaction.cc
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
23 IndexedDBTransaction::TaskQueue::TaskQueue() {}
24 IndexedDBTransaction::TaskQueue::~TaskQueue() { clear(); }
25
26 void IndexedDBTransaction::TaskQueue::clear() {
27   while (!queue_.empty())
28     queue_.pop();
29 }
30
31 IndexedDBTransaction::Operation IndexedDBTransaction::TaskQueue::pop() {
32   DCHECK(!queue_.empty());
33   Operation task(queue_.front());
34   queue_.pop();
35   return task;
36 }
37
38 IndexedDBTransaction::TaskStack::TaskStack() {}
39 IndexedDBTransaction::TaskStack::~TaskStack() { clear(); }
40
41 void IndexedDBTransaction::TaskStack::clear() {
42   while (!stack_.empty())
43     stack_.pop();
44 }
45
46 IndexedDBTransaction::Operation IndexedDBTransaction::TaskStack::pop() {
47   DCHECK(!stack_.empty());
48   Operation task(stack_.top());
49   stack_.pop();
50   return task;
51 }
52
53 IndexedDBTransaction::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
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
89 void 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
106 void IndexedDBTransaction::ScheduleAbortTask(Operation abort_task) {
107   DCHECK_NE(FINISHED, state_);
108   DCHECK(used_);
109   abort_task_stack_.push(abort_task);
110 }
111
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
128 void IndexedDBTransaction::Abort() {
129   Abort(IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
130                                "Internal error (unknown cause)"));
131 }
132
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
182 bool IndexedDBTransaction::IsTaskQueueEmpty() const {
183   return preemptive_task_queue_.empty() && task_queue_.empty();
184 }
185
186 bool IndexedDBTransaction::HasPendingTasks() const {
187   return pending_preemptive_events_ || !IsTaskQueueEmpty();
188 }
189
190 void IndexedDBTransaction::RegisterOpenCursor(IndexedDBCursor* cursor) {
191   open_cursors_.insert(cursor);
192 }
193
194 void IndexedDBTransaction::UnregisterOpenCursor(IndexedDBCursor* cursor) {
195   open_cursors_.erase(cursor);
196 }
197
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:
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
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
238 leveldb::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
278 leveldb::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
333 void 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
393 void IndexedDBTransaction::Timeout() {
394   Abort(IndexedDBDatabaseError(
395       blink::WebIDBDatabaseExceptionTimeoutError,
396       base::ASCIIToUTF16("Transaction timed out due to inactivity.")));
397 }
398
399 void 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