IndexedDB 2.0: Queue up completed requests in the client, handle them one by one.
authorbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 29 Nov 2016 16:05:17 +0000 (16:05 +0000)
committerbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 29 Nov 2016 16:05:17 +0000 (16:05 +0000)
https://bugs.webkit.org/show_bug.cgi?id=165000

Reviewed by Alex Christensen.

Source/WebCore:

No new tests (Covered extensively by every existing test).

Currently when a TransactionOperation completes on the server, it immediately completes
itself on the client side, including scheduling an event dispatch if necessary.

This patch changes it so that "server completed operations" instead queue up in the
IDBTransaction and are "client-side completed" asynchronously, 1-by-1.

Currently this is a "no behavior change" because only one operation is ever sent to
the server at a time.

But that will change with https://webkit.org/b/164932
And this patch is a pre-requisite for that.

* Modules/indexeddb/IDBRequest.cpp:
(WebCore::IDBRequest::dispatchEvent):
(WebCore::IDBRequest::didOpenOrIterateCursor):
(WebCore::IDBRequest::completeRequestAndDispatchEvent):
(WebCore::IDBRequest::requestCompleted): Deleted.
* Modules/indexeddb/IDBRequest.h:

* Modules/indexeddb/IDBTransaction.cpp:
(WebCore::IDBTransaction::IDBTransaction):
(WebCore::IDBTransaction::internalAbort):
(WebCore::IDBTransaction::abortOnServerAndCancelRequests):
(WebCore::IDBTransaction::scheduleOperation):
(WebCore::IDBTransaction::schedulePendingOperationTimer):
(WebCore::IDBTransaction::pendingOperationTimerFired):
(WebCore::IDBTransaction::operationCompletedOnServer):
(WebCore::IDBTransaction::scheduleCompletedOperationTimer):
(WebCore::IDBTransaction::completedOperationTimerFired):
(WebCore::IDBTransaction::completeNoncursorRequest):
(WebCore::IDBTransaction::completeCursorRequest):
(WebCore::IDBTransaction::finishedDispatchEventForRequest):
(WebCore::IDBTransaction::didStart):
(WebCore::IDBTransaction::didOpenCursorOnServer):
(WebCore::IDBTransaction::didIterateCursorOnServer):
(WebCore::IDBTransaction::didGetAllRecordsOnServer):
(WebCore::IDBTransaction::didGetRecordOnServer):
(WebCore::IDBTransaction::didGetCountOnServer):
(WebCore::IDBTransaction::didDeleteRecordOnServer):
(WebCore::IDBTransaction::didClearObjectStoreOnServer):
(WebCore::IDBTransaction::putOrAddOnServer):
(WebCore::IDBTransaction::didPutOrAddOnServer):
(WebCore::IDBTransaction::operationCompletedOnClient):
(WebCore::IDBTransaction::deactivate):
(WebCore::IDBTransaction::connectionClosedFromServer):
(WebCore::IDBTransaction::scheduleOperationTimer): Deleted.
(WebCore::IDBTransaction::operationTimerFired): Deleted.
(WebCore::IDBTransaction::operationDidComplete): Deleted.
* Modules/indexeddb/IDBTransaction.h:

* Modules/indexeddb/client/IDBConnectionProxy.cpp:
(WebCore::IDBClient::IDBConnectionProxy::completeOperation):

* Modules/indexeddb/client/TransactionOperation.cpp:
(WebCore::IDBClient::TransactionOperation::TransactionOperation):
* Modules/indexeddb/client/TransactionOperation.h:
(WebCore::IDBClient::TransactionOperation::transitionToCompleteOnThisThread):
(WebCore::IDBClient::TransactionOperation::transitionToComplete):
(WebCore::IDBClient::TransactionOperation::doComplete):
(WebCore::IDBClient::TransactionOperation::idbRequest):
(WebCore::IDBClient::TransactionOperation::performCompleteOnOriginThread): Deleted.
(WebCore::IDBClient::TransactionOperation::completed): Deleted.

LayoutTests:

* storage/indexeddb/modern/resources/transaction-scheduler-6.js: This test had a bug which was masked by previously
  synchronous behavior. Fix that bug!

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

LayoutTests/ChangeLog
LayoutTests/storage/indexeddb/modern/resources/transaction-scheduler-6.js
Source/WebCore/ChangeLog
Source/WebCore/Modules/indexeddb/IDBRequest.cpp
Source/WebCore/Modules/indexeddb/IDBRequest.h
Source/WebCore/Modules/indexeddb/IDBTransaction.cpp
Source/WebCore/Modules/indexeddb/IDBTransaction.h
Source/WebCore/Modules/indexeddb/client/IDBConnectionProxy.cpp
Source/WebCore/Modules/indexeddb/client/TransactionOperation.cpp
Source/WebCore/Modules/indexeddb/client/TransactionOperation.h
Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.cpp

