Title: [97417] trunk
Revision
97417
Author
commit-qu...@webkit.org
Date
2011-10-13 16:08:13 -0700 (Thu, 13 Oct 2011)

Log Message

make IDBFactory.open wait for pending setVersion transactions to complete
https://bugs.webkit.org/show_bug.cgi?id=69307

Patch by Joshua Bell <jsb...@chromium.org> on 2011-10-13
Reviewed by Tony Chang.

Source/WebCore:

Added a queue of pending open calls, similar to the queue of pending
setVersion calls. Ensure pending calls are processed in the correct
order when transactions complete.

Tests: storage/indexeddb/open-close-version.html
       storage/indexeddb/two-version-changes.html
       storage/indexeddb/version-change-exclusive.html

* storage/IDBDatabaseBackendImpl.cpp:
(WebCore::IDBDatabaseBackendImpl::PendingOpenCall::create):
(WebCore::IDBDatabaseBackendImpl::PendingOpenCall::callbacks):
(WebCore::IDBDatabaseBackendImpl::PendingOpenCall::PendingOpenCall):
(WebCore::IDBDatabaseBackendImpl::setVersion):
(WebCore::IDBDatabaseBackendImpl::transactionStarted):
(WebCore::IDBDatabaseBackendImpl::transactionFinished):
(WebCore::IDBDatabaseBackendImpl::processPendingCalls):
(WebCore::IDBDatabaseBackendImpl::openConnection):
(WebCore::IDBDatabaseBackendImpl::close):
* storage/IDBDatabaseBackendImpl.h:
* storage/IDBFactoryBackendImpl.cpp:
(WebCore::IDBFactoryBackendImpl::open):
* storage/IDBTransactionBackendImpl.cpp:
(WebCore::IDBTransactionBackendImpl::abort):
(WebCore::IDBTransactionBackendImpl::start):
(WebCore::IDBTransactionBackendImpl::commit):

LayoutTests:

Some tests marked FAIL due to crbug.com/100123

* storage/indexeddb/open-close-version-expected.txt: Added.
* storage/indexeddb/open-close-version.html: Added.
* storage/indexeddb/two-version-changes-expected.txt: Added.
* storage/indexeddb/two-version-changes.html: Added.
* storage/indexeddb/version-change-exclusive-expected.txt: Added.
* storage/indexeddb/version-change-exclusive.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (97416 => 97417)


--- trunk/LayoutTests/ChangeLog	2011-10-13 22:54:02 UTC (rev 97416)
+++ trunk/LayoutTests/ChangeLog	2011-10-13 23:08:13 UTC (rev 97417)
@@ -1,3 +1,19 @@
+2011-10-13  Joshua Bell  <jsb...@chromium.org>
+
+        make IDBFactory.open wait for pending setVersion transactions to complete
+        https://bugs.webkit.org/show_bug.cgi?id=69307
+
+        Reviewed by Tony Chang.
+
+        Some tests marked FAIL due to crbug.com/100123
+
+        * storage/indexeddb/open-close-version-expected.txt: Added.
+        * storage/indexeddb/open-close-version.html: Added.
+        * storage/indexeddb/two-version-changes-expected.txt: Added.
+        * storage/indexeddb/two-version-changes.html: Added.
+        * storage/indexeddb/version-change-exclusive-expected.txt: Added.
+        * storage/indexeddb/version-change-exclusive.html: Added.
+
 2011-10-13  Dimitri Glazkov  <dglaz...@chromium.org>
 
         https://bugs.webkit.org/show_bug.cgi?id=70069

Added: trunk/LayoutTests/storage/indexeddb/open-close-version-expected.txt (0 => 97417)


