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