Move basic IDBBackingStoreTransaction operations to IDBServerConnection
[WebKit-https.git] / Source / WebCore / Modules / indexeddb / IDBTransactionBackend.cpp
1 /*
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 "IDBTransactionBackend.h"
28
29 #if ENABLE(INDEXED_DATABASE)
30
31 #include "IDBCursorBackend.h"
32 #include "IDBDatabaseBackend.h"
33 #include "IDBDatabaseCallbacks.h"
34 #include "IDBDatabaseException.h"
35 #include "IDBFactoryBackendInterface.h"
36 #include "IDBKeyRange.h"
37 #include "IDBServerConnection.h"
38 #include "IDBTransactionBackendOperations.h"
39 #include "IDBTransactionCoordinator.h"
40 #include "Logging.h"
41
42 namespace WebCore {
43
44 PassRefPtr<IDBTransactionBackend> IDBTransactionBackend::create(IDBDatabaseBackend* databaseBackend, int64_t id, PassRefPtr<IDBDatabaseCallbacks> callbacks, const Vector<int64_t>& objectStoreIds, IndexedDB::TransactionMode mode)
45 {
46     HashSet<int64_t> objectStoreHashSet;
47     for (size_t i = 0; i < objectStoreIds.size(); ++i)
48         objectStoreHashSet.add(objectStoreIds[i]);
49
50     return adoptRef(new IDBTransactionBackend(databaseBackend, id, callbacks, objectStoreHashSet, mode));
51 }
52
53 IDBTransactionBackend::IDBTransactionBackend(IDBDatabaseBackend* databaseBackend, int64_t id, PassRefPtr<IDBDatabaseCallbacks> callbacks, const HashSet<int64_t>& objectStoreIds, IndexedDB::TransactionMode mode)
54     : m_objectStoreIds(objectStoreIds)
55     , m_mode(mode)
56     , m_state(Unopened)
57     , m_commitPending(false)
58     , m_callbacks(callbacks)
59     , m_database(databaseBackend)
60     , m_taskTimer(this, &IDBTransactionBackend::taskTimerFired)
61     , m_pendingPreemptiveEvents(0)
62     , m_id(id)
63 {
64     // We pass a reference of this object before it can be adopted.
65     relaxAdoptionRequirement();
66
67     m_database->transactionCoordinator()->didCreateTransaction(this);
68
69     RefPtr<IDBTransactionBackend> backend(this);
70     m_database->serverConnection().openTransaction(id, objectStoreIds, mode, [backend](bool success) {
71         if (!success) {
72             callOnMainThread([backend]() {
73                 backend->abort();
74             });
75             return;
76         }
77
78         backend->m_state = Unused;
79         if (backend->hasPendingTasks())
80             backend->start();
81     });
82 }
83
84 IDBTransactionBackend::~IDBTransactionBackend()
85 {
86     // It shouldn't be possible for this object to get deleted until it's either complete or aborted.
87     ASSERT(m_state == Finished);
88     // The backing store transaction should also be gone by now.
89     ASSERT(!m_database->serverConnection().deprecatedBackingStoreTransaction(m_id));
90 }
91
92 IDBBackingStoreTransactionInterface& IDBTransactionBackend::deprecatedBackingStoreTransaction()
93 {
94     IDBBackingStoreTransactionInterface* backingStoreTransaction = m_database->serverConnection().deprecatedBackingStoreTransaction(m_id);
95     ASSERT(backingStoreTransaction);
96     return *backingStoreTransaction;
97 }
98
99 void IDBTransactionBackend::scheduleTask(IDBDatabaseBackend::TaskType type, PassRefPtr<IDBOperation> task, PassRefPtr<IDBSynchronousOperation> abortTask)
100 {
101     if (m_state == Finished)
102         return;
103
104     if (type == IDBDatabaseBackend::NormalTask)
105         m_taskQueue.append(task);
106     else
107         m_preemptiveTaskQueue.append(task);
108
109     if (abortTask)
110         m_abortTaskQueue.prepend(abortTask);
111
112     if (m_state == Unopened)
113         return;
114
115     if (m_state == Unused)
116         start();
117     else if (m_state == Running && !m_taskTimer.isActive())
118         m_taskTimer.startOneShot(0);
119 }
120
121 void IDBTransactionBackend::abort()
122 {
123     abort(IDBDatabaseError::create(IDBDatabaseException::UnknownError, "Internal error (unknown cause)"));
124 }
125
126 void IDBTransactionBackend::abort(PassRefPtr<IDBDatabaseError> error)
127 {
128     LOG(StorageAPI, "IDBTransactionBackend::abort");
129     if (m_state == Finished)
130         return;
131
132     bool wasRunning = m_state == Running;
133
134     // The last reference to this object may be released while performing the
135     // abort steps below. We therefore take a self reference to keep ourselves
136     // alive while executing this method.
137     Ref<IDBTransactionBackend> protect(*this);
138
139     m_state = Finished;
140     m_taskTimer.stop();
141
142     if (wasRunning)
143         m_database->serverConnection().rollbackTransaction(m_id, []() { });
144
145     // Run the abort tasks, if any.
146     while (!m_abortTaskQueue.isEmpty()) {
147         RefPtr<IDBSynchronousOperation> task(m_abortTaskQueue.takeFirst());
148         task->perform();
149     }
150
151     // Backing store resources (held via cursors) must be released before script callbacks
152     // are fired, as the script callbacks may release references and allow the backing store
153     // itself to be released, and order is critical.
154     closeOpenCursors();
155
156     m_database->serverConnection().resetTransaction(m_id, []() { });
157
158     // Transactions must also be marked as completed before the front-end is notified, as
159     // the transaction completion unblocks operations like closing connections.
160     m_database->transactionCoordinator()->didFinishTransaction(this);
161     ASSERT(!m_database->transactionCoordinator()->isActive(this));
162     m_database->transactionFinished(this);
163
164     if (m_callbacks)
165         m_callbacks->onAbort(id(), error);
166
167     m_database->transactionFinishedAndAbortFired(this);
168
169     m_database = 0;
170 }
171
172 bool IDBTransactionBackend::isTaskQueueEmpty() const
173 {
174     return m_preemptiveTaskQueue.isEmpty() && m_taskQueue.isEmpty();
175 }
176
177 bool IDBTransactionBackend::hasPendingTasks() const
178 {
179     return m_pendingPreemptiveEvents || !isTaskQueueEmpty();
180 }
181
182 void IDBTransactionBackend::registerOpenCursor(IDBCursorBackend* cursor)
183 {
184     m_openCursors.add(cursor);
185 }
186
187 void IDBTransactionBackend::unregisterOpenCursor(IDBCursorBackend* cursor)
188 {
189     m_openCursors.remove(cursor);
190 }
191
192 void IDBTransactionBackend::run()
193 {
194     // TransactionCoordinator has started this transaction. Schedule a timer
195     // to process the first task.
196     ASSERT(m_state == StartPending || m_state == Running);
197     ASSERT(!m_taskTimer.isActive());
198
199     m_taskTimer.startOneShot(0);
200 }
201
202 void IDBTransactionBackend::start()
203 {
204     ASSERT(m_state == Unused);
205
206     m_state = StartPending;
207     m_database->transactionCoordinator()->didStartTransaction(this);
208     m_database->transactionStarted(this);
209 }
210
211 void IDBTransactionBackend::commit()
212 {
213     LOG(StorageAPI, "IDBTransactionBackend::commit");
214
215     // In multiprocess ports, front-end may have requested a commit but an abort has already
216     // been initiated asynchronously by the back-end.
217     if (m_state == Finished)
218         return;
219
220     ASSERT(m_state == Unused || m_state == Running);
221     m_commitPending = true;
222
223     // Front-end has requested a commit, but there may be tasks like createIndex which
224     // are considered synchronous by the front-end but are processed asynchronously.
225     if (hasPendingTasks())
226         return;
227
228     // The last reference to this object may be released while performing the
229     // commit steps below. We therefore take a self reference to keep ourselves
230     // alive while executing this method.
231     RefPtr<IDBTransactionBackend> backend(this);
232
233     bool unused = m_state == Unused;
234     m_state = Finished;
235
236     bool committed = unused;
237
238     m_database->serverConnection().commitTransaction(m_id, [backend, this, committed, unused](bool success) mutable {
239         committed |= success;
240
241         // Backing store resources (held via cursors) must be released before script callbacks
242         // are fired, as the script callbacks may release references and allow the backing store
243         // itself to be released, and order is critical.
244         closeOpenCursors();
245
246         m_database->serverConnection().resetTransaction(m_id, []() { });
247
248         // Transactions must also be marked as completed before the front-end is notified, as
249         // the transaction completion unblocks operations like closing connections.
250         if (!unused)
251             m_database->transactionCoordinator()->didFinishTransaction(this);
252         m_database->transactionFinished(this);
253
254         if (committed) {
255             m_callbacks->onComplete(id());
256             m_database->transactionFinishedAndCompleteFired(this);
257         } else {
258             m_callbacks->onAbort(id(), IDBDatabaseError::create(IDBDatabaseException::UnknownError, "Internal error committing transaction."));
259             m_database->transactionFinishedAndAbortFired(this);
260         }
261
262         m_database = 0;
263     });
264 }
265
266 void IDBTransactionBackend::taskTimerFired(Timer<IDBTransactionBackend>*)
267 {
268     LOG(StorageAPI, "IDBTransactionBackend::taskTimerFired");
269
270     if (m_state == StartPending) {
271         m_database->serverConnection().beginTransaction(m_id, []() { });
272         m_state = Running;
273     }
274
275     // The last reference to this object may be released while performing a task.
276     // Take a self reference to keep this object alive so that tasks can
277     // successfully make their completion callbacks.
278     RefPtr<IDBTransactionBackend> self(this);
279
280     TaskQueue* taskQueue = m_pendingPreemptiveEvents ? &m_preemptiveTaskQueue : &m_taskQueue;
281     if (!taskQueue->isEmpty() && m_state != Finished) {
282         ASSERT(m_state == Running);
283         RefPtr<IDBOperation> task(taskQueue->takeFirst());
284         task->perform([self, this, task]() {
285             m_taskTimer.startOneShot(0);
286         });
287
288         return;
289     }
290
291     // If there are no pending tasks, we haven't already committed/aborted,
292     // and the front-end requested a commit, it is now safe to do so.
293     if (!hasPendingTasks() && m_state != Finished && m_commitPending)
294         commit();
295 }
296
297 void IDBTransactionBackend::closeOpenCursors()
298 {
299     for (HashSet<IDBCursorBackend*>::iterator i = m_openCursors.begin(); i != m_openCursors.end(); ++i)
300         (*i)->close();
301     m_openCursors.clear();
302 }
303
304 void IDBTransactionBackend::scheduleCreateObjectStoreOperation(const IDBObjectStoreMetadata& objectStoreMetadata)
305 {
306     scheduleTask(CreateObjectStoreOperation::create(this, objectStoreMetadata), CreateObjectStoreAbortOperation::create(this, objectStoreMetadata.id));
307 }
308
309 void IDBTransactionBackend::scheduleDeleteObjectStoreOperation(const IDBObjectStoreMetadata& objectStoreMetadata)
310 {
311     scheduleTask(DeleteObjectStoreOperation::create(this, objectStoreMetadata), DeleteObjectStoreAbortOperation::create(this, objectStoreMetadata));
312 }
313
314 void IDBTransactionBackend::scheduleVersionChangeOperation(int64_t transactionId, int64_t requestedVersion, PassRefPtr<IDBCallbacks> callbacks, PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks, const IDBDatabaseMetadata& metadata)
315 {
316     scheduleTask(IDBDatabaseBackend::VersionChangeOperation::create(this, transactionId, requestedVersion, callbacks, databaseCallbacks), IDBDatabaseBackend::VersionChangeAbortOperation::create(this, String::number(metadata.version), metadata.version));
317 }
318
319 void IDBTransactionBackend::scheduleCreateIndexOperation(int64_t objectStoreId, const IDBIndexMetadata& indexMetadata)
320 {
321     scheduleTask(CreateIndexOperation::create(this, objectStoreId, indexMetadata), CreateIndexAbortOperation::create(this, objectStoreId, indexMetadata.id));
322 }
323
324 void IDBTransactionBackend::scheduleDeleteIndexOperation(int64_t objectStoreId, const IDBIndexMetadata& indexMetadata)
325 {
326     scheduleTask(DeleteIndexOperation::create(this, objectStoreId, indexMetadata), DeleteIndexAbortOperation::create(this, objectStoreId, indexMetadata));
327 }
328
329 void IDBTransactionBackend::scheduleGetOperation(const IDBDatabaseMetadata& metadata, int64_t objectStoreId, int64_t indexId, PassRefPtr<IDBKeyRange> keyRange, IndexedDB::CursorType cursorType, PassRefPtr<IDBCallbacks> callbacks)
330 {
331     scheduleTask(GetOperation::create(this, metadata, objectStoreId, indexId, keyRange, cursorType, callbacks));
332 }
333
334 void IDBTransactionBackend::schedulePutOperation(const IDBObjectStoreMetadata& objectStoreMetadata, PassRefPtr<SharedBuffer> value, PassRefPtr<IDBKey> key, IDBDatabaseBackend::PutMode putMode, PassRefPtr<IDBCallbacks> callbacks, const Vector<int64_t>& indexIds, const Vector<IndexKeys>& indexKeys)
335 {
336     scheduleTask(PutOperation::create(this, database().id(), objectStoreMetadata, value, key, putMode, callbacks, indexIds, indexKeys));
337 }
338
339 void IDBTransactionBackend::scheduleSetIndexesReadyOperation(size_t indexCount)
340 {
341     scheduleTask(IDBDatabaseBackend::PreemptiveTask, SetIndexesReadyOperation::create(this, indexCount));
342 }
343
344 void IDBTransactionBackend::scheduleOpenCursorOperation(int64_t objectStoreId, int64_t indexId, PassRefPtr<IDBKeyRange> keyRange, IndexedDB::CursorDirection direction, IndexedDB::CursorType cursorType, IDBDatabaseBackend::TaskType taskType, PassRefPtr<IDBCallbacks> callbacks)
345 {
346     scheduleTask(OpenCursorOperation::create(this, database().id(), objectStoreId, indexId, keyRange, direction, cursorType, taskType, callbacks));
347 }
348
349 void IDBTransactionBackend::scheduleCountOperation(int64_t objectStoreId, int64_t indexId, PassRefPtr<IDBKeyRange> keyRange, PassRefPtr<IDBCallbacks> callbacks)
350 {
351     scheduleTask(CountOperation::create(this, database().id(), objectStoreId, indexId, keyRange, callbacks));
352 }
353
354 void IDBTransactionBackend::scheduleDeleteRangeOperation(int64_t objectStoreId, PassRefPtr<IDBKeyRange> keyRange, PassRefPtr<IDBCallbacks> callbacks)
355 {
356     scheduleTask(DeleteRangeOperation::create(this, database().id(), objectStoreId, keyRange, callbacks));
357 }
358
359 void IDBTransactionBackend::scheduleClearOperation(int64_t objectStoreId, PassRefPtr<IDBCallbacks> callbacks)
360 {
361     scheduleTask(ClearOperation::create(this, database().id(), objectStoreId, callbacks));
362 }
363
364 PassRefPtr<IDBCursorBackend> IDBTransactionBackend::createCursorBackend(IDBBackingStoreCursorInterface& cursor, IndexedDB::CursorType cursorType, IDBDatabaseBackend::TaskType taskType, int64_t objectStoreId)
365 {
366     return m_database->factoryBackend().createCursorBackend(*this, cursor, cursorType, taskType, objectStoreId);
367 }
368
369 };
370
371 #endif // ENABLE(INDEXED_DATABASE)