--- trunk/LayoutTests/storage/indexeddb/open-close-version-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/open-close-version-expected.txt	2011-10-13 23:08:13 UTC (rev 97417)
@@ -0,0 +1,103 @@
+Test interleaved open/close/setVersion calls in various permutations
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
+PASS indexedDB == null is false
+
+TEST: setVersion blocked on open handles
+window.dbname = 'test1'; window.ver = 1; window.steps = []
+'h1.open'
+'h1.open.onsuccess'
+'h2.open'
+'h2.open.onsuccess'
+'h1.setVersion'
+'h2.onversionchange'
+    h2 closing, but not immediately
+'h1.setVersion.onblocked'
+'h2.close'
+'h1.setVersion.onsuccess'
+'h1.setVersion.transaction-complete'
+PASS window.steps.toString() is "h1.open,h1.open.onsuccess,h2.open,h2.open.onsuccess,h1.setVersion,h2.onversionchange,h1.setVersion.onblocked,h2.close,h1.setVersion.onsuccess,h1.setVersion.transaction-complete"
+
+TEST: setVersion not blocked if handle closed immediately
+window.dbname = 'test2'; window.ver = 1; window.steps = []
+'h1.open'
+'h1.open.onsuccess'
+'h2.open'
+'h2.open.onsuccess'
+'h1.setVersion'
+'h2.onversionchange'
+    h2 closing immediately
+'h2.close'
+'h1.setVersion.onblocked'
+'h1.setVersion.onsuccess'
+'h1.setVersion.transaction-complete'
+NOTE: Will FAIL with extra bogus h1.setVersion.onblocked step; crbug.com/100123
+FAIL window.steps.toString() should be h1.open,h1.open.onsuccess,h2.open,h2.open.onsuccess,h1.setVersion,h2.onversionchange,h2.close,h1.setVersion.onsuccess,h1.setVersion.transaction-complete. Was h1.open,h1.open.onsuccess,h2.open,h2.open.onsuccess,h1.setVersion,h2.onversionchange,h2.close,h1.setVersion.onblocked,h1.setVersion.onsuccess,h1.setVersion.transaction-complete.
+
+TEST: open and setVersion blocked if a VERSION_CHANGE transaction is running - close when blocked
+window.dbname = 'test3'; window.ver = 1; window.steps = []
+'h1.open'
+'h1.open.onsuccess'
+'h2.open'
+'h2.open.onsuccess'
+'h1.setVersion'
+'h2.setVersion'
+'h2.onversionchange'
+'h1.setVersion.onblocked'
+'h1.onversionchange'
+'h2.setVersion.onblocked'
+    h2 blocked so closing
+'h2.close'
+'h3.open'
+'h2.setVersion.onerror'
+'h1.setVersion.onsuccess'
+'h1.setVersion.transaction-complete'
+'h3.open.onsuccess'
+PASS window.steps.toString() is "h1.open,h1.open.onsuccess,h2.open,h2.open.onsuccess,h1.setVersion,h2.setVersion,h2.onversionchange,h1.setVersion.onblocked,h1.onversionchange,h2.setVersion.onblocked,h2.close,h3.open,h2.setVersion.onerror,h1.setVersion.onsuccess,h1.setVersion.transaction-complete,h3.open.onsuccess"
+
+TEST: open and setVersion blocked if a VERSION_CHANGE transaction is running - just close
+window.dbname = 'test4'; window.ver = 1; window.steps = []
+'h1.open'
+'h1.open.onsuccess'
+'h2.open'
+'h2.open.onsuccess'
+'h1.setVersion'
+'h3.open'
+'h2.close'
+'h1.setVersion.onblocked'
+'h1.setVersion.onsuccess'
+'h1.setVersion.transaction-complete'
+'h3.open.onsuccess'
+NOTE: Will FAIL with extra bogus h1.setVersion.onblocked step; crbug.com/100123
+FAIL window.steps.toString() should be h1.open,h1.open.onsuccess,h2.open,h2.open.onsuccess,h1.setVersion,h3.open,h2.close,h1.setVersion.onsuccess,h1.setVersion.transaction-complete,h3.open.onsuccess. Was h1.open,h1.open.onsuccess,h2.open,h2.open.onsuccess,h1.setVersion,h3.open,h2.close,h1.setVersion.onblocked,h1.setVersion.onsuccess,h1.setVersion.transaction-complete,h3.open.onsuccess.
+
+TEST: open blocked if a VERSION_CHANGE transaction is running
+window.dbname = 'test5'; window.ver = 1; window.steps = []
+'h1.open'
+'h1.open.onsuccess'
+'h1.setVersion'
+'h2.open'
+'h1.setVersion.onsuccess'
+'h1.setVersion.transaction-complete'
+'h2.open.onsuccess'
+PASS window.steps.toString() is "h1.open,h1.open.onsuccess,h1.setVersion,h2.open,h1.setVersion.onsuccess,h1.setVersion.transaction-complete,h2.open.onsuccess"
+
+TEST: two setVersions from the same connection
+window.dbname = 'test6'; window.ver = 1; window.steps = []
+'h1.open'
+'h1.open.onsuccess'
+'h1.setVersion'
+'h1.setVersion'
+'h1.setVersion.onsuccess'
+'h1.setVersion.transaction-complete'
+half done
+'h1.setVersion.onsuccess'
+'h1.setVersion.transaction-complete'
+PASS window.steps.toString() is "h1.open,h1.open.onsuccess,h1.setVersion,h1.setVersion,h1.setVersion.onsuccess,h1.setVersion.transaction-complete,h1.setVersion.onsuccess,h1.setVersion.transaction-complete"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/storage/indexeddb/open-close-version.html (0 => 97417)


