Title: [126254] trunk
Revision
126254
Author
[email protected]
Date
2012-08-21 19:43:47 -0700 (Tue, 21 Aug 2012)

Log Message

IndexedDB: IDBRequest can be GCd during event dispatch
https://bugs.webkit.org/show_bug.cgi?id=94235

Reviewed by Ojan Vafai.

Source/WebCore:

Avoid a "race" where GC may attempt to reclaim IDB objects that are marked
"done" prior to the completion of the event dispatch. The script runtime
may decide to do a GC pass before calling the event handler, releasing the
object and turning the dispatch into a no-op.

This is a partial reversion (with renames, etc) of r123275, r124842,
and r121492. Added a new test, although it does not exercise the "race"
condition directly.

Test: storage/indexeddb/pending-activity.html
      storage/indexeddb/pending-activity-workers.html

* Modules/indexeddb/IDBCursor.cpp:
(WebCore::IDBCursor::close): Let the IDBRequest know it this cursor won't
make it fire again.
* Modules/indexeddb/IDBRequest.cpp:
(WebCore::IDBRequest::IDBRequest): Reintroduce "am I done?" flag.
(WebCore::IDBRequest::finishCursor): Cursors may fire events at the same
IDBRequest repeatedly, so we need to know when they're are really done.
(WebCore):
(WebCore::IDBRequest::hasPendingActivity): Test the flag.
(WebCore::IDBRequest::dispatchEvent): Set the flag.
* Modules/indexeddb/IDBRequest.h:
(IDBRequest):
* Modules/indexeddb/IDBTransaction.cpp:
(WebCore::IDBTransaction::IDBTransaction): Reintroduce "am I done?" flag.
(WebCore::IDBTransaction::hasPendingActivity): Test the flag.
(WebCore::IDBTransaction::dispatchEvent): Set the flag.
* Modules/indexeddb/IDBTransaction.h:

LayoutTests:

Release references to IDBRequest and IDBTransaction objects and force GC,
to ensure that pending events are still fired. (Doesn't exercise race
condition where GC is triggered by script during dispatch itself, though.)

* storage/indexeddb/pending-activity-expected.txt: Added.
* storage/indexeddb/pending-activity-workers-expected.txt: Added.
* storage/indexeddb/pending-activity-workers.html: Added.
* storage/indexeddb/pending-activity.html: Added.
* storage/indexeddb/resources/pending-activity.js: Added.
(test):
(prepareDatabase.request.onsuccess.request.onsuccess.request.onsuccess):
(prepareDatabase.request.onsuccess.request.onsuccess):
(prepareDatabase.request.onsuccess):
(prepareDatabase):
(testTransaction):
(transactionOnComplete):
(testRequest):
(requestOnSuccess):
(testCursorRequest):
(cursorRequestOnFirstSuccess):
(cursorRequestOnSecondSuccess):

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (126253 => 126254)


--- trunk/LayoutTests/ChangeLog	2012-08-22 02:39:50 UTC (rev 126253)
+++ trunk/LayoutTests/ChangeLog	2012-08-22 02:43:47 UTC (rev 126254)
@@ -1,3 +1,32 @@
+2012-08-21  Joshua Bell  <[email protected]>
+
+        IndexedDB: IDBRequest can be GCd during event dispatch
+        https://bugs.webkit.org/show_bug.cgi?id=94235
+
+        Reviewed by Ojan Vafai.
+
+        Release references to IDBRequest and IDBTransaction objects and force GC,
+        to ensure that pending events are still fired. (Doesn't exercise race
+        condition where GC is triggered by script during dispatch itself, though.)
+
+        * storage/indexeddb/pending-activity-expected.txt: Added.
+        * storage/indexeddb/pending-activity-workers-expected.txt: Added.
+        * storage/indexeddb/pending-activity-workers.html: Added.
+        * storage/indexeddb/pending-activity.html: Added.
+        * storage/indexeddb/resources/pending-activity.js: Added.
+        (test):
+        (prepareDatabase.request.onsuccess.request.onsuccess.request.onsuccess):
+        (prepareDatabase.request.onsuccess.request.onsuccess):
+        (prepareDatabase.request.onsuccess):
+        (prepareDatabase):
+        (testTransaction):
+        (transactionOnComplete):
+        (testRequest):
+        (requestOnSuccess):
+        (testCursorRequest):
+        (cursorRequestOnFirstSuccess):
+        (cursorRequestOnSecondSuccess):
+
 2012-08-21  Keishi Hattori  <[email protected]>
 
         range-hit-test-with-padding.html fails unless subpixel layout is on

