Bug 1180978: Don't proceed with opening an invalidated database. r=janv,baku
authorKyle Huey <khuey@kylehuey.com>
Wed, 22 Jul 2015 14:46:19 +0800
changeset 254081 236004185e7f1c24a10900bf32d2e6c4024b54a4
parent 254080 98c2f8a27530580aabbb55b7889242bff3923110
child 254082 e7434cafdf2f35ebd89f0120e33b7d10c73b84a7
child 254127 cef50aeb685eed7fb0f5b20cd2f3dac68a616f71
push id29087
push usercbook@mozilla.com
push dateWed, 22 Jul 2015 12:01:23 +0000
treeherdermozilla-central@e7434cafdf2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjanv, baku
bugs1180978
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1180978: Don't proceed with opening an invalidated database. r=janv,baku
dom/indexedDB/ActorsParent.cpp
dom/indexedDB/test/unit/test_blocked_order.js
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -12310,16 +12310,18 @@ Database::EnsureConnection()
 
 bool
 Database::RegisterTransaction(TransactionBase* aTransaction)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aTransaction);
   MOZ_ASSERT(!mTransactions.GetEntry(aTransaction));
   MOZ_ASSERT(mDirectoryLock);
+  MOZ_ASSERT(!mInvalidated);
+  MOZ_ASSERT(!mClosed);
 
   if (NS_WARN_IF(!mTransactions.PutEntry(aTransaction, fallible))) {
     return false;
   }
 
   return true;
 }
 
@@ -18688,16 +18690,20 @@ FactoryOp::Run()
     case State_WaitingForTransactionsToComplete:
       rv = DispatchToWorkThread();
       break;
 
     case State_SendingResults:
       SendResults();
       return NS_OK;
 
+    // We raced, no need to crash.
+    case State_Completed:
+      return NS_OK;
+
     default:
       MOZ_CRASH("Bad state!");
   }
 
   if (NS_WARN_IF(NS_FAILED(rv)) && mState != State_SendingResults) {
     if (NS_SUCCEEDED(mResultCode)) {
       mResultCode = rv;
     }
@@ -18745,16 +18751,24 @@ FactoryOp::DirectoryLockFailed()
 }
 
 void
 FactoryOp::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsOnBackgroundThread();
 
   NoteActorDestroyed();
+
+  if (mState == State_WaitingForTransactionsToComplete) {
+    // We didn't get an opportunity to clean up.  Do that now.
+    mState = State_SendingResults;
+    IDB_REPORT_INTERNAL_ERR();
+    mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+    SendResults();
+  }
 }
 
 bool
 FactoryOp::RecvPermissionRetry()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!IsActorDestroyed());
   MOZ_ASSERT(mState == State_PermissionChallenge);
@@ -19341,19 +19355,20 @@ OpenDatabaseOp::BeginVersionChange()
 }
 
 void
 OpenDatabaseOp::NoteDatabaseClosed(Database* aDatabase)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aDatabase);
   MOZ_ASSERT(mState == State_WaitingForOtherDatabasesToClose ||
+             mState == State_WaitingForTransactionsToComplete ||
              mState == State_DatabaseWorkVersionChange);
 
-  if (mState == State_DatabaseWorkVersionChange) {
+  if (mState != State_WaitingForOtherDatabasesToClose) {
     MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
     MOZ_ASSERT(mRequestedVersion >
                  aDatabase->Metadata()->mCommonMetadata.version(),
                "Must only be closing databases for a previous version!");
     return;
   }
 
   MOZ_ASSERT(!mMaybeBlockedDatabases.IsEmpty());
@@ -19407,17 +19422,18 @@ OpenDatabaseOp::DispatchToWorkThread()
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State_WaitingForTransactionsToComplete);
   MOZ_ASSERT(mVersionChangeTransaction);
   MOZ_ASSERT(mVersionChangeTransaction->GetMode() ==
                IDBTransaction::VERSION_CHANGE);
   MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread()) ||
-      IsActorDestroyed()) {
+      IsActorDestroyed() ||
+      mDatabase->IsInvalidated()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   mState = State_DatabaseWorkVersionChange;
 
   // Intentionally empty.
   nsTArray<nsString> objectStoreNames;
--- a/dom/indexedDB/test/unit/test_blocked_order.js
+++ b/dom/indexedDB/test/unit/test_blocked_order.js
@@ -51,17 +51,17 @@ function testSteps()
   let request = indexedDB.open(databaseName, 2);
   request.onerror = errorHandler;
   request.onsuccess = grabEventAndContinueHandler;
 
   request.onblocked = function(event) {
     ok(false, "Should not receive a blocked event");
   };
 
-  event = yield undefined;
+  let event = yield undefined;
 
   is(event.type, "success", "Got success event");
   is(databases.length, 0, "All databases with version 1 were closed");
 
   let db = request.result;
   is(db.version, 2, "Got version 2");
 
   info("Deleting database with version 2");
@@ -140,11 +140,40 @@ function testSteps()
   request = indexedDB.deleteDatabase(databaseName);
   request.onerror = errorHandler;
   request.onsuccess = grabEventAndContinueHandler;
 
   event = yield undefined;
 
   is(event.type, "success", "Got success event");
 
+  // Test 3: A blocked database left in that state should not hang shutdown.
+  info("Opening 1 database with version 1");
+
+  request = indexedDB.open(databaseName, 1);
+  request.onerror = errorHandler;
+  request.onblocked = errorHandler;
+  request.onsuccess = grabEventAndContinueHandler;
+
+  event = yield undefined;
+
+  is(event.type, "success", "Got success event");
+
+  db = request.result;
+  is(db.version, 1, "Got version 1");
+
+  info("Opening database with version 2");
+
+  request = indexedDB.open(databaseName, 2);
+  request.onerror = function(e) {
+    e.preventDefault();
+  };
+  request.onsuccess = errorHandler;
+
+  request.onblocked = grabEventAndContinueHandler;
+
+  event = yield undefined;
+  ok(true, "Got blocked");
+  // Just allow this to remain blocked ...
+
   finishTest();
   yield undefined;
 }