--- trunk/LayoutTests/storage/indexeddb/open-close-version.html	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/open-close-version.html	2011-10-13 23:08:13 UTC (rev 97417)
@@ -0,0 +1,300 @@
+<html>
+<head>
+<link rel="stylesheet" href=""
+<script src=""
+<script src=""
+<script src=""
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+
+description("Test interleaved open/close/setVersion calls in various permutations");
+if (window.layoutTestController)
+    layoutTestController.waitUntilDone();
+
+function Connection(id) {
+    id = String(id);
+    var self = this;
+    this.open = function(opts) {
+        window.steps.push(evalAndLog("'" + id + ".open'"));
+        var req = indexedDB.open(window.dbname);
+        req._onerror_ = unexpectedErrorCallback;
+        req._onsuccess_ = function (e) {
+            self.handle = e.target.result;
+            window.steps.push(evalAndLog("'" + id + ".open.onsuccess'"));
+            self.handle._onversionchange_ = function(e) {
+                window.steps.push(evalAndLog("'" + id + ".onversionchange'"));
+                if (opts && opts.onversion) { opts.onversion.call(self); }
+            };
+            if (opts && opts.onsuccess) { opts.onsuccess.call(self); }
+        };
+    };
+
+    this.close = function() {
+        window.steps.push(evalAndLog("'" + id + ".close'"));
+        this.handle.close();
+    };
+
+    this.setVersion = function(opts) {
+        window.steps.push(evalAndLog("'" + id + ".setVersion'"));
+        var req = this.handle.setVersion(String(window.ver++));
+        req._onabort_ = function (e) {
+            window.steps.push(evalAndLog("'" + id + ".setVersion.onabort'"));
+            if (opts && opts.onabort) { opts.onabort.call(self); }
+        };
+        req._onblocked_ = function (e) {
+            window.steps.push(evalAndLog("'" + id + ".setVersion.onblocked'"));
+            if (opts && opts.onblocked) { opts.onblocked.call(self); }
+        };
+        req._onsuccess_ = function (e) {
+            window.steps.push(evalAndLog("'" + id + ".setVersion.onsuccess'"));
+            if (self.handle.objectStoreNames.contains("test-store" + window.ver)) {
+                self.handle.deleteObjectStore("test-store" + window.ver);
+            }
+            var store = self.handle.createObjectStore("test-store" + window.ver);
+            var count = 0;
+            do_async_puts(); // Keep this transaction running for a while
+            function do_async_puts() {
+                var req = store.put(count, count);
+                req._onerror_ = unexpectedErrorCallback;
+                req._onsuccess_ = function (e) {
+                    if (++count < 10) {
+                        do_async_puts();
+                    } else {
+                        window.steps.push(evalAndLog("'" + id + ".setVersion.transaction-complete'"));
+                        if (opts && opts.onsuccess) { opts.onsuccess.call(self); }
+                    }
+                };
+            }
+        };
+        req._onerror_ = function (e) {
+            window.steps.push(evalAndLog("'" + id + ".setVersion.onerror'"));
+            if (opts && opts.onerror) { opts.onerror.call(self); }
+        };
+    };
+}
+
+// run a series of steps that take a continuation function
+function runSteps(commands) {
+    if (commands.length) {
+        var command = commands.shift();
+        command(function() { runSteps(commands); });
+    }
+}
+
+function test() {
+    indexedDB = evalAndLog("indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;");
+    shouldBeFalse("indexedDB == null");
+    test1();
+}
+
+function test1() {
+    debug("");
+    debug("TEST: setVersion blocked on open handles");
+    evalAndLog("window.dbname = 'test1'; window.ver = 1; window.steps = []");
+    var h1 = new Connection("h1");
+    var h2 = new Connection("h2");
+    runSteps([function(doNext) { h1.open({onsuccess: doNext}); },
+         function(doNext) { h2.open({onsuccess: doNext,
+                                onversion: function() {
+                                    debug("    h2 closing, but not immediately");
+                                    setTimeout(function() { h2.close(); }, 0);
+                                }}); },
+         function(doNext) { h1.setVersion({onsuccess: finishTest}); },
+         ]);
+    function finishTest() {
+        shouldBeEqualToString("window.steps.toString()",
+                              ["h1.open",
+                               "h1.open.onsuccess",
+                               "h2.open",
+                               "h2.open.onsuccess",
+                               "h1.setVersion",
+                               "h2.onversionchange",
+                               "h1.setVersion.onblocked",
+                               "h2.close",
+                               "h1.setVersion.onsuccess",
+                               "h1.setVersion.transaction-complete"
+                               ].toString());
+        test2();
+    }
+}
+
+function test2() {
+    debug("");
+    debug("TEST: setVersion not blocked if handle closed immediately");
+    evalAndLog("window.dbname = 'test2'; window.ver = 1; window.steps = []");
+    var h1 = new Connection("h1");
+    var h2 = new Connection("h2");
+    runSteps([function(doNext) { h1.open({onsuccess: doNext}); },
+              function(doNext) { h2.open({onsuccess: doNext,
+                                          onversion: function() {
+                                              debug("    h2 closing immediately");
+                                              h2.close();
+                                          }}); },
+              function(doNext) { h1.setVersion({onsuccess: doNext}); },
+              finishTest
+              ]);
+
+    function finishTest() {
+        debug("NOTE: Will FAIL with extra bogus h1.setVersion.onblocked step; crbug.com/100123");
+        shouldBeEqualToString("window.steps.toString()",
+                              ["h1.open",
+                               "h1.open.onsuccess",
+                               "h2.open",
+                               "h2.open.onsuccess",
+                               "h1.setVersion",
+                               "h2.onversionchange",
+                               "h2.close",
+                               "h1.setVersion.onsuccess",
+                               "h1.setVersion.transaction-complete"
+                               ].toString());
+        test3();
+    }
+}
+
+function test3() {
+    debug("");
+    debug("TEST: open and setVersion blocked if a VERSION_CHANGE transaction is running - close when blocked");
+    evalAndLog("window.dbname = 'test3'; window.ver = 1; window.steps = []");
+    var h1 = new Connection("h1");
+    var h2 = new Connection("h2");
+    var h3 = new Connection("h3");
+    runSteps([function(doNext) { h1.open({onsuccess: doNext}); },
+              function(doNext) { h2.open({onsuccess: doNext}); },
+              function(doNext) { h1.setVersion(); doNext(); },
+              function(doNext) { h2.setVersion({
+                                                   onblocked: function() {
+                                                       debug("    h2 blocked so closing");
+                                                       h2.close();
+                                                       doNext();
+                                                   }}); },
+              function() { h3.open({onsuccess: finishTest});}
+              ]);
+
+    function finishTest() {
+        shouldBeEqualToString("window.steps.toString()",
+                              ["h1.open",
+                               "h1.open.onsuccess",
+                               "h2.open",
+                               "h2.open.onsuccess",
+                               "h1.setVersion",
+                               "h2.setVersion",
+                               "h2.onversionchange",
+                               "h1.setVersion.onblocked",
+                               "h1.onversionchange",
+                               "h2.setVersion.onblocked",
+                               "h2.close",
+                               "h3.open",
+                               "h2.setVersion.onerror",
+                               "h1.setVersion.onsuccess",
+                               "h1.setVersion.transaction-complete",
+                               "h3.open.onsuccess"
+                               ].toString());
+        test4();
+    }
+}
+
+function test4() {
+    debug("");
+    debug("TEST: open and setVersion blocked if a VERSION_CHANGE transaction is running - just close");
+    evalAndLog("window.dbname = 'test4'; window.ver = 1; window.steps = []");
+    var h1 = new Connection("h1");
+    var h2 = new Connection("h2");
+    var h3 = new Connection("h3");
+    runSteps([function(doNext) { h1.open({onsuccess: doNext}); },
+              function(doNext) { h2.open({onsuccess: doNext}); },
+              function(doNext) { h1.setVersion(); doNext(); },
+              function(doNext) { h3.open({onsuccess: finishTest}); doNext(); },
+              function(doNext) { h2.close(); },
+              ]);
+
+    function finishTest() {
+        debug("NOTE: Will FAIL with extra bogus h1.setVersion.onblocked step; crbug.com/100123");
+        shouldBeEqualToString("window.steps.toString()",
+                              ["h1.open",
+                               "h1.open.onsuccess",
+                               "h2.open",
+                               "h2.open.onsuccess",
+                               "h1.setVersion",
+                               "h3.open",
+                               "h2.close",
+                               "h1.setVersion.onsuccess",
+                               "h1.setVersion.transaction-complete",
+                               "h3.open.onsuccess"
+                               ].toString());
+        test5();
+    }
+}
+
+function test5() {
+    debug("");
+    debug("TEST: open blocked if a VERSION_CHANGE transaction is running");
+    evalAndLog("window.dbname = 'test5'; window.ver = 1; window.steps = []");
+    var h1 = new Connection("h1");
+    var h2 = new Connection("h2");
+
+    runSteps([function(doNext) { h1.open({onsuccess: doNext}); },
+              function(doNext) { h1.setVersion(); doNext(); },
+              function(doNext) { h2.open({onsuccess: finishTest}); }
+              ]);
+
+    function finishTest() {
+        shouldBeEqualToString("window.steps.toString()",
+                              ["h1.open",
+                               "h1.open.onsuccess",
+                               "h1.setVersion",
+                               "h2.open",
+                               "h1.setVersion.onsuccess",
+                               "h1.setVersion.transaction-complete",
+                               "h2.open.onsuccess"
+                               ].toString());
+        test6();
+    }
+}
+
+
+function test6() {
+    debug("");
+    debug("TEST: two setVersions from the same connection");
+    evalAndLog("window.dbname = 'test6'; window.ver = 1; window.steps = []");
+    var h1 = new Connection("h1");
+
+    runSteps([function(doNext) { h1.open({onsuccess: doNext}); },                                  
+              function(doNext) { h1.setVersion({onsuccess: halfDone}); 
+                                 h1.setVersion({onsuccess: halfDone}); }
+              ]);
+
+    var counter = 0;
+    function halfDone() {
+        counter++;
+        if (counter < 2) {
+            debug('half done');
+        } else {
+            finishTest();
+        }
+    }  
+
+    function finishTest() {
+        shouldBeEqualToString("window.steps.toString()",
+                              ["h1.open",
+                               "h1.open.onsuccess",
+                               "h1.setVersion",
+                               "h1.setVersion",
+                               "h1.setVersion.onsuccess",
+                               "h1.setVersion.transaction-complete",
+                               "h1.setVersion.onsuccess",
+                               "h1.setVersion.transaction-complete"
+                               ].toString());
+        done();
+    }
+}
+
+var successfullyParsed = true;
+
+test();
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/storage/indexeddb/two-version-changes-expected.txt (0 => 97417)


