Title: [194649] trunk
Revision
194649
Author
beid...@apple.com
Date
2016-01-06 11:24:57 -0800 (Wed, 06 Jan 2016)

Log Message

Modern IDB: storage/indexeddb/transaction-scope-sequencing.html fails
https://bugs.webkit.org/show_bug.cgi?id=152775

Reviewed by Alex Christensen.

Source/WebCore:

No new tests (At least one failing test now passes, plus changes to another existing test).

Any transaction enqueued after a read-write transaction whose scope overlaps with
that read-write transaction cannot run until after that read-write transaction runs.

Additionally, read-only transactions were actually sometimes running even though their scopes
overlapped with a running read-write transaction.

This patch fixes both of those issues.

* Modules/indexeddb/server/UniqueIDBDatabase.cpp:
(WebCore::IDBServer::UniqueIDBDatabase::operationAndTransactionTimerFired):
(WebCore::IDBServer::UniqueIDBDatabase::takeNextRunnableTransaction):
(WebCore::IDBServer::UniqueIDBDatabase::inProgressTransactionCompleted):
* Modules/indexeddb/server/UniqueIDBDatabase.h:

LayoutTests:

In addition to enabling the previously skipped test, this also completely rewrites transaction-scheduler-4,
which covered incorrect behavior, to cover newly implemented correct behavior.

* platform/mac-wk1/TestExpectations:
* storage/indexeddb/modern/resources/transaction-scheduler-4.js: Added.
* storage/indexeddb/modern/transaction-scheduler-4-expected.txt:
* storage/indexeddb/modern/transaction-scheduler-4.html:

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (194648 => 194649)


--- trunk/LayoutTests/ChangeLog	2016-01-06 19:17:55 UTC (rev 194648)
+++ trunk/LayoutTests/ChangeLog	2016-01-06 19:24:57 UTC (rev 194649)
@@ -1,3 +1,18 @@
+2016-01-06  Brady Eidson  <beid...@apple.com>
+
+        Modern IDB: storage/indexeddb/transaction-scope-sequencing.html fails
+        https://bugs.webkit.org/show_bug.cgi?id=152775
+
+        Reviewed by Alex Christensen.
+
+        In addition to enabling the previously skipped test, this also completely rewrites transaction-scheduler-4, 
+        which covered incorrect behavior, to cover newly implemented correct behavior.
+
+        * platform/mac-wk1/TestExpectations:
+        * storage/indexeddb/modern/resources/transaction-scheduler-4.js: Added.
+        * storage/indexeddb/modern/transaction-scheduler-4-expected.txt:
+        * storage/indexeddb/modern/transaction-scheduler-4.html:
+
 2016-01-06  Zalan Bujtas  <za...@apple.com>
 
         Float with media query positioned incorrectly after window resize.

Modified: trunk/LayoutTests/platform/mac-wk1/TestExpectations (194648 => 194649)


--- trunk/LayoutTests/platform/mac-wk1/TestExpectations	2016-01-06 19:17:55 UTC (rev 194648)
+++ trunk/LayoutTests/platform/mac-wk1/TestExpectations	2016-01-06 19:24:57 UTC (rev 194649)
@@ -89,7 +89,6 @@
 storage/indexeddb/open-db-private-browsing.html [ Failure ]
 storage/indexeddb/properties-disabled-at-runtime.html [ Failure ]
 storage/indexeddb/setversion-blocked-by-versionchange-close.html [ Failure ]
-storage/indexeddb/transaction-scope-sequencing.html [ Failure ]
 
 # Relies on internals.observeGC
 storage/indexeddb/connection-leak.html [ Skip ]

Added: trunk/LayoutTests/storage/indexeddb/modern/resources/transaction-scheduler-4.js (0 => 194649)