index aba0494..bcbed1a 100644 (file)
@@ -1,3 +1,13 @@
+2016-11-29  Brady Eidson  <beidson@apple.com>
+
+        IndexedDB 2.0: Queue up completed requests in the client, handle them one by one.
+        https://bugs.webkit.org/show_bug.cgi?id=165000
+
+        Reviewed by Alex Christensen.
+
+        * storage/indexeddb/modern/resources/transaction-scheduler-6.js: This test had a bug which was masked by previously
+          synchronous behavior. Fix that bug!
+
 2016-11-29  Zalan Bujtas  <zalan@apple.com>
 
         Safari (WebKit) doesn't wrap element within flex when width comes below min-width
index 3c8a352..4c73461 100644 (file)
@@ -3,6 +3,10 @@ It verifies that the read-write doesn't start until both read-onlys have finishe
 
 indexedDBTest(prepareDatabase);
 
+function log(msg)
+{
+       debug(msg);
+}
 
 function done()
 {
@@ -13,7 +17,7 @@ var database;
 
 function prepareDatabase(event)
 {
-    debug("Upgrade needed: Old version - " + event.oldVersion + " New version - " + event.newVersion);
+    log("Upgrade needed: Old version - " + event.oldVersion + " New version - " + event.newVersion);
 
     var versionTransaction = event.target.transaction;
     database = event.target.result;
@@ -21,22 +25,22 @@ function prepareDatabase(event)
     var request = objectStore.put("foo", "bar");
 
     request.onerror = function(event) {
-        debug("put FAILED - " + event);
+        log("put FAILED - " + event);
         done();
     }
     
     versionTransaction.onabort = function(event) {
-        debug("versionchange transaction aborted");
+        log("versionchange transaction aborted");
         done();
     }
 
     versionTransaction.oncomplete = function(event) {
-        debug("versionchange transaction completed");
+        log("versionchange transaction completed");
         continueTest();
     }
 
     versionTransaction.onerror = function(event) {
-        debug("versionchange transaction error'ed - " + event);
+        log("versionchange transaction error'ed - " + event);
         done();
     }
 }
@@ -51,26 +55,26 @@ function continueTest()
     var request = objectStore.put("baz", "foo");
 
     request.onsuccess = function(event) {
-        debug("Write in readwrite transaction succeeded");
+        log("Write in readwrite transaction succeeded");
     }
     
     request.onerror = function(event) {
-        debug("Write in readwrite transaction unexpectedly failed");
+        log("Write in readwrite transaction unexpectedly failed");
         done();
     }
     
     transaction.onabort = function(event) {
-        debug("readwrite transaction expectedly aborted");
+        log("readwrite transaction expectedly aborted");
         done();
     }
 
     transaction.oncomplete = function(event) {
-        debug("readwrite transaction completed");
+        log("readwrite transaction completed");
         done();
     }
 
     transaction.onerror = function(event) {
-        debug("readwrite transaction error'ed - " + event);
+        log("readwrite transaction error'ed - " + event);
         done();
     }
 }
@@ -84,7 +88,7 @@ function startTransactionLoop(transaction, isFirstTime)
     
     request.onsuccess = function(event) {
         if (isFirstTime) {
-            debug("Starting a readonly transaction");
+            log("Starting a readonly transaction");
             numberOfOpenTransactions++;
         }
         
@@ -95,24 +99,22 @@ function startTransactionLoop(transaction, isFirstTime)
     }
 
     request.onerror = function(event) {
-        debug("Unexpected request error - " + event);
+        log("Unexpected request error - " + event);
         done();
     }
 
     transaction.onerror = function(event) {
-        debug("Unexpected transaction error - " + event);
+        log("Unexpected transaction error - " + event);
         done();
     }
 
     transaction.onabort = function(event) {
-        --numberOfOpenTransactions;
-        debug("Unexpected transaction abort - " + event);
+        log("Unexpected transaction abort - " + event);
         done();
     }
 
     transaction.oncomplete = function(event) {
-        --numberOfOpenTransactions;
-        debug("readonly transaction completed");
+        log("readonly transaction completed");
     }
 }
 