--- trunk/LayoutTests/storage/indexeddb/two-version-changes-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/two-version-changes-expected.txt	2011-10-13 23:08:13 UTC (rev 97417)
@@ -0,0 +1,40 @@
+Test behavior when the same connection calls setVersion twice
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
+IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.mozIDBTransaction;
+PASS indexedDB == null is false
+PASS IDBTransaction == null is false
+window.state = 'starting'
+indexedDB.open('two-versions-one-connection')
+db = event.target.result
+window.state = 0
+db.setVersion('version 1')
+db.setVersion('version 2')
+setVersion() #1 callback
+window.store1 = db.createObjectStore('test-store1')
+PASS ++window.state is 1
+window.store1.put('aaa', 111)
+PASS ++window.state is 2
+PASS ++window.state is 3
+halfway there...
+setVersion() #2 callback
+PASS ++window.state is 4
+window.store2 = db.createObjectStore('test-store2')
+window.store2.put('bbb', 222)
+PASS ++window.state is 5
+PASS ++window.state is 6
+PASS window.db.version is "version 2"
+window.trans = db.transaction(['test-store1', 'test-store2'])
+window.store = window.trans.objectStore('test-store1')
+window.req = window.store.get(111)
+PASS event.target.result is "aaa"
+window.store = window.trans.objectStore('test-store2')
+window.req = window.store.get(222)
+PASS event.target.result is "bbb"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/storage/indexeddb/two-version-changes.html (0 => 97417)


