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 42namespace WebCore { 43 44PassRefPtr<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 53IDBTransactionBackend::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 84IDBTransactionBackend::~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 90void 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 112void IDBTransactionBackend::abort() 113{ 114 abort(IDBDatabaseError::create(IDBDatabaseException::UnknownError, "Internal error (unknown cause)")); 115} 116 117void 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 169bool IDBTransactionBackend::isTaskQueueEmpty() const 170{ 171 return m_preemptiveTaskQueue.isEmpty() && m_taskQueue.isEmpty(); 172} 173 174bool IDBTransactionBackend::hasPendingTasks() const 175{ 176 return m_pendingPreemptiveEvents || !isTaskQueueEmpty(); 177} 178 179void IDBTransactionBackend::registerOpenCursor(IDBCursorBackend* cursor) 180{ 181 m_openCursors.add(cursor); 182} 183 184void IDBTransactionBackend::unregisterOpenCursor(IDBCursorBackend* cursor) 185{ 186 m_openCursors.remove(cursor); 187} 188 189void 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 199void 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 208void 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 273void IDBTransactionBackend::taskTimerFired(Timer<IDBTransactionBackend>&) 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 304void 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 311void IDBTransactionBackend::scheduleCreateObjectStoreOperation(const IDBObjectStoreMetadata& objectStoreMetadata) 312{ 313 scheduleTask(CreateObjectStoreOperation::create(this, objectStoreMetadata), CreateObjectStoreAbortOperation::create(this, objectStoreMetadata.id)); 314} 315 316void IDBTransactionBackend::scheduleDeleteObjectStoreOperation(const IDBObjectStoreMetadata& objectStoreMetadata) 317{ 318 scheduleTask(DeleteObjectStoreOperation::create(this, objectStoreMetadata), DeleteObjectStoreAbortOperation::create(this, objectStoreMetadata)); 319} 320 321void 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 326void IDBTransactionBackend::scheduleCreateIndexOperation(int64_t objectStoreId, const IDBIndexMetadata& indexMetadata) 327{ 328 scheduleTask(CreateIndexOperation::create(this, objectStoreId, indexMetadata), CreateIndexAbortOperation::create(this, objectStoreId, indexMetadata.id)); 329} 330 331void IDBTransactionBackend::scheduleDeleteIndexOperation(int64_t objectStoreId, const IDBIndexMetadata& indexMetadata) 332{ 333 scheduleTask(DeleteIndexOperation::create(this, objectStoreId, indexMetadata), DeleteIndexAbortOperation::create(this, objectStoreId, indexMetadata)); 334} 335 336void 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 341void 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 346void IDBTransactionBackend::scheduleSetIndexesReadyOperation(size_t indexCount) 347{ 348 scheduleTask(IDBDatabaseBackend::PreemptiveTask, SetIndexesReadyOperation::create(this, indexCount)); 349} 350 351void 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 356void 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 361void IDBTransactionBackend::scheduleDeleteRangeOperation(int64_t objectStoreId, PassRefPtr<IDBKeyRange> keyRange, PassRefPtr<IDBCallbacks> callbacks) 362{ 363 scheduleTask(DeleteRangeOperation::create(this, objectStoreId, keyRange, callbacks)); 364} 365 366void 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) 374