index 7192b48..119ef51 100644 (file)
@@ -1,3 +1,75 @@
+2016-11-29  Brady Eidson  <beidson@apple.com>
+
+        IndexedDB 2.0: Queue up completed requests in the client, handle them one by one.
+        https://bugs.webkit.org/show_bug.cgi?id=165000
+
+        Reviewed by Alex Christensen.
+
+        No new tests (Covered extensively by every existing test).
+
+        Currently when a TransactionOperation completes on the server, it immediately completes
+        itself on the client side, including scheduling an event dispatch if necessary.
+        
+        This patch changes it so that "server completed operations" instead queue up in the 
+        IDBTransaction and are "client-side completed" asynchronously, 1-by-1.
+        
+        Currently this is a "no behavior change" because only one operation is ever sent to
+        the server at a time.
+        
+        But that will change with https://webkit.org/b/164932
+        And this patch is a pre-requisite for that.
+        
+        * Modules/indexeddb/IDBRequest.cpp:
+        (WebCore::IDBRequest::dispatchEvent):
+        (WebCore::IDBRequest::didOpenOrIterateCursor):
+        (WebCore::IDBRequest::completeRequestAndDispatchEvent):
+        (WebCore::IDBRequest::requestCompleted): Deleted.
+        * Modules/indexeddb/IDBRequest.h:
+        
+        * Modules/indexeddb/IDBTransaction.cpp:
+        (WebCore::IDBTransaction::IDBTransaction):
+        (WebCore::IDBTransaction::internalAbort):
+        (WebCore::IDBTransaction::abortOnServerAndCancelRequests):
+        (WebCore::IDBTransaction::scheduleOperation):
+        (WebCore::IDBTransaction::schedulePendingOperationTimer):
+        (WebCore::IDBTransaction::pendingOperationTimerFired):
+        (WebCore::IDBTransaction::operationCompletedOnServer):
+        (WebCore::IDBTransaction::scheduleCompletedOperationTimer):
+        (WebCore::IDBTransaction::completedOperationTimerFired):
+        (WebCore::IDBTransaction::completeNoncursorRequest):
+        (WebCore::IDBTransaction::completeCursorRequest):
+        (WebCore::IDBTransaction::finishedDispatchEventForRequest):
+        (WebCore::IDBTransaction::didStart):
+        (WebCore::IDBTransaction::didOpenCursorOnServer):
+        (WebCore::IDBTransaction::didIterateCursorOnServer):
+        (WebCore::IDBTransaction::didGetAllRecordsOnServer):
+        (WebCore::IDBTransaction::didGetRecordOnServer):
+        (WebCore::IDBTransaction::didGetCountOnServer):
+        (WebCore::IDBTransaction::didDeleteRecordOnServer):
+        (WebCore::IDBTransaction::didClearObjectStoreOnServer):
+        (WebCore::IDBTransaction::putOrAddOnServer):
+        (WebCore::IDBTransaction::didPutOrAddOnServer):
+        (WebCore::IDBTransaction::operationCompletedOnClient):
+        (WebCore::IDBTransaction::deactivate):
+        (WebCore::IDBTransaction::connectionClosedFromServer):
+        (WebCore::IDBTransaction::scheduleOperationTimer): Deleted.
+        (WebCore::IDBTransaction::operationTimerFired): Deleted.
+        (WebCore::IDBTransaction::operationDidComplete): Deleted.
+        * Modules/indexeddb/IDBTransaction.h:
+        
+        * Modules/indexeddb/client/IDBConnectionProxy.cpp:
+        (WebCore::IDBClient::IDBConnectionProxy::completeOperation):
+        
+        * Modules/indexeddb/client/TransactionOperation.cpp:
+        (WebCore::IDBClient::TransactionOperation::TransactionOperation):
+        * Modules/indexeddb/client/TransactionOperation.h:
+        (WebCore::IDBClient::TransactionOperation::transitionToCompleteOnThisThread):
+        (WebCore::IDBClient::TransactionOperation::transitionToComplete):
+        (WebCore::IDBClient::TransactionOperation::doComplete):
+        (WebCore::IDBClient::TransactionOperation::idbRequest):
+        (WebCore::IDBClient::TransactionOperation::performCompleteOnOriginThread): Deleted.
+        (WebCore::IDBClient::TransactionOperation::completed): Deleted.
+
 2016-11-29  Zalan Bujtas  <zalan@apple.com>
 
         Safari (WebKit) doesn't wrap element within flex when width comes below min-width
index fbad35f..7e73292 100644 (file)
@@ -304,6 +304,9 @@ bool IDBRequest::dispatchEvent(Event& event)
         m_transaction->abortDueToFailedRequest(*m_domError);
     }
 