--- trunk/LayoutTests/storage/indexeddb/two-version-changes.html	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/two-version-changes.html	2011-10-13 23:08:13 UTC (rev 97417)
@@ -0,0 +1,116 @@
+<html>
+<head>
+<link rel="stylesheet" href=""
+<script src=""
+<script src=""
+<script src=""
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+
+description("Test behavior when the same connection calls setVersion twice");
+if (window.layoutTestController)
+    layoutTestController.waitUntilDone();
+
+
+function test() {
+    indexedDB = evalAndLog("indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;");
+    IDBTransaction = evalAndLog("IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.mozIDBTransaction;");
+
+    shouldBeFalse("indexedDB == null");
+    shouldBeFalse("IDBTransaction == null");
+    openDBConnection();
+}
+
+function openDBConnection()
+{
+    evalAndLog("window.state = 'starting'");
+    var request = evalAndLog("indexedDB.open('two-versions-one-connection')");
+    request._onsuccess_ = openSuccess;
+    request._onerror_ = unexpectedErrorCallback;
+}
+
+function openSuccess()
+{
+    window.db = evalAndLog("db = event.target.result");
+    evalAndLog("window.state = 0");
+
+    var versionChangeRequest1 = evalAndLog("db.setVersion('version 1')");
+    versionChangeRequest1._onerror_ = unexpectedErrorCallback;
+    versionChangeRequest1._onsuccess_ = inSetVersion1;
+
+    // and concurrently...
+
+    var versionChangeRequest2 = evalAndLog("db.setVersion('version 2')");
+    versionChangeRequest2._onerror_ = unexpectedErrorCallback;
+    versionChangeRequest2._onsuccess_ = inSetVersion2;
+}
+
+function inSetVersion1()
+{
+    debug("setVersion() #1 callback");
+    evalAndLog("window.store1 = db.createObjectStore('test-store1')");
+    shouldBe("++window.state", "1");
+    var req = evalAndLog("window.store1.put('aaa', 111)");
+    req._onerror_ = unexpectedErrorCallback;
+    req._onsuccess_ = function (e) {
+        shouldBe("++window.state", "2");
+        halfwayDone();
+    };
+}
+
+function inSetVersion2()
+{
+    debug("setVersion() #2 callback");
+    shouldBe("++window.state", "4");
+    evalAndLog("window.store2 = db.createObjectStore('test-store2')");
+
+    var req = evalAndLog("window.store2.put('bbb', 222)");
+    req._onerror_ = unexpectedErrorCallback;
+    req._onsuccess_ = function (e) {
+        shouldBe("++window.state", "5");
+        halfwayDone();
+    };
+}
+
+var counter = 0;
+function halfwayDone() 
+{
+    counter += 1;
+    if (counter < 2) {
+        shouldBe("++window.state", "3");
+        debug("halfway there..." );
+    } else {
+        shouldBe("++window.state", "6");
+        checkResults();
+    }
+}
+
+function checkResults() {
+    shouldBeEqualToString("window.db.version", "version 2");
+    trans = evalAndLog("window.trans = db.transaction(['test-store1', 'test-store2'])");
+    store = evalAndLog("window.store = window.trans.objectStore('test-store1')");
+    req = evalAndLog("window.req = window.store.get(111)");
+    req._onerror_ = unexpectedErrorCallback;
+    req._onsuccess_ = function (e) {
+        shouldBeEqualToString("event.target.result", "aaa");
+
+        store = evalAndLog("window.store = window.trans.objectStore('test-store2')");
+        req = evalAndLog("window.req = window.store.get(222)");
+        req._onerror_ = unexpectedErrorCallback;
+        req._onsuccess_ = function (e) {
+            shouldBeEqualToString("event.target.result", "bbb");
+            done();    
+        };
+    };
+}
+
+var successfullyParsed = true;
+
+test();
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/storage/indexeddb/version-change-exclusive-expected.txt (0 => 97417)


--- trunk/LayoutTests/storage/indexeddb/version-change-exclusive-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/version-change-exclusive-expected.txt	2011-10-13 23:08:13 UTC (rev 97417)
@@ -0,0 +1,46 @@
+Ensure VERSION_CHANGE transaction doesn't run concurrently with other transactions
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
+PASS indexedDB == null is false
+window.state = 'starting'
+indexedDB.open('version-change-exclusive')
+db = event.target.result
+calling setVersion() - callback should run immediately
+db.setVersion('version 1')
+calling open() - callback should wait until VERSION_CHANGE transaction is complete
+indexedDB.open('version-change-exclusive')
+setVersion() callback
+starting work in VERSION_CHANGE transaction
+window.state = 'VERSION_CHANGE started'
+store = db.createObjectStore('test-store')
+store.put(0, 0)
+in put's onsuccess
+store.put(1, 1)
+in put's onsuccess
+store.put(2, 2)
+in put's onsuccess
+store.put(3, 3)
+in put's onsuccess
+store.put(4, 4)
+in put's onsuccess
+store.put(5, 5)
+in put's onsuccess
+store.put(6, 6)
+in put's onsuccess
+store.put(7, 7)
+in put's onsuccess
+store.put(8, 8)
+in put's onsuccess
+store.put(9, 9)
+in put's onsuccess
+ending work in VERSION_CHANGE transaction
+window.state = 'VERSION_CHANGE finished'
+open() callback - this should appear after VERSION_CHANGE transaction ends
+PASS window.state is "VERSION_CHANGE finished"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/storage/indexeddb/version-change-exclusive.html (0 => 97417)