--- trunk/LayoutTests/storage/indexeddb/modern/resources/transaction-scheduler-4.js	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/modern/resources/transaction-scheduler-4.js	2016-01-06 19:24:57 UTC (rev 194649)
@@ -0,0 +1,57 @@
+description("Makes sure that a read-only transaction scheduled after a read-write transaction with overlapping scope does not start until the read-write transaction completes");
+
+indexedDBTest(prepareDatabase, versionChangeSuccessCallback);
+
+function prepareDatabase()
+{
+    evalAndLog("connection = event.target.result;");
+    evalAndLog("objectStore = connection.createObjectStore('store');");
+}
+
+function versionChangeSuccessCallback()
+{
+    evalAndLog("transaction1 = connection.transaction('store', 'readwrite');");
+    evalAndLog("transaction2 = connection.transaction('store', 'readonly');");
+    evalAndLog("getRequest = transaction2.objectStore('store').get('foo');");
+
+    debug("Starting a series of 20 puts in the readwrite transaction to make sure the readonly transaction is deferred for some time");
+    evalAndLog("putCount = 0;");
+    evalAndLog("readWriteTransactionDone = false;");
+    evalAndLog("getRequestDone = false;");
+    var putSuccess = function() {
+        if (++putCount == 20) {
+            debug("20th put complete");
+            return;
+        }
+        
+        var newRequest = transaction1.objectStore('store').put('bar', 'foo');
+        newRequest._onsuccess_ = putSuccess;
+    };
+
+    var request = transaction1.objectStore('store').put('bar', 'foo');
+    request._onsuccess_ = putSuccess;
+
+
+
+    transaction1._oncomplete_ = function() {
+        debug("Transaction 1 (readwrite) completed with " + putCount + " puts completed.");
+        shouldBeFalse("getRequestDone");
+        shouldBe("putCount", "20");
+        readWriteTransactionDone = true;
+    }
+
+    getRequest._onsuccess_ = function() {
+        debug("Getting the value of 'foo' completed - This means the readonly transaction started. At this point " + putCount + " puts completed in the readwrite transaction.");
+        shouldBeTrue("readWriteTransactionDone");
+        shouldBe("putCount", "20");
+        getRequestDone = true;
+    }
+
+    transaction2._oncomplete_ = function() {
+        debug("Transaction 1 (readonly) completed");
+        shouldBeTrue("readWriteTransactionDone");
+        shouldBeTrue("getRequestDone");
+        shouldBe("putCount", "20");
+        finishJSTest();
+    }
+}

Modified: trunk/LayoutTests/storage/indexeddb/modern/transaction-scheduler-4-expected.txt (194648 => 194649)


--- trunk/LayoutTests/storage/indexeddb/modern/transaction-scheduler-4-expected.txt	2016-01-06 19:17:55 UTC (rev 194648)
+++ trunk/LayoutTests/storage/indexeddb/modern/transaction-scheduler-4-expected.txt	2016-01-06 19:24:57 UTC (rev 194649)
@@ -1,20 +1,34 @@
-ALERT: Upgrade needed: Old version - 0 New version - 1
-ALERT: versionchange transaction completed
-ALERT: Success opening database connection - Starting readonly transaction
-ALERT: Other objectstore read transaction get success - [object Event]
-ALERT: Other objectstore read transaction complete - [object Event]
-ALERT: Read transaction complete - [object Event]
-ALERT: Write transaction first put success
-ALERT: Second read-only transaction get success - [object Event]
-ALERT: Write transaction complete - [object Event]
-ALERT: Second read-only transaction complete - [object Event]
-ALERT: Done
-This test opens a long running read-only transaction to an object store, and then a read-write transaction to the same object store.
-The read-write transaction should be blocked while the long running read-only transaction is in progress.
-It also queues up a second read-only transaction to that same object store behind the blocked read-write transaction.
-Even though that second read-only transaction could start while the first is in progress and the read-write transaction is blocked,
-it should *not* start because doing so would risk continuing to block the read-write transaction.
-It also queues up a read-only to a different object store behind those first three transactions.
-That last read-only transaction *should* start while the read-write transaction is still blocked,
-as doing so won't risk blocking the read-write transaction further.
+Makes sure that a read-only transaction scheduled after a read-write transaction with overlapping scope does not start until the read-write transaction completes
 
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+indexedDB = self.indexedDB || self.webkitIndexedDB || self.mozIndexedDB || self.msIndexedDB || self.OIndexedDB;
+
+dbname = "transaction-scheduler-4.html"
+indexedDB.deleteDatabase(dbname)
+indexedDB.open(dbname)
+connection = event.target.result;
+objectStore = connection.createObjectStore('store');
+transaction1 = connection.transaction('store', 'readwrite');
+transaction2 = connection.transaction('store', 'readonly');
+getRequest = transaction2.objectStore('store').get('foo');
+Starting a series of 20 puts in the readwrite transaction to make sure the readonly transaction is deferred for some time
+putCount = 0;
+readWriteTransactionDone = false;
+getRequestDone = false;
+20th put complete
+Transaction 1 (readwrite) completed with 20 puts completed.
+PASS getRequestDone is false
+PASS putCount is 20
+Getting the value of 'foo' completed - This means the readonly transaction started. At this point 20 puts completed in the readwrite transaction.
+PASS readWriteTransactionDone is true
+PASS putCount is 20
+Transaction 1 (readonly) completed
+PASS readWriteTransactionDone is true
+PASS getRequestDone is true
+PASS putCount is 20
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Modified: trunk/LayoutTests/storage/indexeddb/modern/transaction-scheduler-4.html (194648 => 194649)