Added: trunk/LayoutTests/storage/indexeddb/pending-activity-expected.txt (0 => 126254)


--- trunk/LayoutTests/storage/indexeddb/pending-activity-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/pending-activity-expected.txt	2012-08-22 02:43:47 UTC (rev 126254)
@@ -0,0 +1,51 @@
+Checks that garbage collection doesn't reclaim objects with pending activity
+
+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 = "pending-activity.html"
+
+prepareDatabase():
+indexedDB.deleteDatabase(dbname)
+indexedDB.open(dbname)
+db = request.result
+PASS db.version is ""
+db.setVersion('1')
+store = db.createObjectStore('store')
+store.put(0, 0)
+
+testTransaction():
+transaction = db.transaction('store')
+transaction._oncomplete_ = transactionOnComplete
+transaction = null
+self.gc()
+PASS Transaction 'complete' event fired.
+
+testRequest():
+transaction = db.transaction('store')
+store = transaction.objectStore('store')
+request = store.get(0)
+request._onsuccess_ = requestOnSuccess
+request = null
+self.gc()
+PASS Request 'success' event fired.
+
+testCursorRequest():
+transaction = db.transaction('store')
+store = transaction.objectStore('store')
+request = store.openCursor()
+request._onsuccess_ = cursorRequestOnFirstSuccess
+PASS Request 'success' event fired.
+cursor = request.result
+cursor.continue()
+request._onsuccess_ = cursorRequestOnSecondSuccess
+request = null
+self.gc()
+PASS Request 'success' event fired.
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/storage/indexeddb/pending-activity-workers-expected.txt (0 => 126254)


--- trunk/LayoutTests/storage/indexeddb/pending-activity-workers-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/pending-activity-workers-expected.txt	2012-08-22 02:43:47 UTC (rev 126254)
@@ -0,0 +1,52 @@
+[Worker] Checks that garbage collection doesn't reclaim objects with pending activity
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Starting worker: resources/pending-activity.js
+[Worker] indexedDB = self.indexedDB || self.webkitIndexedDB || self.mozIndexedDB || self.msIndexedDB || self.OIndexedDB;
+[Worker] 
+[Worker] dbname = "pending-activity.js"
+[Worker] 
+[Worker] prepareDatabase():
+[Worker] indexedDB.deleteDatabase(dbname)
+[Worker] indexedDB.open(dbname)
+[Worker] db = request.result
+PASS [Worker] db.version is ""
+[Worker] db.setVersion('1')
+[Worker] store = db.createObjectStore('store')
+[Worker] store.put(0, 0)
+[Worker] 
+[Worker] testTransaction():
+[Worker] transaction = db.transaction('store')
+[Worker] transaction._oncomplete_ = transactionOnComplete
+[Worker] transaction = null
+[Worker] self.gc()
+PASS [Worker] Transaction 'complete' event fired.
+[Worker] 
+[Worker] testRequest():
+[Worker] transaction = db.transaction('store')
+[Worker] store = transaction.objectStore('store')
+[Worker] request = store.get(0)
+[Worker] request._onsuccess_ = requestOnSuccess
+[Worker] request = null
+[Worker] self.gc()
+PASS [Worker] Request 'success' event fired.
+[Worker] 
+[Worker] testCursorRequest():
+[Worker] transaction = db.transaction('store')
+[Worker] store = transaction.objectStore('store')
+[Worker] request = store.openCursor()
+[Worker] request._onsuccess_ = cursorRequestOnFirstSuccess
+PASS [Worker] Request 'success' event fired.
+[Worker] cursor = request.result
+[Worker] cursor.continue()
+[Worker] request._onsuccess_ = cursorRequestOnSecondSuccess
+[Worker] request = null
+[Worker] self.gc()
+PASS [Worker] Request 'success' event fired.
+[Worker] 
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/storage/indexeddb/pending-activity-workers.html (0 => 126254)


--- trunk/LayoutTests/storage/indexeddb/pending-activity-workers.html	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/pending-activity-workers.html	2012-08-22 02:43:47 UTC (rev 126254)
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<script src=""
+<script src=""
+<body>
+<script>startWorker('resources/pending-activity.js');</script>
+<script src=""
+</body>