--- trunk/LayoutTests/storage/indexeddb/version-change-exclusive.html	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/version-change-exclusive.html	2011-10-13 23:08:13 UTC (rev 97417)
@@ -0,0 +1,87 @@
+<html>
+<head>
+<link rel="stylesheet" href=""
+<script src=""
+<script src=""
+<script src=""
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+
+description("Ensure VERSION_CHANGE transaction doesn't run concurrently with other transactions");
+if (window.layoutTestController)
+    layoutTestController.waitUntilDone();
+
+
+function test() {
+    indexedDB = evalAndLog("indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;");
+    shouldBeFalse("indexedDB == null");
+    openDBConnection();
+}
+
+function openDBConnection()
+{
+    evalAndLog("window.state = 'starting'");
+    request = evalAndLog("indexedDB.open('version-change-exclusive')");
+    request._onsuccess_ = openSuccess;
+    request._onerror_ = unexpectedErrorCallback;
+}
+
+function openSuccess()
+{
+    window.db = evalAndLog("db = event.target.result");
+
+    debug("calling setVersion() - callback should run immediately");
+    var versionChangeRequest = evalAndLog("db.setVersion('version 1')");
+    versionChangeRequest._onerror_ = unexpectedErrorCallback;
+    versionChangeRequest._onsuccess_ = inSetVersion;
+
+    // and concurrently...
+
+    debug("calling open() - callback should wait until VERSION_CHANGE transaction is complete");
+    var openRequest = evalAndLog("indexedDB.open('version-change-exclusive')");
+    openRequest._onsuccess_ = openAgainSuccess;
+    openRequest._onerror_ = unexpectedErrorCallback;
+}
+
+function inSetVersion()
+{
+    debug("setVersion() callback");
+    debug("starting work in VERSION_CHANGE transaction");
+    evalAndLog("window.state = 'VERSION_CHANGE started'");
+
+    window.store = evalAndLog("store = db.createObjectStore('test-store')");
+    window.count = 0;
+    do_async_puts();
+
+    function do_async_puts() {
+        var req = evalAndLog("store.put(" + count + ", " + count + ")");
+        req._onerror_ = unexpectedErrorCallback;
+        req._onsuccess_ = function (e) {
+            debug("in put's onsuccess");
+            if (++window.count < 10) {
+                do_async_puts();
+            } else {
+                debug("ending work in VERSION_CHANGE transaction");
+                evalAndLog("window.state = 'VERSION_CHANGE finished'");
+            }
+        };
+    }
+}
+
+function openAgainSuccess()
+{
+    debug("open() callback - this should appear after VERSION_CHANGE transaction ends");
+    shouldBeEqualToString("window.state", "VERSION_CHANGE finished");
+    done();
+}
+
+var successfullyParsed = true;
+
+test();
+
+</script>
+</body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (97416 => 97417)


--- trunk/Source/WebCore/ChangeLog	2011-10-13 22:54:02 UTC (rev 97416)
+++ trunk/Source/WebCore/ChangeLog	2011-10-13 23:08:13 UTC (rev 97417)
@@ -1,3 +1,36 @@
+2011-10-13  Joshua Bell  <jsb...@chromium.org>
+
+        make IDBFactory.open wait for pending setVersion transactions to complete
+        https://bugs.webkit.org/show_bug.cgi?id=69307
+
+        Reviewed by Tony Chang.
+
+        Added a queue of pending open calls, similar to the queue of pending
+        setVersion calls. Ensure pending calls are processed in the correct
+        order when transactions complete.
+
+        Tests: storage/indexeddb/open-close-version.html
+               storage/indexeddb/two-version-changes.html
+               storage/indexeddb/version-change-exclusive.html
+
+        * storage/IDBDatabaseBackendImpl.cpp:
+        (WebCore::IDBDatabaseBackendImpl::PendingOpenCall::create):
+        (WebCore::IDBDatabaseBackendImpl::PendingOpenCall::callbacks):
+        (WebCore::IDBDatabaseBackendImpl::PendingOpenCall::PendingOpenCall):
+        (WebCore::IDBDatabaseBackendImpl::setVersion):
+        (WebCore::IDBDatabaseBackendImpl::transactionStarted):
+        (WebCore::IDBDatabaseBackendImpl::transactionFinished):
+        (WebCore::IDBDatabaseBackendImpl::processPendingCalls):
+        (WebCore::IDBDatabaseBackendImpl::openConnection):
+        (WebCore::IDBDatabaseBackendImpl::close):
+        * storage/IDBDatabaseBackendImpl.h:
+        * storage/IDBFactoryBackendImpl.cpp:
+        (WebCore::IDBFactoryBackendImpl::open):
+        * storage/IDBTransactionBackendImpl.cpp:
+        (WebCore::IDBTransactionBackendImpl::abort):
+        (WebCore::IDBTransactionBackendImpl::start):
+        (WebCore::IDBTransactionBackendImpl::commit):
+
 2011-10-13  Chris Marrin  <cmar...@apple.com>
 
         Fix Leopard build

Modified: trunk/Source/WebCore/storage/IDBDatabaseBackendImpl.cpp (97416 => 97417)