--- trunk/LayoutTests/storage/indexeddb/modern/transaction-scheduler-4.html	2016-01-06 19:17:55 UTC (rev 194648)
+++ trunk/LayoutTests/storage/indexeddb/modern/transaction-scheduler-4.html	2016-01-06 19:24:57 UTC (rev 194649)
@@ -1,233 +1,9 @@
-This test opens a long running read-only transaction to an object store, and then a read-write transaction to the same object store.<br>
-The read-write transaction should be blocked while the long running read-only transaction is in progress.<br>
-It also queues up a second read-only transaction to that same object store behind the blocked read-write transaction.<br>
-Even though that second read-only transaction could start while the first is in progress and the read-write transaction is blocked,<br>
-it should *not* start because doing so would risk continuing to block the read-write transaction.<br>
-It also queues up a read-only to a different object store behind those first three transactions.<br>
-That last read-only transaction *should* start while the read-write transaction is still blocked,<br>
-as doing so won't risk blocking the read-write transaction further.<br>
-<script>
-
-if (window.testRunner) {
-    testRunner.waitUntilDone();
-    testRunner.dumpAsText();
-}
-
-function done()
-{
-    alert("Done");
-    if (window.testRunner)
-        testRunner.notifyDone();
-}
-
-var createRequest = window.indexedDB.open("TransactionScheduler4Database");
-
-createRequest._onupgradeneeded_ = function(event) {
-    alert("Upgrade needed: Old version - " + event.oldVersion + " New version - " + event.newVersion);
-
-    var versionTransaction = createRequest.transaction;
-    var database = event.target.result;
-    var objectStore = database.createObjectStore("OS");
-    var request = objectStore.put("bar", "foo");
-
-    request._onerror_ = function(event) {
-        alert("first put FAILED - " + event);
-        done();
-    }
-
-    objectStore = database.createObjectStore("OtherOS");
-    request = objectStore.put("bar", "foo");
-    
-    request._onerror_ = function(event) {
-        alert("second put FAILED - " + event);
-        done();
-    }
-    
-    versionTransaction._onabort_ = function(event) {
-        alert("versionchange transaction aborted");
-        done();
-    }
-
-    versionTransaction._oncomplete_ = function(event) {
-        alert("versionchange transaction completed");
-        continueTest();
-        database.close();
-    }
-
-    versionTransaction._onerror_ = function(event) {
-        alert("versionchange transaction error'ed - " + event);
-        done();
-    }
-}
-
-var secondDatabaseConnection;
-function continueTest()
-{
-    var longRunningReadRequest = window.indexedDB.open("TransactionScheduler4Database", 1);
-    longRunningReadRequest._onsuccess_ = function(event) {
-        alert("Success opening database connection - Starting readonly transaction");
-        secondDatabaseConnection = event.target.result;
-        
-        var transaction = secondDatabaseConnection.transaction("OS", "readonly");
-        
-        transaction._onerror_ = function(event) {
-            alert("Unexpected transaction error - " + event);
-            done();
-        }
-
-        transaction._onabort_ = function(event) {
-            alert("Unexpected transaction abort - " + event);
-            done();
-        }
-
-        transaction._oncomplete_ = function(event) {
-            alert("Read transaction complete - " + event);
-        }
-        
-        firstReadTransactionLoop(transaction, true);
-    }
-    longRunningReadRequest._onerror_ = function(event) {
-        alert("Long running read request unexpected error - " + event);
-        done();
-    }
-    longRunningReadRequest._onblocked_ = function(event) {
-        alert("Long running read request unexpected blocked - " + event);
-        done();
-    }
-    longRunningReadRequest._onupgradeneeded_ = function(event) {
-        alert("Long running read request unexpected upgradeneeded - " + event);
-        done();
-    } 
-}
-
-var shouldEndReadTransaction = false;
-
-function firstReadTransactionLoop(transaction, isFirstTime)
-{
-    var objectStore = transaction.objectStore("OS");
-    var request = objectStore.get("foo");
-    
-    request._onsuccess_ = function(event) {
-        if (shouldEndReadTransaction)
-            return;
-            
-        firstReadTransactionLoop(event.target.transaction, false);
-        
-        // Now that the read transaction is open, the other transactions can be queued up.
-        if (isFirstTime)
-             startOtherTransactions()
-    }
-
-    request._onerror_ = function(event) {
-        alert("Unexpected request error - " + event);
-        done();
-    }
-}
-
-var shouldEndReadTransaction = false;
-var loggedWritePutSuccessOnce = false;
-function writeTransactionLoop(transaction)
-{
-    var objectStore = transaction.objectStore("OS");
-    var request = objectStore.put("baz", "foo");
-
-    request._onsuccess_ = function(event) {
-        if (!loggedWritePutSuccessOnce) {
-            alert("Write transaction first put success");
-            loggedWritePutSuccessOnce = true;
-        }
-        if (shouldEndReadTransaction)
-            return;
-        
-        writeTransactionLoop(transaction);
-    }
-    
-    request._onerror_ = function(event) {
-        alert("Write transaction put unexpected error - " + event);
-        done();
-    }
-}
-
-function startOtherTransactions()
-{
-    var transaction = secondDatabaseConnection.transaction("OS", "readwrite");
-
-    transaction._onerror_ = function(event) {
-        alert("Write transaction unexpected error - " + event);
-        done();
-    }
-
-    transaction._onabort_ = function(event) {
-        alert("Write transaction unexpected abort - " + event);
-        done();
-    }
-
-    transaction._oncomplete_ = function(event) {
-        alert("Write transaction complete - " + event);
-    }
-    
-    writeTransactionLoop(transaction);
-
-    // This read-only transaction should be blocked behind the read-write transaction above, 
-    // and should not start until it finishes.
-    transaction = secondDatabaseConnection.transaction("OS", "readonly");
-    transaction._onerror_ = function(event) {
-        alert("Second read-only transaction unexpected error - " + event);
-        done();
-    }
-
-    transaction._onabort_ = function(event) {
-        alert("Second read-only transaction unexpected abort - " + event);
-        done();
-    }
-
-    transaction._oncomplete_ = function(event) {
-        alert("Second read-only transaction complete - " + event);
-        done();
-    }
-    
-    var objectStore = transaction.objectStore("OS");
-    var request = objectStore.get("foo");
-
-    request._onsuccess_ = function(event) {
-        alert("Second read-only transaction get success - " + event);
-    }
-    
-    request._onerror_ = function(event) {
-        alert("Second read-only transaction put unexpected error - " + event);
-        done();
-    }
-
-    // This read-only transaction is queued behind the 3 previous transactions, but should not
-    // be blocked because its scope doesn't overlap the read-write transaction.
-    transaction = secondDatabaseConnection.transaction("OtherOS", "readonly");
-
-    transaction._onerror_ = function(event) {
-        alert("Other objectstore read transaction unexpected error - " + event);
-        done();
-    }
-
-    transaction._onabort_ = function(event) {
-        alert("Other objectstore read transaction unexpected abort - " + event);
-        done();
-    }
-
-    transaction._oncomplete_ = function(event) {
-        alert("Other objectstore read transaction complete - " + event);
-        shouldEndReadTransaction = true;
-    }
-
-    objectStore = transaction.objectStore("OtherOS");
-    request = objectStore.get("foo");
-
-    request._onsuccess_ = function(event) {
-        alert("Other objectstore read transaction get success - " + event);
-    }
-    
-    request._onerror_ = function(event) {
-        alert("Other objectstore read transaction get unexpected error - " + event);
-        done();
-    }
-}
-
-</script>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<script src=""
+</body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (194648 => 194649)