Added: trunk/LayoutTests/storage/indexeddb/pending-activity.html (0 => 126254)


--- trunk/LayoutTests/storage/indexeddb/pending-activity.html	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/pending-activity.html	2012-08-22 02:43:47 UTC (rev 126254)
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<script src=""
+<script src=""
+<script src=""
+<script src=""

Added: trunk/LayoutTests/storage/indexeddb/resources/pending-activity.js (0 => 126254)


--- trunk/LayoutTests/storage/indexeddb/resources/pending-activity.js	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/resources/pending-activity.js	2012-08-22 02:43:47 UTC (rev 126254)
@@ -0,0 +1,97 @@
+if (this.importScripts) {
+    importScripts('../../../fast/js/resources/js-test-pre.js');
+    importScripts('shared.js');
+}
+
+description("Checks that garbage collection doesn't reclaim objects with pending activity");
+
+function test()
+{
+    removeVendorPrefixes();
+    setDBNameFromPath();
+    prepareDatabase();
+}
+
+function prepareDatabase()
+{
+    preamble();
+    request = evalAndLog("indexedDB.deleteDatabase(dbname)");
+    request._onerror_ = unexpectedErrorCallback;
+    request._onsuccess_ = function(e) {
+        request = evalAndLog("indexedDB.open(dbname)");
+        request._onerror_ = unexpectedErrorCallback;
+        request._onsuccess_ = function(e) {
+            evalAndLog("db = request.result");
+            shouldBeEqualToString("db.version", "");
+            request = evalAndLog("db.setVersion('1')");
+            request._onerror_ = unexpectedErrorCallback;
+            request._onsuccess_ = function(e) {
+                trans = request.result;
+                evalAndLog("store = db.createObjectStore('store')");
+                evalAndLog("store.put(0, 0)");
+                trans._oncomplete_ = testTransaction;
+            };
+        };
+    };
+}
+
+function testTransaction()
+{
+    preamble();
+    evalAndLog("transaction = db.transaction('store')");
+    evalAndLog("transaction._oncomplete_ = transactionOnComplete");
+    evalAndLog("transaction = null");
+    evalAndLog("self.gc()");
+}
+
+function transactionOnComplete()
+{
+    testPassed("Transaction 'complete' event fired.");
+    testRequest();
+}
+
+function testRequest()
+{
+    preamble();
+    evalAndLog("transaction = db.transaction('store')");
+    evalAndLog("store = transaction.objectStore('store')");
+    evalAndLog("request = store.get(0)");
+    evalAndLog("request._onsuccess_ = requestOnSuccess");
+    evalAndLog("request = null");
+    evalAndLog("self.gc()");
+}
+
+function requestOnSuccess()
+{
+    testPassed("Request 'success' event fired.");
+    testCursorRequest();
+}
+
+function testCursorRequest()
+{
+    preamble();
+    evalAndLog("transaction = db.transaction('store')");
+    evalAndLog("store = transaction.objectStore('store')");
+    evalAndLog("request = store.openCursor()");
+    evalAndLog("request._onsuccess_ = cursorRequestOnFirstSuccess");
+}
+
+function cursorRequestOnFirstSuccess()
+{
+    testPassed("Request 'success' event fired.");
+    evalAndLog("cursor = request.result");
+    evalAndLog("cursor.continue()");
+    evalAndLog("request._onsuccess_ = cursorRequestOnSecondSuccess");
+    evalAndLog("request = null");
+    evalAndLog("self.gc()");
+}
+
+function cursorRequestOnSecondSuccess()
+{
+    testPassed("Request 'success' event fired.");
+
+    debug("");
+    finishJSTest();
+}
+
+test();

Modified: trunk/Source/WebCore/ChangeLog (126253 => 126254)