--- trunk/Source/WebCore/storage/IDBDatabaseBackendImpl.cpp	2011-10-13 22:54:02 UTC (rev 97416)
+++ trunk/Source/WebCore/storage/IDBDatabaseBackendImpl.cpp	2011-10-13 23:08:13 UTC (rev 97417)
@@ -39,6 +39,22 @@
 
 namespace WebCore {
 
+class IDBDatabaseBackendImpl::PendingOpenCall : public RefCounted<PendingOpenCall> {
+public:
+    static PassRefPtr<PendingOpenCall> create(PassRefPtr<IDBCallbacks> callbacks)
+    {
+        return adoptRef(new PendingOpenCall(callbacks));
+    }
+    PassRefPtr<IDBCallbacks> callbacks() { return m_callbacks; }
+
+private:
+    PendingOpenCall(PassRefPtr<IDBCallbacks> callbacks)
+        : m_callbacks(callbacks)
+    {
+    }
+    RefPtr<IDBCallbacks> m_callbacks;
+};
+
 class IDBDatabaseBackendImpl::PendingSetVersionCall : public RefCounted<PendingSetVersionCall> {
 public:
     static PassRefPtr<PendingSetVersionCall> create(const String& version, PassRefPtr<IDBCallbacks> callbacks, PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks)
@@ -174,12 +190,20 @@
         if (*it != databaseCallbacks)
             (*it)->onVersionChange(version);
     }
+    // FIXME: Only fire onBlocked if there are open connections after the
+    // VersionChangeEvents are received, not just set up to fire.
+    // http://crbug.com/100123
     if (m_databaseCallbacksSet.size() > 1) {
         callbacks->onBlocked();
         RefPtr<PendingSetVersionCall> pendingSetVersionCall = PendingSetVersionCall::create(version, callbacks, databaseCallbacks);
         m_pendingSetVersionCalls.append(pendingSetVersionCall);
         return;
     }
+    if (m_runningVersionChangeTransaction) {
+        RefPtr<PendingSetVersionCall> pendingSetVersionCall = PendingSetVersionCall::create(version, callbacks, databaseCallbacks);
+        m_pendingSetVersionCalls.append(pendingSetVersionCall);
+        return;
+    }
 
     RefPtr<DOMStringList> objectStoreNames = DOMStringList::create();
     RefPtr<IDBDatabaseBackendImpl> database = this;
@@ -203,6 +227,43 @@
     callbacks->onSuccess(transaction);
 }
 
