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_coordinator.h"
6
7#include "base/basictypes.h"
8#include "base/logging.h"
9#include "content/browser/indexed_db/indexed_db_transaction.h"
10
11namespace content {
12
13IndexedDBTransactionCoordinator::IndexedDBTransactionCoordinator() {}
14
15IndexedDBTransactionCoordinator::~IndexedDBTransactionCoordinator() {
16  DCHECK(!queued_transactions_.size());
17  DCHECK(!started_transactions_.size());
18}
19
20void IndexedDBTransactionCoordinator::DidCreateTransaction(
21    scoped_refptr<IndexedDBTransaction> transaction) {
22  DCHECK(!queued_transactions_.count(transaction));
23  DCHECK(!started_transactions_.count(transaction));
24  DCHECK_EQ(IndexedDBTransaction::CREATED, transaction->state());
25
26  queued_transactions_.insert(transaction);
27  ProcessQueuedTransactions();
28}
29
30void IndexedDBTransactionCoordinator::DidFinishTransaction(
31    scoped_refptr<IndexedDBTransaction> transaction) {
32  if (queued_transactions_.count(transaction)) {
33    DCHECK(!started_transactions_.count(transaction));
34    queued_transactions_.erase(transaction);
35  } else {
36    DCHECK(started_transactions_.count(transaction));
37    started_transactions_.erase(transaction);
38  }
39
40  ProcessQueuedTransactions();
41}
42
43#ifndef NDEBUG
44// Verifies internal consistency while returning whether anything is found.
45bool IndexedDBTransactionCoordinator::IsActive(
46    IndexedDBTransaction* transaction) {
47  bool found = false;
48  if (queued_transactions_.count(transaction))
49    found = true;
50  if (started_transactions_.count(transaction)) {
51    DCHECK(!found);
52    found = true;
53  }
54  return found;
55}
56#endif
57
58std::vector<const IndexedDBTransaction*>
59IndexedDBTransactionCoordinator::GetTransactions() const {
60  std::vector<const IndexedDBTransaction*> result;
61
62  for (TransactionSet::const_iterator it = started_transactions_.begin();
63       it != started_transactions_.end();
64       ++it) {
65    result.push_back(*it);
66  }
67  for (TransactionSet::const_iterator it = queued_transactions_.begin();
68       it != queued_transactions_.end();
69       ++it) {
70    result.push_back(*it);
71  }
72
73  return result;
74}
75
76void IndexedDBTransactionCoordinator::ProcessQueuedTransactions() {
77  if (queued_transactions_.empty())
78    return;
79
80  DCHECK(started_transactions_.empty() ||
81         (*started_transactions_.begin())->mode() !=
82             indexed_db::TRANSACTION_VERSION_CHANGE);
83
84  // The locked_scope set accumulates the ids of object stores in the scope of
85  // running read/write transactions. Other read-write transactions with
86  // stores in this set may not be started. Read-only transactions may start,
87  // taking a snapshot of the database, which does not include uncommitted
88  // data. ("Version change" transactions are exclusive, but handled by the
89  // connection sequencing in IndexedDBDatabase.)
90  std::set<int64> locked_scope;
91  for (TransactionSet::const_iterator it = started_transactions_.begin();
92       it != started_transactions_.end();
93       ++it) {
94    IndexedDBTransaction* transaction = *it;
95    if (transaction->mode() == indexed_db::TRANSACTION_READ_WRITE) {
96      // Started read/write transactions have exclusive access to the object
97      // stores within their scopes.
98      locked_scope.insert(transaction->scope().begin(),
99                          transaction->scope().end());
100    }
101  }
102
103  TransactionSet::const_iterator it = queued_transactions_.begin();
104  while (it != queued_transactions_.end()) {
105    scoped_refptr<IndexedDBTransaction> transaction = *it;
106    ++it;
107    if (CanStartTransaction(transaction, locked_scope)) {
108      DCHECK_EQ(IndexedDBTransaction::CREATED, transaction->state());
109      queued_transactions_.erase(transaction);
110      started_transactions_.insert(transaction);
111      transaction->Start();
112      DCHECK_EQ(IndexedDBTransaction::STARTED, transaction->state());
113    }
114    if (transaction->mode() == indexed_db::TRANSACTION_READ_WRITE) {
115      // Either the transaction started, so it has exclusive access to the
116      // stores in its scope, or per the spec the transaction which was
117      // created first must get access first, so the stores are also locked.
118      locked_scope.insert(transaction->scope().begin(),
119                          transaction->scope().end());
120    }
121  }
122}
123
124template<typename T>
125static bool DoSetsIntersect(const std::set<T>& set1,
126                            const std::set<T>& set2) {
127  typename std::set<T>::const_iterator it1 = set1.begin();
128  typename std::set<T>::const_iterator it2 = set2.begin();
129  while (it1 != set1.end() && it2 != set2.end()) {
130    if (*it1 < *it2)
131      ++it1;
132    else if (*it2 < *it1)
133      ++it2;
134    else
135      return true;
136  }
137  return false;
138}
139
140bool IndexedDBTransactionCoordinator::CanStartTransaction(
141    IndexedDBTransaction* const transaction,
142    const std::set<int64>& locked_scope) const {
143  DCHECK(queued_transactions_.count(transaction));
144  switch (transaction->mode()) {
145    case indexed_db::TRANSACTION_VERSION_CHANGE:
146      DCHECK_EQ(static_cast<size_t>(1), queued_transactions_.size());
147      DCHECK(started_transactions_.empty());
148      DCHECK(locked_scope.empty());
149      return true;
150
151    case indexed_db::TRANSACTION_READ_ONLY:
152      return true;
153
154    case indexed_db::TRANSACTION_READ_WRITE:
155      return !DoSetsIntersect(transaction->scope(), locked_scope);
156  }
157  NOTREACHED();
158  return false;
159}
160
161}  // namespace content
162