--- trunk/Source/WebCore/ChangeLog	2012-08-22 02:39:50 UTC (rev 126253)
+++ trunk/Source/WebCore/ChangeLog	2012-08-22 02:43:47 UTC (rev 126254)
@@ -1,3 +1,40 @@
+2012-08-21  Joshua Bell  <[email protected]>
+
+        IndexedDB: IDBRequest can be GCd during event dispatch
+        https://bugs.webkit.org/show_bug.cgi?id=94235
+
+        Reviewed by Ojan Vafai.
+
+        Avoid a "race" where GC may attempt to reclaim IDB objects that are marked
+        "done" prior to the completion of the event dispatch. The script runtime
+        may decide to do a GC pass before calling the event handler, releasing the
+        object and turning the dispatch into a no-op.
+
+        This is a partial reversion (with renames, etc) of r123275, r124842,
+        and r121492. Added a new test, although it does not exercise the "race"
+        condition directly.
+
+        Test: storage/indexeddb/pending-activity.html
+              storage/indexeddb/pending-activity-workers.html
+
+        * Modules/indexeddb/IDBCursor.cpp:
+        (WebCore::IDBCursor::close): Let the IDBRequest know it this cursor won't
+        make it fire again.
+        * Modules/indexeddb/IDBRequest.cpp:
+        (WebCore::IDBRequest::IDBRequest): Reintroduce "am I done?" flag.
+        (WebCore::IDBRequest::finishCursor): Cursors may fire events at the same
+        IDBRequest repeatedly, so we need to know when they're are really done.
+        (WebCore):
+        (WebCore::IDBRequest::hasPendingActivity): Test the flag.
+        (WebCore::IDBRequest::dispatchEvent): Set the flag.
+        * Modules/indexeddb/IDBRequest.h:
+        (IDBRequest):
+        * Modules/indexeddb/IDBTransaction.cpp:
+        (WebCore::IDBTransaction::IDBTransaction): Reintroduce "am I done?" flag.
+        (WebCore::IDBTransaction::hasPendingActivity): Test the flag.
+        (WebCore::IDBTransaction::dispatchEvent): Set the flag.
+        * Modules/indexeddb/IDBTransaction.h:
+
 2012-08-21  Pavel Feldman  <[email protected]>
 
         Web Inspector: [regression] Settings panel fails to open.

Modified: trunk/Source/WebCore/Modules/indexeddb/IDBCursor.cpp (126253 => 126254)


--- trunk/Source/WebCore/Modules/indexeddb/IDBCursor.cpp	2012-08-22 02:39:50 UTC (rev 126253)
+++ trunk/Source/WebCore/Modules/indexeddb/IDBCursor.cpp	2012-08-22 02:43:47 UTC (rev 126254)
@@ -262,6 +262,7 @@
 void IDBCursor::close()
 {
     ASSERT(m_request);
+    m_request->finishCursor();
     m_request.clear();
 }
 

Modified: trunk/Source/WebCore/Modules/indexeddb/IDBRequest.cpp (126253 => 126254)


--- trunk/Source/WebCore/Modules/indexeddb/IDBRequest.cpp	2012-08-22 02:39:50 UTC (rev 126253)
+++ trunk/Source/WebCore/Modules/indexeddb/IDBRequest.cpp	2012-08-22 02:43:47 UTC (rev 126254)
@@ -68,8 +68,10 @@
     , m_requestAborted(false)
     , m_source(source)
     , m_taskType(taskType)
+    , m_hasPendingActivity(true)
     , m_cursorType(IDBCursorBackendInterface::InvalidCursorType)
     , m_cursorDirection(IDBCursor::NEXT)
+    , m_cursorFinished(false)
     , m_pendingCursor(0)
     , m_didFireUpgradeNeededEvent(false)
 {
@@ -224,6 +226,11 @@
     m_result = IDBAny::create(IDBCursorWithValue::fromCursor(cursor));
 }
 
+void IDBRequest::finishCursor()
+{
+    m_cursorFinished = true;
+}
+
 bool IDBRequest::shouldEnqueueEvent() const
 {
     if (m_contextStopped || !scriptExecutionContext())
@@ -404,7 +411,7 @@
     // FIXME: In an ideal world, we should return true as long as anyone has a or can
     //        get a handle to us and we have event listeners. This is order to handle
     //        user generated events properly.
-    return m_readyState == PENDING || ActiveDOMObject::hasPendingActivity();
+    return m_hasPendingActivity || ActiveDOMObject::hasPendingActivity();
 }
 
 void IDBRequest::stop()
@@ -438,6 +445,7 @@
     IDB_TRACE("IDBRequest::dispatchEvent");
     ASSERT(m_readyState == PENDING);
     ASSERT(!m_contextStopped);
