Unreviewed test gardening.
[WebKit-https.git] / Source / WebCore / Modules / indexeddb / legacy / 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 Ref<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 (auto& id : objectStoreIds)
48         objectStoreHashSet.add(id);
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         // Handle the case where the transaction was aborted before the server connection finished opening the transaction.
79         if (backend->m_state == Finished)
80             return;
81
82         backend->m_state = Unused;
83         if (backend->hasPendingTasks())
84             backend->start();
85     });
86 }
87
88 IDBTransactionBackend::~IDBTransactionBackend()
89 {
90     // It shouldn't be possible for this object to get deleted unless it's unused, complete, or aborted.
91     ASSERT(m_state == Finished || m_state == Unused);
92 }
93
94 void IDBTransactionBackend::scheduleTask(IDBDatabaseBackend::TaskType type, PassRefPtr<IDBOperation> task, PassRefPtr<IDBSynchronousOperation> abortTask)
95 {
96     if (m_state == Finished)
97         return;
98
99     if (type == IDBDatabaseBackend::NormalTask)
100         m_taskQueue.append(task);
101     else
102         m_preemptiveTaskQueue.append(task);
103
104     if (abortTask)
105         m_abortTaskQueue.prepend(abortTask);
106
107     if (m_state == Unopened)
108         return;
109
110     if (m_state == Unused)
111         start();
112     else if (m_state == Running && !m_taskTimer.isActive())
113         m_taskTimer.startOneShot(0);
114 }
115
116 void IDBTransactionBackend::abort()
117 {
118     abort(IDBDatabaseError::create(IDBDatabaseException::UnknownError, "Internal error (unknown cause)"));
119 }
120
121 void IDBTransactionBackend::abort(PassRefPtr<IDBDatabaseError> error)
122 {
123 #ifndef NDEBUG
124     if (error)
125         LOG(StorageAPI, "IDBTransactionBackend::abort - (%s) %s", error->name().utf8().data(), error->message().utf8().data());
126     else
127         LOG(StorageAPI, "IDBTransactionBackend::abort (no error)");
128 #endif
129
130     if (m_state == Finished)
131         return;
132
133     bool wasRunning = m_state == Running;
134
135     // The last reference to this object may be released while performing the
136     // abort steps below. We therefore take a self reference to keep ourselves
137     // alive while executing this method.
138     Ref<IDBTransactionBackend> protect(*this);
139
140     m_state = Finished;
141     m_taskTimer.stop();
142
143     if (wasRunning)
144         m_database->serverConnection().rollbackTransactionSync(m_id);
145
146     // Run the abort tasks, if any.
147     while (!m_abortTaskQueue.isEmpty()) {
148         RefPtr<IDBSynchronousOperation> task(m_abortTaskQueue.takeFirst());
149         task->perform();
150     }
151
152     // Backing store resources (held via cursors) must be released before script callbacks
153     // are fired, as the script callbacks may release references and allow the backing store
154     // itself to be released, and order is critical.
155     closeOpenCursors();
156
157     m_database->serverConnection().resetTransactionSync(m_id);
158
159     // Transactions must also be marked as completed before the front-end is notified, as
160     // the transaction completion unblocks operations like closing connections.
161     m_database->transactionCoordinator()->didFinishTransaction(this);
162     ASSERT(!m_database->transactionCoordinator()->isActive(this));
163     m_database->transactionFinished(this);
164
165     RefPtr<IDBDatabaseBackend> database = m_database.release();
166
167     if (m_callbacks)
168         m_callbacks->onAbort(id(), error);
169
170     database->transactionFinishedAndAbortFired(this);
171 }
172
173 bool IDBTransactionBackend::isTaskQueueEmpty() const
174 {
175     return m_preemptiveTaskQueue.isEmpty() && m_taskQueue.isEmpty();
176 }
177
178 bool IDBTransactionBackend::hasPendingTasks() const
179 {
180     return m_pendingPreemptiveEvents || !isTaskQueueEmpty();
181 }
182
183 void IDBTransactionBackend::registerOpenCursor(IDBCursorBackend* cursor)
184 {
185     m_openCursors.add(cursor);
186 }
187
188 void IDBTransactionBackend::unregisterOpenCursor(IDBCursorBackend* cursor)
189 {
190     m_openCursors.remove(cursor);
191 }
192
193 void IDBTransactionBackend::run()
194 {
195     // TransactionCoordinator has started this transaction. Schedule a timer
196     // to process the first task.
197     ASSERT(m_state == StartPending || m_state == Running);
198     ASSERT(!m_taskTimer.isActive());
199
200     m_taskTimer.startOneShot(0);
201 }
202
203 void IDBTransactionBackend::start()
204 {
205     ASSERT(m_state == Unused);
206
207     m_state = StartPending;
208     m_database->transactionCoordinator()->didStartTransaction(this);
209     m_database->transactionStarted(this);
210 }
211
212 void IDBTransactionBackend::commit()
213 {
214     LOG(StorageAPI, "IDBTransactionBackend::commit transaction %lli in state %u", static_cast<long long>(m_id), m_state);
215
216     // In multiprocess ports, front-end may have requested a commit but an abort has already
217     // been initiated asynchronously by the back-end.
218     if (m_state == Finished)
219         return;
220
221     ASSERT(m_state == Unopened || m_state == Unused || m_state == Running);
222     m_commitPending = true;
223
224     // Front-end has requested a commit, but there may be tasks like createIndex which
225     // are considered synchronous by the front-end but are processed asynchronously.
226     if (hasPendingTasks()) {
227         LOG(StorageAPI, "IDBTransactionBackend::commit - Not committing now, transaction still has pending tasks (Transaction %lli)", static_cast<long long>(m_id));
228         return;
229     }
230
231     // The last reference to this object may be released while performing the
232     // commit steps below. We therefore take a self reference to keep ourselves
233     // alive while executing this method.
234     RefPtr<IDBTransactionBackend> backend(this);
235
236     bool unused = m_state == Unused || m_state == Unopened;
237     m_state = Finished;
238
239     bool committed = unused;
240
241     m_database->serverConnection().commitTransaction(m_id, [backend, this, committed, unused](bool success) mutable {
242         // This might be commitTransaction request aborting during or after synchronous IDBTransactionBackend::abort() call.
243         // This can easily happen if the page is navigated before all transactions finish.
244         // In this case we have no further cleanup and don't need to make any callbacks.
245         if (!m_database) {
246             ASSERT(!success);
247             return;
248         }
249
250         committed |= success;
251
252         // Backing store resources (held via cursors) must be released before script callbacks
253         // are fired, as the script callbacks may release references and allow the backing store
254         // itself to be released, and order is critical.
255         closeOpenCursors();
256
257         m_database->serverConnection().resetTransaction(m_id, []() { });
258
259         // Transactions must also be marked as completed before the front-end is notified, as
260         // the transaction completion unblocks operations like closing connections.
261         if (!unused)
262             m_database->transactionCoordinator()->didFinishTransaction(this);
263         m_database->transactionFinished(this);
264
265         if (committed) {
266             m_callbacks->onComplete(id());
267             m_database->transactionFinishedAndCompleteFired(this);
268         } else {
269             m_callbacks->onAbort(id(), IDBDatabaseError::create(IDBDatabaseException::UnknownError, "Internal error committing transaction."));
270             m_database->transactionFinishedAndAbortFired(this);
271         }
272
273         m_database = nullptr;
274     });
275 }
276
277 void IDBTransactionBackend::taskTimerFired()
278 {
279     LOG(StorageAPI, "IDBTransactionBackend::taskTimerFired");
280
281     if (m_state == StartPending) {
282         m_database->serverConnection().beginTransaction(m_id, []() { });
283         m_state = Running;
284     }
285
286     // The last reference to this object may be released while performing a task.
287     // Take a self reference to keep this object alive so that tasks can
288     // successfully make their completion callbacks.
289     RefPtr<IDBTransactionBackend> self(this);
290
291     TaskQueue* taskQueue = m_pendingPreemptiveEvents ? &m_preemptiveTaskQueue : &m_taskQueue;
292     if (!taskQueue->isEmpty() && m_state != Finished) {
293         ASSERT(m_state == Running);
294         RefPtr<IDBOperation> task(taskQueue->takeFirst());
295         task->perform([self, this, task]() {
296             m_taskTimer.startOneShot(0);
297         });
298
299         return;
300     }
301
302     // If there are no pending tasks, we haven't already committed/aborted,
303     // and the front-end requested a commit, it is now safe to do so.
304     if (!hasPendingTasks() && m_state != Finished && m_commitPending)
305         commit();
306 }
307
308 void IDBTransactionBackend::closeOpenCursors()
309 {
310     for (auto& cursor : m_openCursors)
311         cursor->close();
312     m_openCursors.clear();
313 }
314
315 void IDBTransactionBackend::scheduleCreateObjectStoreOperation(const IDBObjectStoreMetadata& objectStoreMetadata)
316 {
317     scheduleTask(CreateObjectStoreOperation::create(this, objectStoreMetadata), CreateObjectStoreAbortOperation::create(this, objectStoreMetadata.id));
318 }
319
320 void IDBTransactionBackend::scheduleDeleteObjectStoreOperation(const IDBObjectStoreMetadata& objectStoreMetadata)
321 {
322     scheduleTask(DeleteObjectStoreOperation::create(this, objectStoreMetadata), DeleteObjectStoreAbortOperation::create(this, objectStoreMetadata));
323 }
324
325 void IDBTransactionBackend::scheduleVersionChangeOperation(int64_t requestedVersion, PassRefPtr<IDBCallbacks> callbacks, PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks, const IDBDatabaseMetadata& metadata)
326 {
327     scheduleTask(IDBDatabaseBackend::VersionChangeOperation::create(this, requestedVersion, callbacks, databaseCallbacks), IDBDatabaseBackend::VersionChangeAbortOperation::create(this, String::number(metadata.version), metadata.version));
328 }
329
330 void IDBTransactionBackend::scheduleCreateIndexOperation(int64_t objectStoreId, const IDBIndexMetadata& indexMetadata)
331 {
332     scheduleTask(CreateIndexOperation::create(this, objectStoreId, indexMetadata), CreateIndexAbortOperation::create(this, objectStoreId, indexMetadata.id));
333 }
334
335 void IDBTransactionBackend::scheduleDeleteIndexOperation(int64_t objectStoreId, const IDBIndexMetadata& indexMetadata)
336 {
337     scheduleTask(DeleteIndexOperation::create(this, objectStoreId, indexMetadata), DeleteIndexAbortOperation::create(this, objectStoreId, indexMetadata));
338 }
339
340 void IDBTransactionBackend::scheduleGetOperation(const IDBDatabaseMetadata& metadata, int64_t objectStoreId, int64_t indexId, PassRefPtr<IDBKeyRange> keyRange, IndexedDB::CursorType cursorType, PassRefPtr<IDBCallbacks> callbacks)
341 {
342     scheduleTask(GetOperation::create(this, metadata, objectStoreId, indexId, keyRange, cursorType, callbacks));
343 }
344
345 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)
346 {
347     scheduleTask(PutOperation::create(this, objectStoreMetadata, value, key, putMode, callbacks, indexIds, indexKeys));
348 }
349
350 void IDBTransactionBackend::scheduleSetIndexesReadyOperation(size_t indexCount)
351 {
352     scheduleTask(IDBDatabaseBackend::PreemptiveTask, SetIndexesReadyOperation::create(this, indexCount));
353 }
354
355 void IDBTransactionBackend::scheduleOpenCursorOperation(int64_t objectStoreId, int64_t indexId, PassRefPtr<IDBKeyRange> keyRange, IndexedDB::CursorDirection direction, IndexedDB::CursorType cursorType, IDBDatabaseBackend::TaskType taskType, PassRefPtr<IDBCallbacks> callbacks)
356 {
357     scheduleTask(OpenCursorOperation::create(this, objectStoreId, indexId, keyRange, direction, cursorType, taskType, callbacks));
358 }
359
360 void IDBTransactionBackend::scheduleCountOperation(int64_t objectStoreId, int64_t indexId, PassRefPtr<IDBKeyRange> keyRange, PassRefPtr<IDBCallbacks> callbacks)
361 {
362     scheduleTask(CountOperation::create(this, objectStoreId, indexId, keyRange, callbacks));
363 }
364
365 void IDBTransactionBackend::scheduleDeleteRangeOperation(int64_t objectStoreId, PassRefPtr<IDBKeyRange> keyRange, PassRefPtr<IDBCallbacks> callbacks)
366 {
367     scheduleTask(DeleteRangeOperation::create(this, objectStoreId, keyRange, callbacks));
368 }
369
370 void IDBTransactionBackend::scheduleClearObjectStoreOperation(int64_t objectStoreId, PassRefPtr<IDBCallbacks> callbacks)
371 {
372     scheduleTask(ClearObjectStoreOperation::create(this, objectStoreId, callbacks));
373 }
374
375 };
376
377 #endif // ENABLE(INDEXED_DATABASE)