--- trunk/Source/WebCore/ChangeLog	2016-01-06 19:17:55 UTC (rev 194648)
+++ trunk/Source/WebCore/ChangeLog	2016-01-06 19:24:57 UTC (rev 194649)
@@ -1,3 +1,26 @@
+2016-01-06  Brady Eidson  <beid...@apple.com>
+
+        Modern IDB: storage/indexeddb/transaction-scope-sequencing.html fails
+        https://bugs.webkit.org/show_bug.cgi?id=152775
+
+        Reviewed by Alex Christensen.
+
+        No new tests (At least one failing test now passes, plus changes to another existing test).
+        
+        Any transaction enqueued after a read-write transaction whose scope overlaps with
+        that read-write transaction cannot run until after that read-write transaction runs.
+        
+        Additionally, read-only transactions were actually sometimes running even though their scopes
+        overlapped with a running read-write transaction.
+        
+        This patch fixes both of those issues.
+    
+        * Modules/indexeddb/server/UniqueIDBDatabase.cpp:
+        (WebCore::IDBServer::UniqueIDBDatabase::operationAndTransactionTimerFired):
+        (WebCore::IDBServer::UniqueIDBDatabase::takeNextRunnableTransaction):
+        (WebCore::IDBServer::UniqueIDBDatabase::inProgressTransactionCompleted):
+        * Modules/indexeddb/server/UniqueIDBDatabase.h:
+
 2016-01-06  Zalan Bujtas  <za...@apple.com>
 
         Float with media query positioned incorrectly after window resize.

