IndexedDB: Allow multiple transactions to interleave request execution
authorjsbell@chromium.org <jsbell@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 6 Dec 2012 01:30:35 +0000 (01:30 +0000)
committerjsbell@chromium.org <jsbell@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 6 Dec 2012 01:30:35 +0000 (01:30 +0000)
https://bugs.webkit.org/show_bug.cgi?id=97570

Reviewed by Tony Chang.

Source/WebCore:

Implement spec logic for allowing read-only transactions, and read-write transactions
with non-overlapping scopes, to run concurrently. Transactions all still run in the
same thread with tasks triggered via timers, so tasks and the underlying database
operations are interleaved rather than truly parallelized.

Within IDBTransactionCoordinator, rename started->queued, running->started to match
spec terminology and clear up confusion.

Test: storage/indexeddb/transaction-coordination-across-databases.html
      storage/indexeddb/transaction-coordination-within-database.html
      storage/indexeddb/transaction-readwrite-exclusive.html
      storage/indexeddb/transaction-scope-sequencing.html
      storage/indexeddb/transaction-starvation.html

* Modules/indexeddb/IDBDatabaseBackendImpl.cpp: Use IDBTransaction::Mode enum
(WebCore::IDBDatabaseBackendImpl::createTransaction):
* Modules/indexeddb/IDBDatabaseBackendImpl.h: Ditto.
(IDBDatabaseBackendImpl):
* Modules/indexeddb/IDBDatabaseBackendInterface.h: Ditto.
(IDBDatabaseBackendInterface):
* Modules/indexeddb/IDBTransactionBackendImpl.cpp: Convert scope as a HashSet for
fast intersecting.
(WebCore::IDBTransactionBackendImpl::create):
(WebCore::IDBTransactionBackendImpl::IDBTransactionBackendImpl):
* Modules/indexeddb/IDBTransactionBackendImpl.h:
(IDBTransactionBackendImpl):
(WebCore::IDBTransactionBackendImpl::mode):
(WebCore::IDBTransactionBackendImpl::scope):
* Modules/indexeddb/IDBTransactionCoordinator.cpp: Spec logic goes here.
(WebCore::IDBTransactionCoordinator::processStartedTransactions): Extend this
method to test all plausibly runnable transactions.
(WebCore):
(WebCore::IDBTransactionCoordinator::canRunTransaction): Test to see if one
particular transaction can be run.
(WebCore::IDBTransactionCoordinator::doScopesOverlap): Do a quick intersection
test between transaction scopes.
* Modules/indexeddb/IDBTransactionCoordinator.h:
(IDBTransactionCoordinator):

Source/WebKit/chromium:

Map to IDBTransaction::Mode enum as appropriate.

* src/IDBDatabaseBackendProxy.cpp:
(WebKit::IDBDatabaseBackendProxy::createTransaction):
* src/IDBDatabaseBackendProxy.h:
(IDBDatabaseBackendProxy):
* src/WebIDBDatabaseImpl.cpp:
(WebKit::WebIDBDatabaseImpl::createTransaction):
 * tests/IDBDatabaseBackendTest.cpp:

LayoutTests:

New tests for parallel transactions, update existing tests that relied on sequential execution.

* storage/indexeddb/database-close-expected.txt:
* storage/indexeddb/mozilla/clear-expected.txt:
* storage/indexeddb/mozilla/resources/clear.js: Split up transaction steps.
* storage/indexeddb/resources/database-close.js: Don't log on individual transaction completion.
* storage/indexeddb/resources/transaction-coordination-within-database.js: Added.
* storage/indexeddb/resources/transaction-scope-sequencing.js: Added.
* storage/indexeddb/resources/transaction-starvation.js: Added.
* storage/indexeddb/transaction-coordination-within-database-expected.txt: Added.
* storage/indexeddb/transaction-coordination-within-database.html: Added.
* storage/indexeddb/transaction-scope-sequencing-expected.txt: Added.
* storage/indexeddb/transaction-scope-sequencing.html: Added.
* storage/indexeddb/transaction-starvation-expected.txt: Added.
* storage/indexeddb/transaction-starvation.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@136782 268f45cc-cd09-0410-ab3c-d52691b4dbfc

27 files changed:
LayoutTests/ChangeLog
LayoutTests/storage/indexeddb/database-close-expected.txt
LayoutTests/storage/indexeddb/mozilla/clear-expected.txt
LayoutTests/storage/indexeddb/mozilla/resources/clear.js
LayoutTests/storage/indexeddb/resources/database-close.js
LayoutTests/storage/indexeddb/resources/transaction-coordination-within-database.js [new file with mode: 0644]
LayoutTests/storage/indexeddb/resources/transaction-scope-sequencing.js [new file with mode: 0644]
LayoutTests/storage/indexeddb/resources/transaction-starvation.js [new file with mode: 0644]
LayoutTests/storage/indexeddb/transaction-coordination-within-database-expected.txt [new file with mode: 0644]
LayoutTests/storage/indexeddb/transaction-coordination-within-database.html [new file with mode: 0644]
LayoutTests/storage/indexeddb/transaction-scope-sequencing-expected.txt [new file with mode: 0644]
LayoutTests/storage/indexeddb/transaction-scope-sequencing.html [new file with mode: 0644]
LayoutTests/storage/indexeddb/transaction-starvation-expected.txt [new file with mode: 0644]
LayoutTests/storage/indexeddb/transaction-starvation.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Modules/indexeddb/IDBDatabaseBackendImpl.cpp
Source/WebCore/Modules/indexeddb/IDBDatabaseBackendImpl.h
Source/WebCore/Modules/indexeddb/IDBDatabaseBackendInterface.h
Source/WebCore/Modules/indexeddb/IDBTransactionBackendImpl.cpp
Source/WebCore/Modules/indexeddb/IDBTransactionBackendImpl.h
Source/WebCore/Modules/indexeddb/IDBTransactionCoordinator.cpp
Source/WebCore/Modules/indexeddb/IDBTransactionCoordinator.h
Source/WebKit/chromium/ChangeLog
Source/WebKit/chromium/src/IDBDatabaseBackendProxy.cpp
Source/WebKit/chromium/src/IDBDatabaseBackendProxy.h
Source/WebKit/chromium/src/WebIDBDatabaseImpl.cpp
Source/WebKit/chromium/tests/IDBDatabaseBackendTest.cpp

