diff --git a/dom/indexedDB/TransactionThreadPool.cpp b/dom/indexedDB/TransactionThreadPool.cpp index ec282872a59d..b421e532dd93 100644 --- a/dom/indexedDB/TransactionThreadPool.cpp +++ b/dom/indexedDB/TransactionThreadPool.cpp @@ -61,36 +61,22 @@ const PRUint32 kIdleThreadTimeoutMs = 30000; TransactionThreadPool* gInstance = nsnull; bool gShutdown = false; -inline -nsresult -CheckOverlapAndMergeObjectStores(nsTArray& aLockedStores, - const nsTArray& aObjectStores, - bool aShouldMerge, - bool* aStoresOverlap) -{ - PRUint32 length = aObjectStores.Length(); - - bool overlap = false; - - for (PRUint32 index = 0; index < length; index++) { - const nsString& storeName = aObjectStores[index]; - if (aLockedStores.Contains(storeName)) { - overlap = true; - } - else if (aShouldMerge && !aLockedStores.AppendElement(storeName)) { - NS_WARNING("Out of memory!"); - return NS_ERROR_OUT_OF_MEMORY; - } - } - - *aStoresOverlap = overlap; - return NS_OK; -} - } // anonymous namespace BEGIN_INDEXEDDB_NAMESPACE +struct QueuedDispatchInfo +{ + QueuedDispatchInfo() + : finish(false) + { } + + nsRefPtr transaction; + nsCOMPtr runnable; + nsCOMPtr finishRunnable; + bool finish; +}; + class FinishTransactionRunnable : public nsIRunnable { public: @@ -254,34 +240,27 @@ TransactionThreadPool::FinishTransaction(IDBTransaction* aTransaction) "Didn't find the transaction we were looking for!"); } - // We need to rebuild the locked object store list if dbTransactionInfo is - // still alive. - if (count > 1) { - dbTransactionInfo->storesWriting.Clear(); - dbTransactionInfo->storesReading.Clear(); - } - // Try to dispatch all the queued transactions again. nsTArray queuedDispatch; queuedDispatch.SwapElements(mDelayedDispatchQueue); count = queuedDispatch.Length(); for (PRUint32 index = 0; index < count; index++) { - if (NS_FAILED(Dispatch(queuedDispatch[index]))) { + QueuedDispatchInfo& info = queuedDispatch[index]; + if (NS_FAILED(Dispatch(info.transaction, info.runnable, info.finish, + info.finishRunnable))) { NS_WARNING("Dispatch failed!"); } } } -nsresult +bool TransactionThreadPool::TransactionCanRun(IDBTransaction* aTransaction, - bool* aCanRun, - TransactionQueue** aExistingQueue) + TransactionQueue** aQueue) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(aTransaction, "Null pointer!"); - NS_ASSERTION(aCanRun, "Null pointer!"); - NS_ASSERTION(aExistingQueue, "Null pointer!"); + NS_ASSERTION(aQueue, "Null pointer!"); const PRUint32 databaseId = aTransaction->mDatabase->Id(); const nsTArray& objectStoreNames = aTransaction->mObjectStoreNames; @@ -291,62 +270,123 @@ TransactionThreadPool::TransactionCanRun(IDBTransaction* aTransaction, DatabaseTransactionInfo* dbTransactionInfo; if (!mTransactionsInProgress.Get(databaseId, &dbTransactionInfo)) { // First transaction for this database, fine to run. - *aCanRun = true; - *aExistingQueue = nsnull; - return NS_OK; + *aQueue = nsnull; + return true; } nsTArray& transactionsInProgress = dbTransactionInfo->transactions; PRUint32 transactionCount = transactionsInProgress.Length(); - NS_ASSERTION(transactionCount, "Should never be 0!"); if (mode == IDBTransaction::FULL_LOCK) { - dbTransactionInfo->lockPending = true; - } + switch (transactionCount) { + case 0: { + *aQueue = nsnull; + return true; + } - for (PRUint32 index = 0; index < transactionCount; index++) { - // See if this transaction is in out list of current transactions. - const TransactionInfo& info = transactionsInProgress[index]; - if (info.transaction == aTransaction) { - *aCanRun = true; - *aExistingQueue = info.queue; - return NS_OK; + case 1: { + if (transactionsInProgress[0].transaction == aTransaction) { + *aQueue = transactionsInProgress[0].queue; + return true; + } + return false; + } + + default: { + dbTransactionInfo->lockPending = true; + return false; + } } } - if (dbTransactionInfo->locked || dbTransactionInfo->lockPending) { - *aCanRun = false; - *aExistingQueue = nsnull; - return NS_OK; + bool locked = dbTransactionInfo->locked || dbTransactionInfo->lockPending; + + bool mayRun = true; + + // Another transaction for this database is already running. See if we can + // run at the same time. + for (PRUint32 transactionIndex = 0; + transactionIndex < transactionCount; + transactionIndex++) { + TransactionInfo& transactionInfo = transactionsInProgress[transactionIndex]; + + if (transactionInfo.transaction == aTransaction) { + // Same transaction, already running. + *aQueue = transactionInfo.queue; + return true; + } + + if (locked) { + // Full lock in place or requested, no need to check objectStores. + continue; + } + + // Not our transaction. See if the objectStores overlap. + nsTArray& objectStoreInfoArray = + transactionInfo.objectStoreInfo; + + PRUint32 objectStoreCount = objectStoreInfoArray.Length(); + for (PRUint32 objectStoreIndex = 0; + objectStoreIndex < objectStoreCount; + objectStoreIndex++) { + TransactionObjectStoreInfo& objectStoreInfo = + objectStoreInfoArray[objectStoreIndex]; + + if (objectStoreNames.Contains(objectStoreInfo.objectStoreName)) { + // Overlapping name, see if the modes are compatible. + switch (mode) { + case nsIIDBTransaction::READ_WRITE: { + // Someone else is reading or writing to this table, we can't + // run now. Mark that we're waiting for it though. + objectStoreInfo.writerWaiting = true; + + // We need to mark all overlapping transactions, not just the first + // one we find. Set the retval to false here but don't return until + // we've gone through the rest of the open transactions. + mayRun = false; + } break; + + case nsIIDBTransaction::READ_ONLY: { + if (objectStoreInfo.writing || objectStoreInfo.writerWaiting) { + // Someone else is writing to this table, we can't run now. + return false; + } + } break; + + case nsIIDBTransaction::SNAPSHOT_READ: { + NS_NOTYETIMPLEMENTED("Not implemented!"); + } break; + + default: { + NS_NOTREACHED("Should never get here!"); + } + } + } + + // Continue on to the next TransactionObjectStoreInfo. + } + + // Continue on to the next TransactionInfo. } - bool writeOverlap; - nsresult rv = - CheckOverlapAndMergeObjectStores(dbTransactionInfo->storesWriting, - objectStoreNames, - mode == nsIIDBTransaction::READ_WRITE, - &writeOverlap); - NS_ENSURE_SUCCESS(rv, rv); - - bool readOverlap; - rv = CheckOverlapAndMergeObjectStores(dbTransactionInfo->storesReading, - objectStoreNames, - mode == nsIIDBTransaction::READ_ONLY, - &readOverlap); - NS_ENSURE_SUCCESS(rv, rv); - - if (writeOverlap || - (readOverlap && mode == nsIIDBTransaction::READ_WRITE)) { - *aCanRun = false; - *aExistingQueue = nsnull; - return NS_OK; + if (locked) { + // If we got here then this is a new transaction and we won't allow it to + // start. + return false; } - *aCanRun = true; - *aExistingQueue = nsnull; - return NS_OK; + if (!mayRun) { + // If we got here then there are conflicting transactions and we can't run + // yet. + return false; + } + + // If we got here then there are no conflicting transactions and we should + // be fine to run. + *aQueue = nsnull; + return true; } nsresult @@ -359,12 +399,8 @@ TransactionThreadPool::Dispatch(IDBTransaction* aTransaction, NS_ASSERTION(aTransaction, "Null pointer!"); NS_ASSERTION(aRunnable, "Null pointer!"); - bool canRun; TransactionQueue* existingQueue; - nsresult rv = TransactionCanRun(aTransaction, &canRun, &existingQueue); - NS_ENSURE_SUCCESS(rv, rv); - - if (!canRun) { + if (!TransactionCanRun(aTransaction, &existingQueue)) { QueuedDispatchInfo* info = mDelayedDispatchQueue.AppendElement(); NS_ENSURE_TRUE(info, NS_ERROR_OUT_OF_MEMORY); @@ -407,18 +443,6 @@ TransactionThreadPool::Dispatch(IDBTransaction* aTransaction, dbTransactionInfo->locked = true; } - const nsTArray& objectStoreNames = aTransaction->mObjectStoreNames; - - nsTArray& storesInUse = - aTransaction->mMode == nsIIDBTransaction::READ_WRITE ? - dbTransactionInfo->storesWriting : - dbTransactionInfo->storesReading; - - if (!storesInUse.AppendElements(objectStoreNames)) { - NS_WARNING("Out of memory!"); - return NS_ERROR_OUT_OF_MEMORY; - } - nsTArray& transactionInfoArray = dbTransactionInfo->transactions; @@ -432,15 +456,21 @@ TransactionThreadPool::Dispatch(IDBTransaction* aTransaction, } transactionInfo->mode = aTransaction->mMode; - if (!transactionInfo->objectStoreNames.AppendElements(objectStoreNames)) { - NS_WARNING("Out of memory!"); - return NS_ERROR_OUT_OF_MEMORY; + const nsTArray& objectStoreNames = aTransaction->mObjectStoreNames; + PRUint32 count = objectStoreNames.Length(); + for (PRUint32 index = 0; index < count; index++) { + TransactionObjectStoreInfo* info = + transactionInfo->objectStoreInfo.AppendElement(); + NS_ENSURE_TRUE(info, NS_ERROR_OUT_OF_MEMORY); + + info->objectStoreName = objectStoreNames[index]; + info->writing = transactionInfo->mode == nsIIDBTransaction::READ_WRITE; } if (autoDBTransactionInfo) { if (!mTransactionsInProgress.Put(databaseId, autoDBTransactionInfo)) { - NS_WARNING("Failed to put!"); - return NS_ERROR_OUT_OF_MEMORY; + NS_ERROR("Failed to put!"); + return NS_ERROR_FAILURE; } autoDBTransactionInfo.forget(); } diff --git a/dom/indexedDB/TransactionThreadPool.h b/dom/indexedDB/TransactionThreadPool.h index 47f017dbbb50..26b49d48bc32 100644 --- a/dom/indexedDB/TransactionThreadPool.h +++ b/dom/indexedDB/TransactionThreadPool.h @@ -101,6 +101,17 @@ protected: bool mShouldFinish; }; + struct TransactionObjectStoreInfo + { + TransactionObjectStoreInfo() + : writing(false), writerWaiting(false) + { } + + nsString objectStoreName; + bool writing; + bool writerWaiting; + }; + struct TransactionInfo { TransactionInfo() @@ -109,7 +120,7 @@ protected: nsRefPtr transaction; nsRefPtr queue; - nsTArray objectStoreNames; + nsTArray objectStoreInfo; PRUint16 mode; }; @@ -122,20 +133,6 @@ protected: bool locked; bool lockPending; nsTArray transactions; - nsTArray storesReading; - nsTArray storesWriting; - }; - - struct QueuedDispatchInfo - { - QueuedDispatchInfo() - : finish(false) - { } - - nsRefPtr transaction; - nsCOMPtr runnable; - nsCOMPtr finishRunnable; - bool finish; }; TransactionThreadPool(); @@ -146,15 +143,8 @@ protected: void FinishTransaction(IDBTransaction* aTransaction); - nsresult TransactionCanRun(IDBTransaction* aTransaction, - bool* aCanRun, - TransactionQueue** aExistingQueue); - - nsresult Dispatch(const QueuedDispatchInfo& aInfo) - { - return Dispatch(aInfo.transaction, aInfo.runnable, aInfo.finish, - aInfo.finishRunnable); - } + bool TransactionCanRun(IDBTransaction* aTransaction, + TransactionQueue** aQueue); nsCOMPtr mThreadPool; diff --git a/dom/indexedDB/test/test_overlapping_transactions.html b/dom/indexedDB/test/test_overlapping_transactions.html index 8ff663e4c767..7700263b3b07 100644 --- a/dom/indexedDB/test/test_overlapping_transactions.html +++ b/dom/indexedDB/test/test_overlapping_transactions.html @@ -49,16 +49,7 @@ .add({}); request.onerror = errorHandler; request.onsuccess = function(event) { - is(stepNumber, 1, "This callback came first"); - stepNumber++; - } - - request = db.transaction(["foo"], READ_WRITE) - .objectStore("foo") - .add({}); - request.onerror = errorHandler; - request.onsuccess = function(event) { - is(stepNumber, 2, "This callback came second"); + ok(stepNumber == 1 || stepNumber == 2, "This callback came before 3"); stepNumber++; } @@ -67,17 +58,9 @@ .add({}); request.onerror = errorHandler; request.onsuccess = function(event) { - is(stepNumber, 3, "This callback came third"); - stepNumber++; - } - - request = db.transaction(["foo", "bar"], READ_WRITE) - .objectStore("bar") - .add({}); - request.onerror = errorHandler; - request.onsuccess = function(event) { - is(stepNumber, 4, "This callback came fourth"); + is(stepNumber, 3, "This callback came in at 3"); stepNumber++; + event.transaction.oncomplete = grabEventAndContinueHandler; } request = db.transaction(["bar"], READ_WRITE) @@ -85,15 +68,14 @@ .add({}); request.onerror = errorHandler; request.onsuccess = function(event) { - is(stepNumber, 5, "This callback came fifth"); + ok(stepNumber == 1 || stepNumber == 2, "This callback came before 3"); stepNumber++; - event.transaction.oncomplete = grabEventAndContinueHandler; } stepNumber++; yield; - is(stepNumber, 6, "All callbacks received"); + is(stepNumber, 4, "All callbacks received"); } finishTest();