Modified: trunk/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp (194648 => 194649)


--- trunk/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp	2016-01-06 19:17:55 UTC (rev 194648)
+++ trunk/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp	2016-01-06 19:24:57 UTC (rev 194649)
@@ -1067,8 +1067,13 @@
 
     if (transaction) {
         m_inProgressTransactions.set(transaction->info().identifier(), transaction);
-        for (auto objectStore : transaction->objectStoreIdentifiers())
+        for (auto objectStore : transaction->objectStoreIdentifiers()) {
             m_objectStoreTransactionCounts.add(objectStore);
+            if (!transaction->isReadOnly()) {
+                m_objectStoreWriteTransactions.add(objectStore);
+                ASSERT(m_objectStoreTransactionCounts.count(objectStore) == 1);
+            }
+        }
 
         activateTransactionInBackingStore(*transaction);
 
@@ -1125,26 +1130,33 @@
     Deque<RefPtr<UniqueIDBDatabaseTransaction>> deferredTransactions;
     RefPtr<UniqueIDBDatabaseTransaction> currentTransaction;
 
+    HashSet<uint64_t> deferredReadWriteScopes;
+
     while (!m_pendingTransactions.isEmpty()) {
         currentTransaction = m_pendingTransactions.takeFirst();
 
         switch (currentTransaction->info().mode()) {
-        case IndexedDB::TransactionMode::ReadOnly:
-            // If there are any deferred transactions, the first one is a read-write transaction we need to unblock.
-            // Therefore, skip this read-only transaction if its scope overlaps with that read-write transaction.
-            if (!deferredTransactions.isEmpty()) {
-                ASSERT(deferredTransactions.first()->info().mode() == IndexedDB::TransactionMode::ReadWrite);
-                if (scopesOverlap(deferredTransactions.first()->objectStoreIdentifiers(), currentTransaction->objectStoreIdentifiers()))
-                    deferredTransactions.append(WTFMove(currentTransaction));
-            }
+        case IndexedDB::TransactionMode::ReadOnly: {
+            bool hasOverlappingScopes = scopesOverlap(deferredReadWriteScopes, currentTransaction->objectStoreIdentifiers());
+            hasOverlappingScopes |= scopesOverlap(m_objectStoreWriteTransactions, currentTransaction->objectStoreIdentifiers());
 
+            if (hasOverlappingScopes)
+                deferredTransactions.append(WTFMove(currentTransaction));
+
             break;
-        case IndexedDB::TransactionMode::ReadWrite:
-            // If this read-write transaction's scope overlaps with running transactions, it must be deferred.
-            if (scopesOverlap(m_objectStoreTransactionCounts, currentTransaction->objectStoreIdentifiers()))
+        }
+        case IndexedDB::TransactionMode::ReadWrite: {
+            bool hasOverlappingScopes = scopesOverlap(m_objectStoreTransactionCounts, currentTransaction->objectStoreIdentifiers());
+            hasOverlappingScopes |= scopesOverlap(deferredReadWriteScopes, currentTransaction->objectStoreIdentifiers());
+
+            if (hasOverlappingScopes) {
+                for (auto objectStore : currentTransaction->objectStoreIdentifiers())
+                    deferredReadWriteScopes.add(objectStore);
                 deferredTransactions.append(WTFMove(currentTransaction));
+            }
 
             break;
+        }
         case IndexedDB::TransactionMode::VersionChange:
             // Version change transactions should never be scheduled in the traditional manner.
             RELEASE_ASSERT_NOT_REACHED();
@@ -1171,8 +1183,13 @@
     auto transaction = m_inProgressTransactions.take(transactionIdentifier);
     ASSERT(transaction);
 
-    for (auto objectStore : transaction->objectStoreIdentifiers())
+    for (auto objectStore : transaction->objectStoreIdentifiers()) {
+        if (!transaction->isReadOnly()) {
+            m_objectStoreWriteTransactions.remove(objectStore);
+            ASSERT(m_objectStoreTransactionCounts.count(objectStore) == 1);
+        }
         m_objectStoreTransactionCounts.remove(objectStore);
+    }
 
     if (!transaction->databaseConnection().hasNonFinishedTransactions())
         m_closePendingDatabaseConnections.remove(&transaction->databaseConnection());

Modified: trunk/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.h (194648 => 194649)


--- trunk/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.h	2016-01-06 19:17:55 UTC (rev 194648)
+++ trunk/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.h	2016-01-06 19:24:57 UTC (rev 194649)
@@ -203,11 +203,10 @@
     Deque<RefPtr<UniqueIDBDatabaseTransaction>> m_pendingTransactions;
     HashMap<IDBResourceIdentifier, RefPtr<UniqueIDBDatabaseTransaction>> m_inProgressTransactions;
 
-    // The key into this set is the object store ID.
-    // The set counts how many transactions are open to the given object store.
-    // This helps make sure opening narrowly scoped transactions (one or two object stores)
-    // doesn't continuously block widely scoped write transactions.
+    // The keys into these sets are the object store ID.
+    // These sets help to decide which transactions can be started and which must be deferred.
     HashCountedSet<uint64_t> m_objectStoreTransactionCounts;
+    HashSet<uint64_t> m_objectStoreWriteTransactions;
 
     bool m_deletePending { false };
     bool m_deleteBackingStoreInProgress { false };
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to