+    ASSERT(m_hasPendingActivity);
     ASSERT(m_enqueuedEvents.size());
     ASSERT(scriptExecutionContext());
     ASSERT(event->target() == this);
@@ -488,6 +496,9 @@
     if (cursorToNotify)
         cursorToNotify->postSuccessHandlerCallback();
 
+    if (m_readyState == DONE && (!cursorToNotify || m_cursorFinished) && event->type() != eventNames().upgradeneededEvent)
+        m_hasPendingActivity = false;
+
     if (m_transaction) {
         if (event->type() == eventNames().errorEvent && dontPreventDefault && !m_requestAborted) {
             m_transaction->setError(m_error);

Modified: trunk/Source/WebCore/Modules/indexeddb/IDBRequest.h (126253 => 126254)


--- trunk/Source/WebCore/Modules/indexeddb/IDBRequest.h	2012-08-22 02:39:50 UTC (rev 126253)
+++ trunk/Source/WebCore/Modules/indexeddb/IDBRequest.h	2012-08-22 02:43:47 UTC (rev 126254)
@@ -77,6 +77,7 @@
     void markEarlyDeath();
     void setCursorDetails(IDBCursorBackendInterface::CursorType, IDBCursor::Direction);
     void setPendingCursor(PassRefPtr<IDBCursor>);
+    void finishCursor();
     void abort();
 
     // IDBCallbacks
@@ -138,11 +139,13 @@
     RefPtr<IDBAny> m_source;
     const IDBTransactionBackendInterface::TaskType m_taskType;
 
+    bool m_hasPendingActivity;
     Vector<RefPtr<Event> > m_enqueuedEvents;
 
     // Only used if the result type will be a cursor.
     IDBCursorBackendInterface::CursorType m_cursorType;
     IDBCursor::Direction m_cursorDirection;
+    bool m_cursorFinished;
     RefPtr<IDBCursor> m_pendingCursor;
     RefPtr<IDBKey> m_cursorKey;
     RefPtr<IDBKey> m_cursorPrimaryKey;

Modified: trunk/Source/WebCore/Modules/indexeddb/IDBTransaction.cpp (126253 => 126254)


--- trunk/Source/WebCore/Modules/indexeddb/IDBTransaction.cpp	2012-08-22 02:39:50 UTC (rev 126253)
+++ trunk/Source/WebCore/Modules/indexeddb/IDBTransaction.cpp	2012-08-22 02:43:47 UTC (rev 126254)
@@ -94,6 +94,7 @@
     , m_mode(mode)
     , m_active(true)
     , m_state(Unused)
+    , m_hasPendingActivity(true)
     , m_contextStopped(false)
 {
     ASSERT(m_backend);
@@ -321,7 +322,7 @@
     // FIXME: In an ideal world, we should return true as long as anyone has a or can
     //        get a handle to us or any child request object and any of those have
     //        event listeners. This is  in order to handle user generated events properly.
-    return m_state != Finished || ActiveDOMObject::hasPendingActivity();
+    return m_hasPendingActivity || ActiveDOMObject::hasPendingActivity();
 }
 
 IDBTransaction::Mode IDBTransaction::stringToMode(const String& modeString, ExceptionCode& ec)
@@ -370,6 +371,7 @@
 {
     IDB_TRACE("IDBTransaction::dispatchEvent");
     ASSERT(m_state != Finished);
+    ASSERT(m_hasPendingActivity);
     ASSERT(scriptExecutionContext());
     ASSERT(event->target() == this);
     m_state = Finished;
@@ -392,6 +394,7 @@
         ASSERT(isVersionChange());
         m_openDBRequest->transactionDidFinishAndDispatch();
     }
+    m_hasPendingActivity = false;
     return returnValue;
 }
 

Modified: trunk/Source/WebCore/Modules/indexeddb/IDBTransaction.h (126253 => 126254)


--- trunk/Source/WebCore/Modules/indexeddb/IDBTransaction.h	2012-08-22 02:39:50 UTC (rev 126253)
+++ trunk/Source/WebCore/Modules/indexeddb/IDBTransaction.h	2012-08-22 02:43:47 UTC (rev 126254)
@@ -148,6 +148,7 @@
     const Mode m_mode;
     bool m_active;
     State m_state;
+    bool m_hasPendingActivity;
     bool m_contextStopped;
     RefPtr<DOMError> m_error;
 
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to