blob: 44af5cf7ed5786d372ad64bcabe86dbb0cc97791 [file] [log] [blame]
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +01001/*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "modules/indexeddb/IDBTransactionBackendImpl.h"
28
29#include "modules/indexeddb/IDBBackingStore.h"
30#include "modules/indexeddb/IDBCursorBackendImpl.h"
31#include "modules/indexeddb/IDBDatabaseBackendImpl.h"
32#include "modules/indexeddb/IDBDatabaseCallbacks.h"
33#include "modules/indexeddb/IDBDatabaseException.h"
34#include "modules/indexeddb/IDBTracing.h"
35#include "modules/indexeddb/IDBTransactionCoordinator.h"
36
37namespace WebCore {
38
39PassRefPtr<IDBTransactionBackendImpl> IDBTransactionBackendImpl::create(int64_t id, PassRefPtr<IDBDatabaseCallbacks> callbacks, const Vector<int64_t>& objectStoreIds, IndexedDB::TransactionMode mode, IDBDatabaseBackendImpl* database)
40{
41 HashSet<int64_t> objectStoreHashSet;
42 for (size_t i = 0; i < objectStoreIds.size(); ++i)
43 objectStoreHashSet.add(objectStoreIds[i]);
44
45 return adoptRef(new IDBTransactionBackendImpl(id, callbacks, objectStoreHashSet, mode, database));
46}
47
48IDBTransactionBackendImpl::IDBTransactionBackendImpl(int64_t id, PassRefPtr<IDBDatabaseCallbacks> callbacks, const HashSet<int64_t>& objectStoreIds, IndexedDB::TransactionMode mode, IDBDatabaseBackendImpl* database)
49 : m_id(id)
50 , m_objectStoreIds(objectStoreIds)
51 , m_mode(mode)
52 , m_state(Unused)
53 , m_commitPending(false)
54 , m_callbacks(callbacks)
55 , m_database(database)
56 , m_transaction(database->backingStore().get())
57 , m_taskTimer(this, &IDBTransactionBackendImpl::taskTimerFired)
58 , m_pendingPreemptiveEvents(0)
59{
60 // We pass a reference of this object before it can be adopted.
61 relaxAdoptionRequirement();
62
63 m_database->transactionCoordinator()->didCreateTransaction(this);
64}
65
66IDBTransactionBackendImpl::~IDBTransactionBackendImpl()
67{
68 // It shouldn't be possible for this object to get deleted until it's either complete or aborted.
69 ASSERT(m_state == Finished);
70}
71
72void IDBTransactionBackendImpl::scheduleTask(IDBDatabaseBackendInterface::TaskType type, PassOwnPtr<Operation> task, PassOwnPtr<Operation> abortTask)
73{
74 if (m_state == Finished)
75 return;
76
77 if (type == IDBDatabaseBackendInterface::NormalTask)
78 m_taskQueue.append(task);
79 else
80 m_preemptiveTaskQueue.append(task);
81
82 if (abortTask)
83 m_abortTaskQueue.prepend(abortTask);
84
85 if (m_state == Unused)
86 start();
87 else if (m_state == Running && !m_taskTimer.isActive())
88 m_taskTimer.startOneShot(0);
89}
90
91void IDBTransactionBackendImpl::abort()
92{
93 abort(IDBDatabaseError::create(IDBDatabaseException::UnknownError, "Internal error (unknown cause)"));
94}
95
96void IDBTransactionBackendImpl::abort(PassRefPtr<IDBDatabaseError> error)
97{
98 IDB_TRACE("IDBTransactionBackendImpl::abort");
99 if (m_state == Finished)
100 return;
101
102 bool wasRunning = m_state == Running;
103
104 // The last reference to this object may be released while performing the
105 // abort steps below. We therefore take a self reference to keep ourselves
106 // alive while executing this method.
107 RefPtr<IDBTransactionBackendImpl> protect(this);
108
109 m_state = Finished;
110 m_taskTimer.stop();
111
112 if (wasRunning)
113 m_transaction.rollback();
114
115 // Run the abort tasks, if any.
116 while (!m_abortTaskQueue.isEmpty()) {
117 OwnPtr<Operation> task(m_abortTaskQueue.takeFirst());
118 task->perform(0);
119 }
120
121 // Backing store resources (held via cursors) must be released before script callbacks
122 // are fired, as the script callbacks may release references and allow the backing store
123 // itself to be released, and order is critical.
124 closeOpenCursors();
125 m_transaction.reset();
126
127 // Transactions must also be marked as completed before the front-end is notified, as
128 // the transaction completion unblocks operations like closing connections.
129 m_database->transactionCoordinator()->didFinishTransaction(this);
130 ASSERT(!m_database->transactionCoordinator()->isActive(this));
131 m_database->transactionFinished(this);
132
133 if (m_callbacks)
134 m_callbacks->onAbort(m_id, error);
135
136 m_database->transactionFinishedAndAbortFired(this);
137
138 m_database = 0;
139}
140
141bool IDBTransactionBackendImpl::isTaskQueueEmpty() const
142{
143 return m_preemptiveTaskQueue.isEmpty() && m_taskQueue.isEmpty();
144}
145
146bool IDBTransactionBackendImpl::hasPendingTasks() const
147{
148 return m_pendingPreemptiveEvents || !isTaskQueueEmpty();
149}
150
151void IDBTransactionBackendImpl::registerOpenCursor(IDBCursorBackendImpl* cursor)
152{
153 m_openCursors.add(cursor);
154}
155
156void IDBTransactionBackendImpl::unregisterOpenCursor(IDBCursorBackendImpl* cursor)
157{
158 m_openCursors.remove(cursor);
159}
160
161void IDBTransactionBackendImpl::run()
162{
163 // TransactionCoordinator has started this transaction. Schedule a timer
164 // to process the first task.
165 ASSERT(m_state == StartPending || m_state == Running);
166 ASSERT(!m_taskTimer.isActive());
167
168 m_taskTimer.startOneShot(0);
169}
170
171void IDBTransactionBackendImpl::start()
172{
173 ASSERT(m_state == Unused);
174
175 m_state = StartPending;
176 m_database->transactionCoordinator()->didStartTransaction(this);
177 m_database->transactionStarted(this);
178}
179
180void IDBTransactionBackendImpl::commit()
181{
182 IDB_TRACE("IDBTransactionBackendImpl::commit");
183
184 // In multiprocess ports, front-end may have requested a commit but an abort has already
185 // been initiated asynchronously by the back-end.
186 if (m_state == Finished)
187 return;
188
189 ASSERT(m_state == Unused || m_state == Running);
190 m_commitPending = true;
191
192 // Front-end has requested a commit, but there may be tasks like createIndex which
193 // are considered synchronous by the front-end but are processed asynchronously.
194 if (hasPendingTasks())
195 return;
196
197 // The last reference to this object may be released while performing the
198 // commit steps below. We therefore take a self reference to keep ourselves
199 // alive while executing this method.
200 RefPtr<IDBTransactionBackendImpl> protect(this);
201
202 bool unused = m_state == Unused;
203 m_state = Finished;
204
205 bool committed = unused || m_transaction.commit();
206
207 // Backing store resources (held via cursors) must be released before script callbacks
208 // are fired, as the script callbacks may release references and allow the backing store
209 // itself to be released, and order is critical.
210 closeOpenCursors();
211 m_transaction.reset();
212
213 // Transactions must also be marked as completed before the front-end is notified, as
214 // the transaction completion unblocks operations like closing connections.
215 if (!unused)
216 m_database->transactionCoordinator()->didFinishTransaction(this);
217 m_database->transactionFinished(this);
218
219 if (committed) {
220 m_callbacks->onComplete(m_id);
221 m_database->transactionFinishedAndCompleteFired(this);
222 } else {
223 m_callbacks->onAbort(m_id, IDBDatabaseError::create(IDBDatabaseException::UnknownError, "Internal error committing transaction."));
224 m_database->transactionFinishedAndAbortFired(this);
225 }
226
227 m_database = 0;
228}
229
230void IDBTransactionBackendImpl::taskTimerFired(Timer<IDBTransactionBackendImpl>*)
231{
232 IDB_TRACE("IDBTransactionBackendImpl::taskTimerFired");
233 ASSERT(!isTaskQueueEmpty());
234
235 if (m_state == StartPending) {
236 m_transaction.begin();
237 m_state = Running;
238 }
239
240 // The last reference to this object may be released while performing the
241 // tasks. Take take a self reference to keep this object alive so that
242 // the loop termination conditions can be checked.
243 RefPtr<IDBTransactionBackendImpl> protect(this);
244
245 TaskQueue* taskQueue = m_pendingPreemptiveEvents ? &m_preemptiveTaskQueue : &m_taskQueue;
246 while (!taskQueue->isEmpty() && m_state != Finished) {
247 ASSERT(m_state == Running);
248 OwnPtr<Operation> task(taskQueue->takeFirst());
249 task->perform(this);
250
251 // Event itself may change which queue should be processed next.
252 taskQueue = m_pendingPreemptiveEvents ? &m_preemptiveTaskQueue : &m_taskQueue;
253 }
254
255 // If there are no pending tasks, we haven't already committed/aborted,
256 // and the front-end requested a commit, it is now safe to do so.
257 if (!hasPendingTasks() && m_state != Finished && m_commitPending)
258 commit();
259}
260
261void IDBTransactionBackendImpl::closeOpenCursors()
262{
263 for (HashSet<IDBCursorBackendImpl*>::iterator i = m_openCursors.begin(); i != m_openCursors.end(); ++i)
264 (*i)->close();
265 m_openCursors.clear();
266}
267
268} // namespace WebCore