+void IDBDatabaseBackendImpl::transactionStarted(PassRefPtr<IDBTransactionBackendInterface> prpTransaction)
+{
+    RefPtr<IDBTransactionBackendInterface> transaction = prpTransaction;
+    if (transaction->mode() == IDBTransaction::VERSION_CHANGE) {
+        ASSERT(!m_runningVersionChangeTransaction);
+        m_runningVersionChangeTransaction = transaction;
+    }
+}
+
+void IDBDatabaseBackendImpl::transactionFinished(PassRefPtr<IDBTransactionBackendInterface> prpTransaction)
+{
+    RefPtr<IDBTransactionBackendInterface> transaction = prpTransaction;
+    if (transaction->mode() == IDBTransaction::VERSION_CHANGE) {
+        ASSERT(transaction.get() == m_runningVersionChangeTransaction.get());
+        m_runningVersionChangeTransaction.clear();
+        processPendingCalls();
+    }
+}
+
+void IDBDatabaseBackendImpl::processPendingCalls()
+{
+    // Pending calls may be requeued or aborted
+    Deque<RefPtr<PendingSetVersionCall> > pendingSetVersionCalls;
+    m_pendingSetVersionCalls.swap(pendingSetVersionCalls);
+    while (!pendingSetVersionCalls.isEmpty()) {
+        ExceptionCode ec = 0;
+        RefPtr<PendingSetVersionCall> pendingSetVersionCall = pendingSetVersionCalls.takeFirst();
+        setVersion(pendingSetVersionCall->version(), pendingSetVersionCall->callbacks(), pendingSetVersionCall->databaseCallbacks(), ec);
+        ASSERT(!ec);
+    }
+
+    while (!m_runningVersionChangeTransaction && m_pendingSetVersionCalls.isEmpty() && !m_pendingOpenCalls.isEmpty()) {
+        RefPtr<PendingOpenCall> pendingOpenCall = m_pendingOpenCalls.takeFirst();
+        openConnection(pendingOpenCall->callbacks());
+    }
+}
+
 PassRefPtr<IDBTransactionBackendInterface> IDBDatabaseBackendImpl::transaction(DOMStringList* objectStoreNames, unsigned short mode, ExceptionCode& ec)
 {
     for (size_t i = 0; i < objectStoreNames->length(); ++i) {
@@ -221,6 +282,14 @@
     m_databaseCallbacksSet.add(RefPtr<IDBDatabaseCallbacks>(callbacks));
 }
 
+void IDBDatabaseBackendImpl::openConnection(PassRefPtr<IDBCallbacks> callbacks)
+{
+    if (m_runningVersionChangeTransaction || !m_pendingSetVersionCalls.isEmpty())
+        m_pendingOpenCalls.append(PendingOpenCall::create(callbacks));
+    else
+        callbacks->onSuccess(this);
+}
+
 void IDBDatabaseBackendImpl::close(PassRefPtr<IDBDatabaseCallbacks> prpCallbacks)
 {
     RefPtr<IDBDatabaseCallbacks> callbacks = prpCallbacks;
@@ -229,12 +298,7 @@
     if (m_databaseCallbacksSet.size() > 1)
         return;
 
-    while (!m_pendingSetVersionCalls.isEmpty()) {
-        ExceptionCode ec = 0;
-        RefPtr<PendingSetVersionCall> pendingSetVersionCall = m_pendingSetVersionCalls.takeFirst();
-        setVersion(pendingSetVersionCall->version(), pendingSetVersionCall->callbacks(), pendingSetVersionCall->databaseCallbacks(), ec);
-        ASSERT(!ec);
-    }
+    processPendingCalls();
 }
 
 void IDBDatabaseBackendImpl::loadObjectStores()

Modified: trunk/Source/WebCore/storage/IDBDatabaseBackendImpl.h (97416 => 97417)


--- trunk/Source/WebCore/storage/IDBDatabaseBackendImpl.h	2011-10-13 22:54:02 UTC (rev 97416)
+++ trunk/Source/WebCore/storage/IDBDatabaseBackendImpl.h	2011-10-13 23:08:13 UTC (rev 97417)
@@ -54,7 +54,10 @@
 
     static const int64_t InvalidId = 0;
     int64_t id() const { return m_id; }
+
+    // FIXME: Rename "open" to something more descriptive, like registerFrontEndCallbacks.
     void open(PassRefPtr<IDBDatabaseCallbacks>);
+    void openConnection(PassRefPtr<IDBCallbacks>);
 
     virtual String name() const { return m_name; }
     virtual String version() const { return m_version; }
@@ -68,11 +71,14 @@
 
     PassRefPtr<IDBObjectStoreBackendInterface> objectStore(const String& name);
     IDBTransactionCoordinator* transactionCoordinator() const { return m_transactionCoordinator.get(); }
+    void transactionStarted(PassRefPtr<IDBTransactionBackendInterface>);
+    void transactionFinished(PassRefPtr<IDBTransactionBackendInterface>);
 
 private:
     IDBDatabaseBackendImpl(const String& name, IDBBackingStore* database, IDBTransactionCoordinator*, IDBFactoryBackendImpl*, const String& uniqueIdentifier);
 
     void loadObjectStores();
+    void processPendingCalls();
 
     static void createObjectStoreInternal(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl>, PassRefPtr<IDBObjectStoreBackendImpl>, PassRefPtr<IDBTransactionBackendInterface>);
     static void deleteObjectStoreInternal(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl>, PassRefPtr<IDBObjectStoreBackendImpl>, PassRefPtr<IDBTransactionBackendInterface>);
@@ -96,10 +102,14 @@
     ObjectStoreMap m_objectStores;
 
     RefPtr<IDBTransactionCoordinator> m_transactionCoordinator;
+    RefPtr<IDBTransactionBackendInterface> m_runningVersionChangeTransaction;
 
     class PendingSetVersionCall;
     Deque<RefPtr<PendingSetVersionCall> > m_pendingSetVersionCalls;
 
+    class PendingOpenCall;
+    Deque<RefPtr<PendingOpenCall> > m_pendingOpenCalls;
+
     typedef ListHashSet<RefPtr<IDBDatabaseCallbacks> > DatabaseCallbacksSet;
     DatabaseCallbacksSet m_databaseCallbacksSet;
 };

Modified: trunk/Source/WebCore/storage/IDBFactoryBackendImpl.cpp (97416 => 97417)


--- trunk/Source/WebCore/storage/IDBFactoryBackendImpl.cpp	2011-10-13 22:54:02 UTC (rev 97416)
+++ trunk/Source/WebCore/storage/IDBFactoryBackendImpl.cpp	2011-10-13 23:08:13 UTC (rev 97417)
@@ -109,7 +109,9 @@
 
     IDBDatabaseBackendMap::iterator it = m_databaseBackendMap.find(uniqueIdentifier);
     if (it != m_databaseBackendMap.end()) {
-        callbacks->onSuccess(it->second);
+        // If it's already been opened, we have to wait for any pending
+        // setVersion calls to complete.
+        it->second->openConnection(callbacks);
         return;
     }
 

Modified: trunk/Source/WebCore/storage/IDBTransactionBackendImpl.cpp (97416 => 97417)


--- trunk/Source/WebCore/storage/IDBTransactionBackendImpl.cpp	2011-10-13 22:54:02 UTC (rev 97416)
+++ trunk/Source/WebCore/storage/IDBTransactionBackendImpl.cpp	2011-10-13 23:08:13 UTC (rev 97417)
@@ -131,6 +131,7 @@
     m_callbacks->onAbort();
     m_database->transactionCoordinator()->didFinishTransaction(this);
     ASSERT(!m_database->transactionCoordinator()->isActive(this));
+    m_database->transactionFinished(this);
     m_database = 0;
 }
 
@@ -171,6 +172,7 @@
 
     m_state = StartPending;
     m_database->transactionCoordinator()->didStartTransaction(this);
+    m_database->transactionStarted(this);
 }
 
 void IDBTransactionBackendImpl::commit()
@@ -186,6 +188,7 @@
     m_transaction->commit();
     m_callbacks->onComplete();
     m_database->transactionCoordinator()->didFinishTransaction(this);
+    m_database->transactionFinished(this);
     m_database = 0;
 }
 
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to