index 5ebf902..b3754a4 100644 (file)
@@ -1,3 +1,26 @@
+2012-12-05  Joshua Bell  <jsbell@chromium.org>
+
+        IndexedDB: Allow multiple transactions to interleave request execution
+        https://bugs.webkit.org/show_bug.cgi?id=97570
+
+        Reviewed by Tony Chang.
+
+        New tests for parallel transactions, update existing tests that relied on sequential execution.
+
+        * storage/indexeddb/database-close-expected.txt:
+        * storage/indexeddb/mozilla/clear-expected.txt:
+        * storage/indexeddb/mozilla/resources/clear.js: Split up transaction steps.
+        * storage/indexeddb/resources/database-close.js: Don't log on individual transaction completion.
+        * storage/indexeddb/resources/transaction-coordination-within-database.js: Added.
+        * storage/indexeddb/resources/transaction-scope-sequencing.js: Added.
+        * storage/indexeddb/resources/transaction-starvation.js: Added.
+        * storage/indexeddb/transaction-coordination-within-database-expected.txt: Added.
+        * storage/indexeddb/transaction-coordination-within-database.html: Added.
+        * storage/indexeddb/transaction-scope-sequencing-expected.txt: Added.
+        * storage/indexeddb/transaction-scope-sequencing.html: Added.
+        * storage/indexeddb/transaction-starvation-expected.txt: Added.
+        * storage/indexeddb/transaction-starvation.html: Added.
+
 2012-12-05  Dan Bernstein  <mitz@apple.com>
 
         Text decorations are rotated when text-combine takes effect
index a3079fa..60281ed 100644 (file)
@@ -39,11 +39,9 @@ indexedDB.deleteDatabase(dbname)
 
 transaction #1 request successful
 PASS event.target.result is "value1"
-awaiting_transaction_count -= 1
 
 transaction #2 request successful
 PASS event.target.result is "value2"
-awaiting_transaction_count -= 1
 
 All transactions completed - database deletes should now be unblocked.
 
index efa0e1a..505fe75 100644 (file)
@@ -14,7 +14,8 @@ Expecting exception from db.transaction('foo').objectStore('foo').clear();
 PASS Exception was thrown.
 PASS code is 0
 PASS ename is 'ReadOnlyError'
-db.transaction('foo', 'readwrite').objectStore('foo').clear();
+db.transaction('foo', 'readwrite')
+transaction.objectStore('foo').clear();
 request = db.transaction('foo').objectStore('foo').openCursor();
 cursor = request.result;
 PASS cursor is null