+    if (m_transaction)
+        m_transaction->finishedDispatchEventForRequest(*this);
+
     return dontPreventDefault;
 }
 
@@ -465,10 +468,10 @@ void IDBRequest::didOpenOrIterateCursor(const IDBResultData& resultData)
     m_cursorRequestNotifier = nullptr;
     m_pendingCursor = nullptr;
 
-    requestCompleted(resultData);
+    completeRequestAndDispatchEvent(resultData);
 }
 
-void IDBRequest::requestCompleted(const IDBResultData& resultData)
+void IDBRequest::completeRequestAndDispatchEvent(const IDBResultData& resultData)
 {
     ASSERT(currentThread() == originThreadID());
 
index e945872..8f53dc9 100644 (file)
@@ -87,7 +87,7 @@ public:
     using RefCounted::ref;
     using RefCounted::deref;
 
-    void requestCompleted(const IDBResultData&);
+    void completeRequestAndDispatchEvent(const IDBResultData&);
 
     void setResult(const IDBKeyData&);
     void setResult(const Vector<IDBKeyData>&);
index b8326af..00c9131 100644 (file)
@@ -74,8 +74,10 @@ IDBTransaction::IDBTransaction(IDBDatabase& database, const IDBTransactionInfo&
     : IDBActiveDOMObject(database.scriptExecutionContext())
     , m_database(database)
     , m_info(info)
-    , m_operationTimer(*this, &IDBTransaction::operationTimerFired)
+    , m_pendingOperationTimer(*this, &IDBTransaction::pendingOperationTimerFired)
+    , m_completedOperationTimer(*this, &IDBTransaction::completedOperationTimerFired)
     , m_openDBRequest(request)
+    , m_currentlyCompletingRequest(request)
 
 {
     LOG(IndexedDB, "IDBTransaction::IDBTransaction - %s", m_info.loggingString().utf8().data());
@@ -244,7 +246,7 @@ void IDBTransaction::internalAbort()
 
     transitionedToFinishing(IndexedDB::TransactionState::Aborting);
     
-    m_abortQueue.swap(m_transactionOperationQueue);
+    m_abortQueue.swap(m_pendingTransactionOperationQueue);
 
     scheduleOperation(IDBClient::createTransactionOperation(*this, nullptr, &IDBTransaction::abortOnServerAndCancelRequests));
 }
@@ -253,19 +255,23 @@ void IDBTransaction::abortOnServerAndCancelRequests(IDBClient::TransactionOperat
 {
     LOG(IndexedDB, "IDBTransaction::abortOnServerAndCancelRequests");
     ASSERT(currentThread() == m_database->originThreadID());
-    ASSERT(m_transactionOperationQueue.isEmpty());
+    ASSERT(m_pendingTransactionOperationQueue.isEmpty());
 
     m_database->connectionProxy().abortTransaction(*this);
 
     ASSERT(m_transactionOperationMap.contains(operation.identifier()));
     m_transactionOperationMap.remove(operation.identifier());
 
+    m_currentlyCompletingRequest = nullptr;
+    
     IDBError error(IDBDatabaseException::AbortError);
-    for (auto& operation : m_abortQueue)
-        operation->completed(IDBResultData::error(operation->identifier(), error));
+    for (auto& operation : m_abortQueue) {
+        m_currentlyCompletingRequest = nullptr;
+        operation->doComplete(IDBResultData::error(operation->identifier(), error));
+    }
 
     // Since we're aborting, it should be impossible to have queued any further operations.
-    ASSERT(m_transactionOperationQueue.isEmpty());
+    ASSERT(m_pendingTransactionOperationQueue.isEmpty());
 }
 
 const char* IDBTransaction::activeDOMObjectName() const
@@ -339,30 +345,30 @@ void IDBTransaction::scheduleOperation(RefPtr<IDBClient::TransactionOperation>&&
     ASSERT(!m_transactionOperationMap.contains(operation->identifier()));
     ASSERT(currentThread() == m_database->originThreadID());
 
-    m_transactionOperationQueue.append(operation);
+    m_pendingTransactionOperationQueue.append(operation);
     m_transactionOperationMap.set(operation->identifier(), WTFMove(operation));
 
-    scheduleOperationTimer();
+    schedulePendingOperationTimer();
 }
 
-void IDBTransaction::scheduleOperationTimer()
+void IDBTransaction::schedulePendingOperationTimer()
 {
     ASSERT(currentThread() == m_database->originThreadID());
 
-    if (!m_operationTimer.isActive())
-        m_operationTimer.startOneShot(0);
+    if (!m_pendingOperationTimer.isActive())
+        m_pendingOperationTimer.startOneShot(0);
 }
 
-void IDBTransaction::operationTimerFired()
+void IDBTransaction::pendingOperationTimerFired()
 {
-    LOG(IndexedDB, "IDBTransaction::operationTimerFired (%p)", this);
+    LOG(IndexedDB, "IDBTransaction::pendingOperationTimerFired (%p)", this);
     ASSERT(currentThread() == m_database->originThreadID());
 
     if (!m_startedOnServer)
         return;
 
-    if (!m_transactionOperationQueue.isEmpty()) {
-        auto operation = m_transactionOperationQueue.takeFirst();
+    if (!m_pendingTransactionOperationQueue.isEmpty()) {
+        auto operation = m_pendingTransactionOperationQueue.takeFirst();
         operation->perform();
 
         return;
@@ -375,6 +381,67 @@ void IDBTransaction::operationTimerFired()
         commit();
 }
 
+void IDBTransaction::operationCompletedOnServer(const IDBResultData& data, IDBClient::TransactionOperation& operation)
+{
+    ASSERT(currentThread() == m_database->originThreadID());
+    ASSERT(currentThread() == operation.originThreadID());
+
+    m_completedOnServerQueue.append({ &operation, data });
+    scheduleCompletedOperationTimer();
+}
+
+void IDBTransaction::scheduleCompletedOperationTimer()
+{
+    ASSERT(currentThread() == m_database->originThreadID());
+
+    if (!m_completedOperationTimer.isActive())
+        m_completedOperationTimer.startOneShot(0);
+}
+
+void IDBTransaction::completedOperationTimerFired()
+{
+    LOG(IndexedDB, "IDBTransaction::completedOperationTimerFired (%p)", this);
+    ASSERT(currentThread() == m_database->originThreadID());
+
+    if (m_completedOnServerQueue.isEmpty() || m_currentlyCompletingRequest)
+        return;
+
+    auto iterator = m_completedOnServerQueue.takeFirst();
+    iterator.first->doComplete(iterator.second);
+
+    if (!m_completedOnServerQueue.isEmpty() && !m_currentlyCompletingRequest)
+        scheduleCompletedOperationTimer();
+}
+
+void IDBTransaction::completeNoncursorRequest(IDBRequest& request, const IDBResultData& result)
+{
+    ASSERT(!m_currentlyCompletingRequest);
+
+    request.completeRequestAndDispatchEvent(result);
+
+    m_currentlyCompletingRequest = &request;
+}
+
+void IDBTransaction::completeCursorRequest(IDBRequest& request, const IDBResultData& result)
+{
+    ASSERT(!m_currentlyCompletingRequest);
+
+    request.didOpenOrIterateCursor(result);
+
+    m_currentlyCompletingRequest = &request;
+}
+
+void IDBTransaction::finishedDispatchEventForRequest(IDBRequest& request)
+{
+    if (isFinishedOrFinishing())
+        return;
+
+    ASSERT_UNUSED(request, !m_currentlyCompletingRequest || m_currentlyCompletingRequest == &request);
+
+    m_currentlyCompletingRequest = nullptr;
+    scheduleCompletedOperationTimer();
+}
+
 void IDBTransaction::commit()
 {
     LOG(IndexedDB, "IDBTransaction::commit");
@@ -422,7 +489,7 @@ void IDBTransaction::didStart(const IDBError& error)
         return;
     }
 
-    scheduleOperationTimer();
+    schedulePendingOperationTimer();
 }
 
 void IDBTransaction::notifyDidAbort(const IDBError& error)
@@ -724,7 +791,7 @@ void IDBTransaction::didOpenCursorOnServer(IDBRequest& request, const IDBResultD
     LOG(IndexedDB, "IDBTransaction::didOpenCursorOnServer");
     ASSERT(currentThread() == m_database->originThreadID());
 
-    request.didOpenOrIterateCursor(resultData);
+    completeCursorRequest(request, resultData);
 }
 
 void IDBTransaction::iterateCursor(IDBCursor& cursor, const IDBIterateCursorData& data)
@@ -753,7 +820,7 @@ void IDBTransaction::didIterateCursorOnServer(IDBRequest& request, const IDBResu
     LOG(IndexedDB, "IDBTransaction::didIterateCursorOnServer");
     ASSERT(currentThread() == m_database->originThreadID());
 
-    request.didOpenOrIterateCursor(resultData);
+    completeCursorRequest(request, resultData);
 }
 
 Ref<IDBRequest> IDBTransaction::requestGetAllObjectStoreRecords(JSC::ExecState& state, IDBObjectStore& objectStore, const IDBKeyRangeData& keyRangeData, IndexedDB::GetAllType getAllType, std::optional<uint32_t> count)
@@ -806,7 +873,7 @@ void IDBTransaction::didGetAllRecordsOnServer(IDBRequest& request, const IDBResu
     ASSERT(currentThread() == m_database->originThreadID());
 
     if (resultData.type() == IDBResultType::Error) {
-        request.requestCompleted(resultData);
+        completeNoncursorRequest(request, resultData);
         return;
     }
 
@@ -822,7 +889,7 @@ void IDBTransaction::didGetAllRecordsOnServer(IDBRequest& request, const IDBResu
         break;
     }
 
-    request.requestCompleted(resultData);
+    completeNoncursorRequest(request, resultData);
 }
 
 Ref<IDBRequest> IDBTransaction::requestGetRecord(ExecState& state, IDBObjectStore& objectStore, const IDBGetRecordData& getRecordData)
@@ -890,7 +957,7 @@ void IDBTransaction::didGetRecordOnServer(IDBRequest& request, const IDBResultDa
     ASSERT(currentThread() == m_database->originThreadID());
 
     if (resultData.type() == IDBResultType::Error) {
-        request.requestCompleted(resultData);
+        completeNoncursorRequest(request, resultData);
         return;
     }
 
@@ -910,7 +977,7 @@ void IDBTransaction::didGetRecordOnServer(IDBRequest& request, const IDBResultDa
             request.setResultToUndefined();
     }
 
-    request.requestCompleted(resultData);
+    completeNoncursorRequest(request, resultData);
 }
 
 Ref<IDBRequest> IDBTransaction::requestCount(ExecState& state, IDBObjectStore& objectStore, const IDBKeyRangeData& range)
@@ -961,7 +1028,7 @@ void IDBTransaction::didGetCountOnServer(IDBRequest& request, const IDBResultDat
     ASSERT(currentThread() == m_database->originThreadID());
 
     request.setResult(resultData.resultInteger());
-    request.requestCompleted(resultData);
+    completeNoncursorRequest(request, resultData);
 }
 
 Ref<IDBRequest> IDBTransaction::requestDeleteRecord(ExecState& state, IDBObjectStore& objectStore, const IDBKeyRangeData& range)
@@ -994,7 +1061,7 @@ void IDBTransaction::didDeleteRecordOnServer(IDBRequest& request, const IDBResul
     ASSERT(currentThread() == m_database->originThreadID());
 
     request.setResultToUndefined();
-    request.requestCompleted(resultData);
+    completeNoncursorRequest(request, resultData);
 }
 
 Ref<IDBRequest> IDBTransaction::requestClearObjectStore(ExecState& state, IDBObjectStore& objectStore)
@@ -1028,7 +1095,7 @@ void IDBTransaction::didClearObjectStoreOnServer(IDBRequest& request, const IDBR
     ASSERT(currentThread() == m_database->originThreadID());
 
     request.setResultToUndefined();
-    request.requestCompleted(resultData);
+    completeNoncursorRequest(request, resultData);
 }
 
 Ref<IDBRequest> IDBTransaction::requestPutOrAdd(ExecState& state, IDBObjectStore& objectStore, IDBKey* key, SerializedScriptValue& value, IndexedDB::ObjectStoreOverwriteMode overwriteMode)
@@ -1074,7 +1141,7 @@ void IDBTransaction::putOrAddOnServer(IDBClient::TransactionOperation& operation
             RefPtr<IDBClient::TransactionOperation> protectedOperation(&operation);
             auto result = IDBResultData::error(operation.identifier(), { IDBDatabaseException::UnknownError, ASCIILiteral("Error preparing Blob/File data to be stored in object store") });
             scriptExecutionContext()->postTask([protectedOperation = WTFMove(protectedOperation), result = WTFMove(result)](ScriptExecutionContext&) {
-                protectedOperation->completed(result);
+                protectedOperation->doComplete(result);
             });
         }
         return;
@@ -1092,7 +1159,7 @@ void IDBTransaction::putOrAddOnServer(IDBClient::TransactionOperation& operation
         // In that case, we cannot successfully store this record, so we callback with an error.
         auto result = IDBResultData::error(protectedOperation->identifier(), { IDBDatabaseException::UnknownError, ASCIILiteral("Error preparing Blob/File data to be stored in object store") });
         callOnMainThread([protectedThis = WTFMove(protectedThis), protectedOperation = WTFMove(protectedOperation), result = WTFMove(result)]() mutable {
-            protectedOperation->completed(result);
+            protectedOperation->doComplete(result);
         });
     });
 }
@@ -1106,7 +1173,7 @@ void IDBTransaction::didPutOrAddOnServer(IDBRequest& request, const IDBResultDat
         request.setResult(*result);
     else
         request.setResultToUndefined();
-    request.requestCompleted(resultData);
+    completeNoncursorRequest(request, resultData);
 }
 
 void IDBTransaction::deleteObjectStore(const String& objectStoreName)
@@ -1167,15 +1234,17 @@ void IDBTransaction::didDeleteIndexOnServer(const IDBResultData& resultData)
     ASSERT_UNUSED(resultData, resultData.type() == IDBResultType::DeleteIndexSuccess || resultData.type() == IDBResultType::Error);
 }
 
-void IDBTransaction::operationDidComplete(IDBClient::TransactionOperation& operation)
+void IDBTransaction::operationCompletedOnClient(IDBClient::TransactionOperation& operation)
 {
+    LOG(IndexedDB, "IDBTransaction::operationCompletedOnClient");
+
     ASSERT(m_transactionOperationMap.get(operation.identifier()) == &operation);
     ASSERT(currentThread() == m_database->originThreadID());
     ASSERT(currentThread() == operation.originThreadID());
 
     m_transactionOperationMap.remove(operation.identifier());
 
-    scheduleOperationTimer();
+    schedulePendingOperationTimer();
 }
 
 void IDBTransaction::establishOnServer()
@@ -1203,7 +1272,7 @@ void IDBTransaction::deactivate()
     if (m_state == IndexedDB::TransactionState::Active)
         m_state = IndexedDB::TransactionState::Inactive;
 
-    scheduleOperationTimer();
+    schedulePendingOperationTimer();
 }
 
 void IDBTransaction::connectionClosedFromServer(const IDBError& error)
@@ -1215,12 +1284,14 @@ void IDBTransaction::connectionClosedFromServer(const IDBError& error)
     Vector<RefPtr<IDBClient::TransactionOperation>> operations;
     copyValuesToVector(m_transactionOperationMap, operations);
 
-    for (auto& operation : operations)
-        operation->completed(IDBResultData::error(operation->identifier(), error));
+    for (auto& operation : operations) {
+        m_currentlyCompletingRequest = nullptr;
+        operation->doComplete(IDBResultData::error(operation->identifier(), error));
+    }
 
     connectionProxy().forgetActiveOperations(operations);
 
-    m_transactionOperationQueue.clear();
+    m_pendingTransactionOperationQueue.clear();
     m_abortQueue.clear();
     m_transactionOperationMap.clear();
 
index ff18d89..df223e1 100644 (file)
@@ -138,7 +138,10 @@ public:
     void activate();
     void deactivate();
 
-    void operationDidComplete(IDBClient::TransactionOperation&);
+    void operationCompletedOnServer(const IDBResultData&, IDBClient::TransactionOperation&);
+    void operationCompletedOnClient(IDBClient::TransactionOperation&);
+
+    void finishedDispatchEventForRequest(IDBRequest&);
 
     bool isFinishedOrFinishing() const;
     bool isFinished() const { return m_state == IndexedDB::TransactionState::Finished; }