index c82faf2..79f3b78 100644 (file)
@@ -24,7 +24,14 @@ function prepareDatabase()
 function clear()
 {
     evalAndExpectException("db.transaction('foo').objectStore('foo').clear();", "0", "'ReadOnlyError'");
-    evalAndLog("db.transaction('foo', 'readwrite').objectStore('foo').clear();");
+    transaction = evalAndLog("db.transaction('foo', 'readwrite')");
+    evalAndLog("transaction.objectStore('foo').clear();");
+    transaction.oncomplete = cleared;
+    transaction.onabort = unexpectedAbortCallback;
+}
+
+function cleared()
+{
     request = evalAndLog("request = db.transaction('foo').objectStore('foo').openCursor();");
     request.onsuccess = areWeClearYet;
     request.onerror = unexpectedErrorCallback;
index 691e55b..46dba30 100644 (file)
@@ -35,7 +35,7 @@ function testClose()
     debug("Step 2: Wait for all transactions created using connection to complete. Once they are complete, connection is closed.");
     evalAndLog("awaiting_transaction_count = 2");
     function transactionCompleted() {
-        evalAndLog("awaiting_transaction_count -= 1");
+        awaiting_transaction_count -= 1;
 
         if (awaiting_transaction_count == 0) {
             debug("");
diff --git a/LayoutTests/storage/indexeddb/resources/transaction-coordination-within-database.js b/LayoutTests/storage/indexeddb/resources/transaction-coordination-within-database.js
new file mode 100644 (file)
index 0000000..d6f0359
--- /dev/null
@@ -0,0 +1,77 @@
+if (this.importScripts) {
+    importScripts('../../../fast/js/resources/js-test-pre.js');
+    importScripts('shared.js');
+}
+
+description("Check that read-only transactions within a database can run in parallel.");
+
+indexedDBTest(prepareDatabase, runParallelTransactions);
+
+function prepareDatabase(evt)
+{
+    preamble(evt);
+    evalAndLog("db = event.target.result");
+    evalAndLog("store = db.createObjectStore('store')");
+    evalAndLog("store.put('value', 'key')");
+}
+
+function runParallelTransactions(evt)
+{
+    preamble(evt);
+    evalAndLog("db = event.target.result");
+    debug("");
+    evalAndLog("transaction1 = db.transaction('store', 'readonly')");
+    transaction1.onabort = unexpectedAbortCallback;
+    transaction1.oncomplete = onTransactionComplete;
+    evalAndLog("transaction2 = db.transaction('store', 'readonly')");
+    transaction1.onabort = unexpectedAbortCallback;
+    transaction2.oncomplete = onTransactionComplete;
+
+    evalAndLog("transaction1GetSuccess = false");
+    evalAndLog("transaction2GetSuccess = false");
+
+    debug("Keep both transactions alive until each has reported at least one successful operation");
+
+    function doTransaction1Get() {
+        // NOTE: No logging since execution order is not deterministic.
+        request = transaction1.objectStore('store').get('key');
+        request.onerror = unexpectedErrorCallback;
+        request.onsuccess = function() {
+            transaction1GetSuccess = true;
+            if (!transaction2GetSuccess)
+                doTransaction1Get();
+        };
+    }
+
+    function doTransaction2Get() {
+        // NOTE: No logging since execution order is not deterministic.
+        request = transaction2.objectStore('store').get('key');
+        request.onerror = unexpectedErrorCallback;
+        request.onsuccess = function() {
+            transaction2GetSuccess = true;
+            if (!transaction1GetSuccess)
+                doTransaction2Get();
+        };
+    }
+
+    doTransaction1Get();
+    doTransaction2Get();
+}
+
+transactionCompletionCount = 0;
+function onTransactionComplete(evt)
+{
+    preamble(evt);
+
+    ++transactionCompletionCount;
+    if (transactionCompletionCount < 2) {
+        debug("first transaction complete, still waiting...");
+        return;
+    }
+
+    shouldBeTrue("transaction1GetSuccess");
+    shouldBeTrue("transaction2GetSuccess");
+
+    evalAndLog("db.close()");
+    finishJSTest();
+}
diff --git a/LayoutTests/storage/indexeddb/resources/transaction-scope-sequencing.js b/LayoutTests/storage/indexeddb/resources/transaction-scope-sequencing.js
new file mode 100644 (file)
index 0000000..72f8a09
--- /dev/null
@@ -0,0 +1,81 @@
+if (this.importScripts) {
+    importScripts('../../../fast/js/resources/js-test-pre.js');
+    importScripts('shared.js');
+}
+
+description("Check that scope restrictions on read-write transactions are enforced.");
+
+indexedDBTest(prepareDatabase, runTransactions);
+
+function prepareDatabase(evt)
+{
+    preamble(evt);
+    evalAndLog("db = event.target.result");
+    evalAndLog("db.createObjectStore('a')");
+    evalAndLog("db.createObjectStore('b')");
+    evalAndLog("db.createObjectStore('c')");
+}
+
+function runTransactions(evt)
+{
+    preamble(evt);
+    evalAndLog("db = event.target.result");
+    debug("");
+
+    evalAndLog("transaction1 = db.transaction(['a'], 'readwrite')");
+    evalAndLog("transaction1Started = false");
+    evalAndLog("transaction1Complete = false");
+    request = evalAndLog("transaction1.objectStore('a').get(0)");
+    request.onerror = unexpectedErrorCallback;
+    request.onsuccess = function() {
+        debug("");
+        evalAndLog("transaction1Started = true");
+    };
+    transaction1.onabort = unexpectedAbortCallback;
+    transaction1.oncomplete = function() {
+        debug("");
+        evalAndLog("transaction1Complete = true");
+        shouldBeFalse("transaction2Started");
+        shouldBeFalse("transaction3Started");
+    };
+
+    debug("");
+    debug("transaction2 overlaps with transaction1, so must wait until transaction1 completes");
+    evalAndLog("transaction2 = db.transaction(['a', 'b'], 'readwrite')");
+    evalAndLog("transaction2Started = false");
+    evalAndLog("transaction2Complete = false");
+    request = evalAndLog("transaction2.objectStore('a').get(0)");
+    request.onerror = unexpectedErrorCallback;
+    request.onsuccess = function() {
+        debug("");
+        shouldBeTrue("transaction1Complete");
+        evalAndLog("transaction2Started = true");
+    };
+    transaction2.onabort = unexpectedAbortCallback;
+    transaction2.oncomplete = function() {
+        debug("");
+        evalAndLog("transaction2Complete = true");
+        shouldBeFalse("transaction3Started");
+    };
+
+    debug("");
+    debug("transaction3 overlaps with transaction2, so must wait until transaction2 completes");
+    debug("even though it does not overlap with transaction1");
+    evalAndLog("transaction3 = db.transaction(['b', 'c'], 'readwrite')");
+    evalAndLog("transaction3Started = false");
+    evalAndLog("transaction3Complete = false");
+    request = evalAndLog("transaction3.objectStore('b').get(0)");
+    request.onerror = unexpectedErrorCallback;
+    request.onsuccess = function() {
+        debug("");
+        shouldBeTrue("transaction1Complete");
+        shouldBeTrue("transaction2Complete");
+        evalAndLog("transaction3Started = true");
+    };
+    transaction3.onabort = unexpectedAbortCallback;
+    transaction3.oncomplete = function() {
+        debug("");
+        evalAndLog("transaction3Complete = true");
+        finishJSTest();
+    };
+}
diff --git a/LayoutTests/storage/indexeddb/resources/transaction-starvation.js b/LayoutTests/storage/indexeddb/resources/transaction-starvation.js
new file mode 100644 (file)
index 0000000..8b2f97e
--- /dev/null
@@ -0,0 +1,63 @@
+if (this.importScripts) {
+    importScripts('../../../fast/js/resources/js-test-pre.js');
+    importScripts('shared.js');
+}
+
+description("Check that read-only transactions don't starve read-write transactions.");
+
+indexedDBTest(prepareDatabase, runTransactions);
+
+function prepareDatabase(evt)
+{
+    preamble(evt);
+    evalAndLog("db = event.target.result");
+    evalAndLog("db.createObjectStore('store')");
+}
+
+function runTransactions(evt)
+{
+    preamble(evt);
+    evalAndLog("db = event.target.result");
+    debug("");
+
+    evalAndLog("readWriteTransactionStarted = false");
+    evalAndLog("readWriteTransactionComplete = false");
+
+    startReadOnlyTransaction();
+}
+
+
+function startReadOnlyTransaction()
+{
+    preamble();
+    evalAndLog("transaction = db.transaction('store', 'readonly')");
+    transaction.onabort = unexpectedAbortCallback;
+    evalAndLog("store = transaction.objectStore('store')");
+    debug("Keep the transaction alive with an endless series of gets");
+
+    function doGet() {
+        request = store.get(0);
+        request.onsuccess = function() {
+            if (!readWriteTransactionStarted)
+                startReadWriteTransaction();
+
+            if (!readWriteTransactionComplete)
+                doGet();
+        };
+    }
+    doGet();
+}
+
+function startReadWriteTransaction()
+{
+    preamble();
+    evalAndLog("transaction = db.transaction('store', 'readwrite')");
+    transaction.onabort = unexpectedAbortCallback;
+    evalAndLog("readWriteTransactionStarted = true");
+    transaction.oncomplete = function readWriteTransactionComplete() {
+        preamble();
+        testPassed("Transaction wasn't starved");
+        evalAndLog("readWriteTransactionComplete = true");
+        finishJSTest();
+    };
+}
\ No newline at end of file
diff --git a/LayoutTests/storage/indexeddb/transaction-coordination-within-database-expected.txt b/LayoutTests/storage/indexeddb/transaction-coordination-within-database-expected.txt
new file mode 100644 (file)
index 0000000..2ff3ffc
--- /dev/null
@@ -0,0 +1,36 @@
+Check that read-only transactions within a database can run in parallel.
+
+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-coordination-within-database.html"
+indexedDB.deleteDatabase(dbname)
+indexedDB.open(dbname)
+
+prepareDatabase():
+db = event.target.result
+store = db.createObjectStore('store')
+store.put('value', 'key')
+
+runParallelTransactions():
+db = event.target.result
+
+transaction1 = db.transaction('store', 'readonly')
+transaction2 = db.transaction('store', 'readonly')
+transaction1GetSuccess = false
+transaction2GetSuccess = false
+Keep both transactions alive until each has reported at least one successful operation
+
+onTransactionComplete():
+first transaction complete, still waiting...
+
+onTransactionComplete():
+PASS transaction1GetSuccess is true
+PASS transaction2GetSuccess is true
+db.close()
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/storage/indexeddb/transaction-coordination-within-database.html b/LayoutTests/storage/indexeddb/transaction-coordination-within-database.html
new file mode 100644 (file)
index 0000000..4f0c5b7
--- /dev/null
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../fast/js/resources/js-test-pre.js"></script>
+<script src="resources/shared.js"></script>
+</head>
+<body>
+<script src="resources/transaction-coordination-within-database.js"></script>
+<script src="../../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/storage/indexeddb/transaction-scope-sequencing-expected.txt b/LayoutTests/storage/indexeddb/transaction-scope-sequencing-expected.txt
new file mode 100644 (file)
index 0000000..e75aa80
--- /dev/null
@@ -0,0 +1,59 @@
+Check that scope restrictions on read-write transactions are enforced.
+
+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-scope-sequencing.html"
+indexedDB.deleteDatabase(dbname)
+indexedDB.open(dbname)
+
+prepareDatabase():
+db = event.target.result
+db.createObjectStore('a')
+db.createObjectStore('b')
+db.createObjectStore('c')
+
+runTransactions():
+db = event.target.result
+
+transaction1 = db.transaction(['a'], 'readwrite')
+transaction1Started = false
+transaction1Complete = false
+transaction1.objectStore('a').get(0)
+
+transaction2 overlaps with transaction1, so must wait until transaction1 completes
+transaction2 = db.transaction(['a', 'b'], 'readwrite')
+transaction2Started = false
+transaction2Complete = false
+transaction2.objectStore('a').get(0)
+
+transaction3 overlaps with transaction2, so must wait until transaction2 completes
+even though it does not overlap with transaction1
+transaction3 = db.transaction(['b', 'c'], 'readwrite')
+transaction3Started = false
+transaction3Complete = false
+transaction3.objectStore('b').get(0)
+
+transaction1Started = true
+
+transaction1Complete = true
+PASS transaction2Started is false
+PASS transaction3Started is false
+
+PASS transaction1Complete is true
+transaction2Started = true
+
+transaction2Complete = true
+PASS transaction3Started is false
+
+PASS transaction1Complete is true
+PASS transaction2Complete is true
+transaction3Started = true
+
+transaction3Complete = true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/storage/indexeddb/transaction-scope-sequencing.html b/LayoutTests/storage/indexeddb/transaction-scope-sequencing.html
new file mode 100644 (file)
index 0000000..83eedc2
--- /dev/null
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../fast/js/resources/js-test-pre.js"></script>
+<script src="resources/shared.js"></script>
+</head>
+<body>
+<script src="resources/transaction-scope-sequencing.js"></script>
+<script src="../../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/storage/indexeddb/transaction-starvation-expected.txt b/LayoutTests/storage/indexeddb/transaction-starvation-expected.txt
new file mode 100644 (file)
index 0000000..058384d
--- /dev/null
@@ -0,0 +1,37 @@
+Check that read-only transactions don't starve read-write transactions.
+
+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-starvation.html"
+indexedDB.deleteDatabase(dbname)
+indexedDB.open(dbname)
+
+prepareDatabase():
+db = event.target.result
+db.createObjectStore('store')
+
+runTransactions():
+db = event.target.result
+
+readWriteTransactionStarted = false
+readWriteTransactionComplete = false
+
+startReadOnlyTransaction():
+transaction = db.transaction('store', 'readonly')
+store = transaction.objectStore('store')
+Keep the transaction alive with an endless series of gets
+
+startReadWriteTransaction():
+transaction = db.transaction('store', 'readwrite')
+readWriteTransactionStarted = true
+
+readWriteTransactionComplete():
+PASS Transaction wasn't starved
+readWriteTransactionComplete = true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/storage/indexeddb/transaction-starvation.html b/LayoutTests/storage/indexeddb/transaction-starvation.html
new file mode 100644 (file)
index 0000000..fb8f0ce
--- /dev/null
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../fast/js/resources/js-test-pre.js"></script>
+<script src="resources/shared.js"></script>
+</head>
+<body>
+<script src="resources/transaction-starvation.js"></script>
+<script src="../../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
index 19595ba..2268b74 100644 (file)
@@ -1,3 +1,49 @@
+2012-12-05  Joshua Bell  <jsbell@chromium.org>
+
+        IndexedDB: Allow multiple transactions to interleave request execution
+        https://bugs.webkit.org/show_bug.cgi?id=97570
+
+        Reviewed by Tony Chang.
+
+        Implement spec logic for allowing read-only transactions, and read-write transactions
+        with non-overlapping scopes, to run concurrently. Transactions all still run in the
+        same thread with tasks triggered via timers, so tasks and the underlying database
+        operations are interleaved rather than truly parallelized.
+
+        Within IDBTransactionCoordinator, rename started->queued, running->started to match
+        spec terminology and clear up confusion.
+
+        Test: storage/indexeddb/transaction-coordination-across-databases.html
+              storage/indexeddb/transaction-coordination-within-database.html
+              storage/indexeddb/transaction-readwrite-exclusive.html
+              storage/indexeddb/transaction-scope-sequencing.html
+              storage/indexeddb/transaction-starvation.html
+
+        * Modules/indexeddb/IDBDatabaseBackendImpl.cpp: Use IDBTransaction::Mode enum
+        (WebCore::IDBDatabaseBackendImpl::createTransaction):
+        * Modules/indexeddb/IDBDatabaseBackendImpl.h: Ditto.
+        (IDBDatabaseBackendImpl):
+        * Modules/indexeddb/IDBDatabaseBackendInterface.h: Ditto.
+        (IDBDatabaseBackendInterface):
+        * Modules/indexeddb/IDBTransactionBackendImpl.cpp: Convert scope as a HashSet for
+        fast intersecting.
+        (WebCore::IDBTransactionBackendImpl::create):
+        (WebCore::IDBTransactionBackendImpl::IDBTransactionBackendImpl):
+        * Modules/indexeddb/IDBTransactionBackendImpl.h:
+        (IDBTransactionBackendImpl):
+        (WebCore::IDBTransactionBackendImpl::mode):
+        (WebCore::IDBTransactionBackendImpl::scope):
+        * Modules/indexeddb/IDBTransactionCoordinator.cpp: Spec logic goes here.
+        (WebCore::IDBTransactionCoordinator::processStartedTransactions): Extend this
+        method to test all plausibly runnable transactions.
+        (WebCore):
+        (WebCore::IDBTransactionCoordinator::canRunTransaction): Test to see if one
+        particular transaction can be run.
+        (WebCore::IDBTransactionCoordinator::doScopesOverlap): Do a quick intersection
+        test between transaction scopes.
+        * Modules/indexeddb/IDBTransactionCoordinator.h:
+        (IDBTransactionCoordinator):
+
 2012-12-05  No'am Rosenthal  <noam@webkit.org>
 
         Coordinated Graphics: Enable support for setContentsToBackgroundColor
index 7ab618b..ae7af0e 100644 (file)
@@ -433,7 +433,7 @@ void IDBDatabaseBackendImpl::processPendingCalls()
 }
 
 // FIXME: Remove this method in https://bugs.webkit.org/show_bug.cgi?id=103923.
-PassRefPtr<IDBTransactionBackendInterface> IDBDatabaseBackendImpl::createTransaction(int64_t transactionId, const Vector<int64_t>& objectStoreIds, unsigned short mode)
+PassRefPtr<IDBTransactionBackendInterface> IDBDatabaseBackendImpl::createTransaction(int64_t transactionId, const Vector<int64_t>& objectStoreIds, IDBTransaction::Mode mode)
 {
     RefPtr<IDBTransactionBackendImpl> transaction = IDBTransactionBackendImpl::create(transactionId, objectStoreIds, mode, this);
     m_transactions.add(transaction.get());
index 29e4e53..632aa6a 100644 (file)
@@ -64,7 +64,7 @@ public:
     virtual PassRefPtr<IDBObjectStoreBackendInterface> createObjectStore(int64_t id, const String& name, const IDBKeyPath&, bool autoIncrement, IDBTransactionBackendInterface*, ExceptionCode&);
     virtual void deleteObjectStore(int64_t, IDBTransactionBackendInterface*, ExceptionCode&);
     // FIXME: Remove this method in https://bugs.webkit.org/show_bug.cgi?id=103923.
-    virtual PassRefPtr<IDBTransactionBackendInterface> createTransaction(int64_t transactionId, const Vector<int64_t>& objectStoreIds, unsigned short mode);
+    virtual PassRefPtr<IDBTransactionBackendInterface> createTransaction(int64_t transactionId, const Vector<int64_t>& objectStoreIds, IDBTransaction::Mode);
     virtual void createTransaction(int64_t transactionId, PassRefPtr<IDBDatabaseCallbacks>, const Vector<int64_t>& objectStoreIds, unsigned short mode);
     virtual void close(PassRefPtr<IDBDatabaseCallbacks>);
 
index a6a2af7..116c438 100644 (file)
@@ -26,6 +26,7 @@
 #ifndef IDBDatabaseBackendInterface_h
 #define IDBDatabaseBackendInterface_h
 
+#include "IDBTransaction.h"
 #include <wtf/PassRefPtr.h>
 #include <wtf/RefCounted.h>
 #include <wtf/text/WTFString.h>
@@ -56,7 +57,7 @@ public:
     virtual PassRefPtr<IDBObjectStoreBackendInterface> createObjectStore(int64_t, const String& name, const IDBKeyPath&, bool autoIncrement, IDBTransactionBackendInterface*, ExceptionCode&) = 0;
     virtual void deleteObjectStore(int64_t, IDBTransactionBackendInterface*, ExceptionCode&) = 0;
     // FIXME: Remove this method in https://bugs.webkit.org/show_bug.cgi?id=103923.
-    virtual PassRefPtr<IDBTransactionBackendInterface> createTransaction(int64_t transactionId, const Vector<int64_t>& objectStoreIds, unsigned short mode) = 0;
+    virtual PassRefPtr<IDBTransactionBackendInterface> createTransaction(int64_t transactionId, const Vector<int64_t>& objectStoreIds, IDBTransaction::Mode) = 0;
     virtual void createTransaction(int64_t transactionId, PassRefPtr<IDBDatabaseCallbacks>, const Vector<int64_t>& objectStoreIds, unsigned short mode) = 0;
     virtual void close(PassRefPtr<IDBDatabaseCallbacks>) = 0;
 
index f2f71c4..2bad590 100644 (file)
 
 namespace WebCore {
 
-PassRefPtr<IDBTransactionBackendImpl> IDBTransactionBackendImpl::create(int64_t id, const Vector<int64_t>& objectStoreIds, unsigned short mode, IDBDatabaseBackendImpl* database)
+PassRefPtr<IDBTransactionBackendImpl> IDBTransactionBackendImpl::create(int64_t id, const Vector<int64_t>& objectStoreIds, IDBTransaction::Mode mode, IDBDatabaseBackendImpl* database)
 {
-    return adoptRef(new IDBTransactionBackendImpl(id, objectStoreIds, mode, database));
+    HashSet<int64_t> objectStoreHashSet;
+    for (size_t i = 0; i < objectStoreIds.size(); ++i)
+        objectStoreHashSet.add(objectStoreIds[i]);
+
+    return adoptRef(new IDBTransactionBackendImpl(id, objectStoreHashSet, mode, database));
 }
 
-IDBTransactionBackendImpl::IDBTransactionBackendImpl(int64_t id, const Vector<int64_t>& objectStoreIds, unsigned short mode, IDBDatabaseBackendImpl* database)
+IDBTransactionBackendImpl::IDBTransactionBackendImpl(int64_t id, const HashSet<int64_t>& objectStoreIds, IDBTransaction::Mode mode, IDBDatabaseBackendImpl* database)
     : m_id(id)
     , m_objectStoreIds(objectStoreIds)
     , m_mode(mode)
index 512478d..439c75b 100644 (file)
@@ -30,6 +30,7 @@
 
 #include "IDBBackingStore.h"
 #include "IDBDatabaseError.h"
+#include "IDBTransaction.h"
 #include "IDBTransactionBackendInterface.h"
 #include "IDBTransactionCallbacks.h"
 #include "Timer.h"
@@ -43,7 +44,7 @@ class IDBDatabaseBackendImpl;
 
 class IDBTransactionBackendImpl : public IDBTransactionBackendInterface {
 public:
-    static PassRefPtr<IDBTransactionBackendImpl> create(int64_t transactionId, const Vector<int64_t>&, unsigned short mode, IDBDatabaseBackendImpl*);
+    static PassRefPtr<IDBTransactionBackendImpl> create(int64_t transactionId, const Vector<int64_t>&, IDBTransaction::Mode, IDBDatabaseBackendImpl*);
     static IDBTransactionBackendImpl* from(IDBTransactionBackendInterface* interface)
     {
         return static_cast<IDBTransactionBackendImpl*>(interface);
@@ -63,7 +64,8 @@ public:
 
     void abort(PassRefPtr<IDBDatabaseError>);
     void run();
-    unsigned short mode() const { return m_mode; }
+    IDBTransaction::Mode mode() const { return m_mode; }
+    const HashSet<int64_t>& scope() const { return m_objectStoreIds; }
     bool isFinished() const { return m_state == Finished; }
     bool scheduleTask(PassOwnPtr<Operation> task, PassOwnPtr<Operation> abortTask = nullptr) { return scheduleTask(NormalTask, task, abortTask); }
     bool scheduleTask(TaskType, PassOwnPtr<Operation>, PassOwnPtr<Operation> abortTask = nullptr);
@@ -75,7 +77,7 @@ public:
     int64_t id() const { return m_id; }
 
 private:
-    IDBTransactionBackendImpl(int64_t id, const Vector<int64_t>& objectStoreIds, unsigned short mode, IDBDatabaseBackendImpl*);
+    IDBTransactionBackendImpl(int64_t id, const HashSet<int64_t>& objectStoreIds, IDBTransaction::Mode, IDBDatabaseBackendImpl*);
 
     enum State {
         Unused, // Created, but no tasks yet.
@@ -94,8 +96,8 @@ private:
     void closeOpenCursors();
 
     const int64_t m_id;
-    const Vector<int64_t> m_objectStoreIds;
-    const unsigned short m_mode;
+    const HashSet<int64_t> m_objectStoreIds;
+    const IDBTransaction::Mode m_mode;
 
     State m_state;
     bool m_commitPending;
index cfd3d16..24ea802 100644 (file)
@@ -30,6 +30,7 @@
 
 #include "IDBDatabaseBackendImpl.h"
 #include "IDBObjectStoreBackendInterface.h"
+#include "IDBTransaction.h"
 #include "IDBTransactionBackendImpl.h"
 
 namespace WebCore {
@@ -57,7 +58,7 @@ void IDBTransactionCoordinator::didStartTransaction(IDBTransactionBackendImpl* t
 {
     ASSERT(m_transactions.contains(transaction));
 
-    m_startedTransactions.add(transaction);
+    m_queuedTransactions.add(transaction);
     processStartedTransactions();
 }
 
@@ -65,11 +66,11 @@ void IDBTransactionCoordinator::didFinishTransaction(IDBTransactionBackendImpl*
 {
     ASSERT(m_transactions.contains(transaction));
 
-    if (m_startedTransactions.contains(transaction)) {
-        ASSERT(!m_runningTransactions.contains(transaction));
+    if (m_queuedTransactions.contains(transaction)) {
+        ASSERT(!m_startedTransactions.contains(transaction));
+        m_queuedTransactions.remove(transaction);
+    } else if (m_startedTransactions.contains(transaction))
         m_startedTransactions.remove(transaction);
-    } else if (m_runningTransactions.contains(transaction))
-        m_runningTransactions.remove(transaction);
 
     m_transactions.remove(transaction);
 
@@ -81,9 +82,9 @@ void IDBTransactionCoordinator::didFinishTransaction(IDBTransactionBackendImpl*
 bool IDBTransactionCoordinator::isActive(IDBTransactionBackendImpl* transaction)
 {
     bool found = false;
-    if (m_startedTransactions.contains(transaction))
+    if (m_queuedTransactions.contains(transaction))
         found = true;
-    if (m_runningTransactions.contains(transaction)) {
+    if (m_startedTransactions.contains(transaction)) {
         ASSERT(!found);
         found = true;
     }
@@ -94,17 +95,58 @@ bool IDBTransactionCoordinator::isActive(IDBTransactionBackendImpl* transaction)
 
 void IDBTransactionCoordinator::processStartedTransactions()
 {
-    // FIXME: For now, we only allow one transaction to run at a time.
-    if (!m_runningTransactions.isEmpty())
+    if (m_queuedTransactions.isEmpty())
         return;
 
-    if (m_startedTransactions.isEmpty())
-        return;
+    ASSERT(m_startedTransactions.isEmpty() || (*m_startedTransactions.begin())->mode() != IDBTransaction::VERSION_CHANGE);
+
+    ListHashSet<IDBTransactionBackendImpl*>::const_iterator it = m_queuedTransactions.begin();
+    while (it != m_queuedTransactions.end()) {
+        IDBTransactionBackendImpl* transaction = *it;
+        ++it;
+        if (canRunTransaction(transaction)) {
+            m_queuedTransactions.remove(transaction);
+            m_startedTransactions.add(transaction);
+            transaction->run();
+        }
+    }
+}
 
-    IDBTransactionBackendImpl* transaction = *m_startedTransactions.begin();
-    m_startedTransactions.remove(transaction);
-    m_runningTransactions.add(transaction);
-    transaction->run();
+static bool doScopesOverlap(const HashSet<int64_t>& scope1, const HashSet<int64_t>& scope2)
+{
+    for (HashSet<int64_t>::const_iterator it = scope1.begin(); it != scope1.end(); ++it) {
+        if (scope2.contains(*it))
+            return true;
+    }
+    return false;
+}
+
+bool IDBTransactionCoordinator::canRunTransaction(IDBTransactionBackendImpl* transaction)
+{
+    ASSERT(m_queuedTransactions.contains(transaction));
+    switch (transaction->mode()) {
+    case IDBTransaction::VERSION_CHANGE:
+        ASSERT(m_queuedTransactions.size() == 1);
+        ASSERT(m_startedTransactions.isEmpty());
+        return true;
+
+    case IDBTransaction::READ_ONLY:
+        return true;
+
+    case IDBTransaction::READ_WRITE:
+        for (HashSet<IDBTransactionBackendImpl*>::const_iterator it = m_startedTransactions.begin(); it != m_startedTransactions.end(); ++it) {
+            if ((*it)->mode() == IDBTransaction::READ_WRITE && doScopesOverlap(transaction->scope(), (*it)->scope()))
+                return false;
+        }
+        for (ListHashSet<IDBTransactionBackendImpl*>::const_iterator it = m_queuedTransactions.begin(); *it != transaction; ++it) {
+            ASSERT(it != m_queuedTransactions.end());
+            if ((*it)->mode() == IDBTransaction::READ_WRITE && doScopesOverlap(transaction->scope(), (*it)->scope()))
+                return false;
+        }
+        return true;
+    }
+    ASSERT_NOT_REACHED();
+    return false;
 }
 
 };
index 60906fe..91dc0e0 100644 (file)
@@ -57,12 +57,13 @@ private:
     IDBTransactionCoordinator();
 
     void processStartedTransactions();
+    bool canRunTransaction(IDBTransactionBackendImpl*);
 
     // This is just an efficient way to keep references to all transactions.
     HashMap<IDBTransactionBackendImpl*, RefPtr<IDBTransactionBackendImpl> > m_transactions;
     // Transactions in different states are grouped below.
-    ListHashSet<IDBTransactionBackendImpl*> m_startedTransactions;
-    HashSet<IDBTransactionBackendImpl*> m_runningTransactions;
+    ListHashSet<IDBTransactionBackendImpl*> m_queuedTransactions;
+    HashSet<IDBTransactionBackendImpl*> m_startedTransactions;
 };
 
 } // namespace WebCore
index 1e038d9..cefa4a3 100644 (file)
@@ -1,3 +1,20 @@
+2012-12-05  Joshua Bell  <jsbell@chromium.org>
+
+        IndexedDB: Allow multiple transactions to interleave request execution
+        https://bugs.webkit.org/show_bug.cgi?id=97570
+
+        Reviewed by Tony Chang.
+
+        Map to IDBTransaction::Mode enum as appropriate.
+
+        * src/IDBDatabaseBackendProxy.cpp:
+        (WebKit::IDBDatabaseBackendProxy::createTransaction):
+        * src/IDBDatabaseBackendProxy.h:
+        (IDBDatabaseBackendProxy):
+        * src/WebIDBDatabaseImpl.cpp:
+        (WebKit::WebIDBDatabaseImpl::createTransaction):
+         * tests/IDBDatabaseBackendTest.cpp:
+
 2012-12-05  Eberhard Graether  <egraether@google.com>
 
         [chromium] Remove WebLayerTreeView API to set font atlas
index a0dc24e..45040b6 100644 (file)
@@ -85,7 +85,7 @@ void IDBDatabaseBackendProxy::deleteObjectStore(int64_t objectStoreId, IDBTransa
     m_webIDBDatabase->deleteObjectStore(objectStoreId, *transactionProxy->getWebIDBTransaction(), ec);
 }
 
-PassRefPtr<IDBTransactionBackendInterface> IDBDatabaseBackendProxy::createTransaction(int64_t id, const Vector<int64_t>& objectStoreIds, unsigned short mode)
+PassRefPtr<IDBTransactionBackendInterface> IDBDatabaseBackendProxy::createTransaction(int64_t id, const Vector<int64_t>& objectStoreIds, IDBTransaction::Mode mode)
 {
     OwnPtr<WebIDBTransaction> transaction = adoptPtr(m_webIDBDatabase->createTransaction(id, objectStoreIds, mode));
     if (!transaction)
index b988d35..88b9ccd 100644 (file)
@@ -47,7 +47,7 @@ public:
     virtual PassRefPtr<WebCore::IDBObjectStoreBackendInterface> createObjectStore(int64_t, const String& name, const WebCore::IDBKeyPath&, bool autoIncrement, WebCore::IDBTransactionBackendInterface*, WebCore::ExceptionCode&);
     virtual void deleteObjectStore(int64_t, WebCore::IDBTransactionBackendInterface*, WebCore::ExceptionCode&);
     // FIXME: Remove this method in https://bugs.webkit.org/show_bug.cgi?id=103923.
-    virtual PassRefPtr<WebCore::IDBTransactionBackendInterface> createTransaction(int64_t, const Vector<int64_t>&, unsigned short mode);
+    virtual PassRefPtr<WebCore::IDBTransactionBackendInterface> createTransaction(int64_t, const Vector<int64_t>&, WebCore::IDBTransaction::Mode);
     virtual void createTransaction(int64_t, PassRefPtr<WebCore::IDBDatabaseCallbacks>, const Vector<int64_t>&, unsigned short mode);
     virtual void close(PassRefPtr<WebCore::IDBDatabaseCallbacks>);
 
index 8684648..3a0a944 100644 (file)
@@ -81,7 +81,7 @@ WebIDBTransaction* WebIDBDatabaseImpl::createTransaction(long long id, const Web
     Vector<int64_t> objectStoreIdList(objectStoreIds.size());
     for (size_t i = 0; i < objectStoreIds.size(); ++i)
         objectStoreIdList[i] = objectStoreIds[i];
-    RefPtr<IDBTransactionBackendInterface> transaction = m_databaseBackend->createTransaction(id, objectStoreIdList, mode);
+    RefPtr<IDBTransactionBackendInterface> transaction = m_databaseBackend->createTransaction(id, objectStoreIdList, static_cast<IDBTransaction::Mode>(mode));
     if (!transaction)
         return 0;
     return new WebIDBTransactionImpl(transaction);
index 55b85da..c2efc19 100644 (file)
@@ -155,7 +155,7 @@ public:
     virtual void deleteObjectStore(const String& name, IDBTransactionBackendInterface*, ExceptionCode&) { }
     virtual void deleteObjectStore(int64_t, IDBTransactionBackendInterface*, ExceptionCode&) { }
     // FIXME: Remove this method in https://bugs.webkit.org/show_bug.cgi?id=103923.
-    virtual PassRefPtr<IDBTransactionBackendInterface> createTransaction(int64_t, const Vector<int64_t>&, unsigned short mode) { return 0; }
+    virtual PassRefPtr<IDBTransactionBackendInterface> createTransaction(int64_t, const Vector<int64_t>&, IDBTransaction::Mode) { return 0; }
     void createTransaction(int64_t, PassRefPtr<IDBDatabaseCallbacks>, const Vector<int64_t>&, unsigned short mode) { }
 
     virtual void close(PassRefPtr<IDBDatabaseCallbacks>)