@@ -159,7 +162,8 @@ private:
     void finishAbortOrCommit();
 
     void scheduleOperation(RefPtr<IDBClient::TransactionOperation>&&);
-    void operationTimerFired();
+    void pendingOperationTimerFired();
+    void completedOperationTimerFired();
 
     void fireOnComplete();
     void fireOnAbort();
@@ -217,7 +221,11 @@ private:
 
     void establishOnServer();
 
-    void scheduleOperationTimer();
+    void completeNoncursorRequest(IDBRequest&, const IDBResultData&);
+    void completeCursorRequest(IDBRequest&, const IDBResultData&);
+
+    void schedulePendingOperationTimer();
+    void scheduleCompletedOperationTimer();
 
     Ref<IDBDatabase> m_database;
     IDBTransactionInfo m_info;
@@ -228,12 +236,14 @@ private:
     IDBError m_idbError;
     RefPtr<DOMError> m_domError;
 
-    Timer m_operationTimer;
+    Timer m_pendingOperationTimer;
+    Timer m_completedOperationTimer;
     std::unique_ptr<Timer> m_activationTimer;
 
     RefPtr<IDBOpenDBRequest> m_openDBRequest;
 
-    Deque<RefPtr<IDBClient::TransactionOperation>> m_transactionOperationQueue;
+    Deque<RefPtr<IDBClient::TransactionOperation>> m_pendingTransactionOperationQueue;
+    Deque<std::pair<RefPtr<IDBClient::TransactionOperation>, IDBResultData>> m_completedOnServerQueue;
     Deque<RefPtr<IDBClient::TransactionOperation>> m_abortQueue;
     HashMap<IDBResourceIdentifier, RefPtr<IDBClient::TransactionOperation>> m_transactionOperationMap;
 
@@ -242,6 +252,7 @@ private:
     HashMap<uint64_t, std::unique_ptr<IDBObjectStore>> m_deletedObjectStores;
 
     HashSet<RefPtr<IDBRequest>> m_openRequests;
+    RefPtr<IDBRequest> m_currentlyCompletingRequest;
 
     bool m_contextStopped { false };
 };
index 805f30e..4db50c0 100644 (file)
@@ -249,7 +249,7 @@ void IDBConnectionProxy::completeOperation(const IDBResultData& resultData)
     if (!operation)
         return;
 
-    operation->performCompleteOnOriginThread(resultData, WTFMove(operation));
+    operation->transitionToComplete(resultData, WTFMove(operation));
 }
 
 void IDBConnectionProxy::abortOpenAndUpgradeNeeded(uint64_t databaseConnectionIdentifier, const IDBResourceIdentifier& transactionIdentifier)
index 16f7475..8f35475 100644 (file)
@@ -43,6 +43,8 @@ TransactionOperation::TransactionOperation(IDBTransaction& transaction, IDBReque
         m_indexRecordType = request.requestedIndexRecordType();
     if (auto* cursor = request.pendingCursor())
         m_cursorIdentifier = std::make_unique<IDBResourceIdentifier>(cursor->info().identifier());
+
+    m_idbRequest = &request;
 }
 
 } // namespace IDBClient
index a36ea4a..71a1eba 100644 (file)
@@ -61,25 +61,31 @@ public:
         m_performFunction = { };
     }
 
-    void performCompleteOnOriginThread(const IDBResultData& data, RefPtr<TransactionOperation>&& lastRef)
+    void transitionToCompleteOnThisThread(const IDBResultData& data)
+    {
+        ASSERT(m_originThreadID == currentThread());
+        m_transaction->operationCompletedOnServer(data, *this);
+    }
+
+    void transitionToComplete(const IDBResultData& data, RefPtr<TransactionOperation>&& lastRef)
     {
         ASSERT(isMainThread());
 
         if (m_originThreadID == currentThread())
-            completed(data);
+            transitionToCompleteOnThisThread(data);
         else {
-            m_transaction->performCallbackOnOriginThread(*this, &TransactionOperation::completed, data);
+            m_transaction->performCallbackOnOriginThread(*this, &TransactionOperation::transitionToCompleteOnThisThread, data);
             m_transaction->callFunctionOnOriginThread([lastRef = WTFMove(lastRef)]() {
             });
         }
     }
 
-    void completed(const IDBResultData& data)
+    void doComplete(const IDBResultData& data)
     {
         ASSERT(m_originThreadID == currentThread());
         ASSERT(m_completeFunction);
         m_completeFunction(data);
-        m_transaction->operationDidComplete(*this);
+        m_transaction->operationCompletedOnClient(*this);
 
         // m_completeFunction might be holding the last ref to this TransactionOperation,
         // so we need to do this trick to null it out without first destroying it.
@@ -91,6 +97,8 @@ public:
 
     ThreadIdentifier originThreadID() const { return m_originThreadID; }
 
+    IDBRequest* idbRequest() { return m_idbRequest.get(); }
+
 protected:
     TransactionOperation(IDBTransaction& transaction)
         : m_transaction(transaction)
@@ -118,6 +126,7 @@ private:
     IndexedDB::IndexRecordType indexRecordType() const { return m_indexRecordType; }
 
     ThreadIdentifier m_originThreadID { currentThread() };
+    RefPtr<IDBRequest> m_idbRequest;
 };
 
 template <typename... Arguments>
index 461b9c9..42546fb 100644 (file)
@@ -807,11 +807,8 @@ IDBError SQLiteIDBBackingStore::abortTransaction(const IDBResourceIdentifier& id
         return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to abort a transaction that hasn't been established") };
     }
 
-
-    if (transaction->mode() == IDBTransactionMode::Versionchange) {
-        ASSERT(m_originalDatabaseInfoBeforeVersionChange);
+    if (transaction->mode() == IDBTransactionMode::Versionchange && m_originalDatabaseInfoBeforeVersionChange)
         m_databaseInfo = WTFMove(m_originalDatabaseInfoBeforeVersionChange);
-    }
 
     return transaction->abort();
 }