Bug 870460 - Part 1: Let cookie db startup-read off-main-thread. r=nwgh, r=jdm, data-r=francois
☠☠ backed out by ede558a89106 ☠ ☠
authorJunior Hsu <juhsu@mozilla.com>
Tue, 29 Aug 2017 18:16:27 +0800
changeset 428211 5aa7980268cb3c335fb7d44090e925563be9d0a2
parent 428210 b7ef07909cc4277230ea5ea70399f1ad5e581778
child 428212 50368fa3e040a806a0b86c0c6471055f4cb41ae1
push id97
push userfmarier@mozilla.com
push dateSat, 14 Oct 2017 01:12:59 +0000
reviewersnwgh, jdm
bugs870460
milestone58.0a1
Bug 870460 - Part 1: Let cookie db startup-read off-main-thread. r=nwgh, r=jdm, data-r=francois
extensions/cookie/test/unit/test_cookies_async_failure.js
extensions/cookie/test/unit/test_cookies_read.js
extensions/cookie/test/unit/test_schema_2_migration.js
extensions/cookie/test/unit/test_schema_3_migration.js
netwerk/base/nsIOService.cpp
netwerk/cookie/nsCookieService.cpp
netwerk/cookie/nsCookieService.h
netwerk/cookie/test/unit/test_bug1321912.js
toolkit/components/telemetry/Histograms.json
--- a/extensions/cookie/test/unit/test_cookies_async_failure.js
+++ b/extensions/cookie/test/unit/test_cookies_async_failure.js
@@ -227,46 +227,33 @@ function* run_test_2(generator)
 
   // Load the profile.
   do_load_profile();
 
   // At this point, the database connection should be open. Ensure that it
   // succeeded.
   do_check_false(do_get_backup_file(profile).exists());
 
-  // Synchronously read in the first cookie. This will cause it to go into the
-  // cookie table, whereupon it will be written out during database rebuild.
-  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
-
-  // Wait for the asynchronous read to choke, at which point the backup file
-  // will be created and the database rebuilt.
-  new _observer(sub_generator, "cookie-db-rebuilding");
-  yield;
-  do_execute_soon(function() { do_run_generator(sub_generator); });
-  yield;
-
-  // At this point, the cookies should still be in memory.
-  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
-  do_check_eq(do_count_cookies(), 1);
+  // Recreate a new database since it was corrupted
+  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 0);
+  do_check_eq(do_count_cookies(), 0);
 
   // Close the profile.
   do_close_profile(sub_generator);
   yield;
 
   // Check that the original database was renamed.
   do_check_true(do_get_backup_file(profile).exists());
   do_check_eq(do_get_backup_file(profile).fileSize, size);
   let db = Services.storage.openDatabase(do_get_cookie_file(profile));
-  do_check_eq(do_count_cookies_in_db(db, "0.com"), 1);
   db.close();
 
-  // Load the profile, and check that it contains the new cookie.
   do_load_profile();
-  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
-  do_check_eq(do_count_cookies(), 1);
+  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 0);
+  do_check_eq(do_count_cookies(), 0);
 
   // Close the profile.
   do_close_profile(sub_generator);
   yield;
 
   // Clean up.
   do_get_cookie_file(profile).remove(false);
   do_get_backup_file(profile).remove(false);
@@ -305,35 +292,26 @@ function* run_test_3(generator)
 
   // Load the profile.
   do_load_profile();
 
   // At this point, the database connection should be open. Ensure that it
   // succeeded.
   do_check_false(do_get_backup_file(profile).exists());
 
-  // Synchronously read in the cookies for our two domains. The first should
-  // succeed, but the second should fail midway through, resulting in none of
-  // those cookies being present.
-  do_check_eq(Services.cookiemgr.countCookiesFromHost("hither.com"), 10);
+  // Recreate a new database since it was corrupted
+  do_check_eq(Services.cookiemgr.countCookiesFromHost("hither.com"), 0);
   do_check_eq(Services.cookiemgr.countCookiesFromHost("haithur.com"), 0);
 
-  // Wait for the backup file to be created and the database rebuilt.
-  do_check_false(do_get_backup_file(profile).exists());
-  new _observer(sub_generator, "cookie-db-rebuilding");
-  yield;
-  do_execute_soon(function() { do_run_generator(sub_generator); });
-  yield;
-
   // Close the profile.
   do_close_profile(sub_generator);
   yield;
   let db = Services.storage.openDatabase(do_get_cookie_file(profile));
-  do_check_eq(do_count_cookies_in_db(db, "hither.com"), 10);
-  do_check_eq(do_count_cookies_in_db(db), 10);
+  do_check_eq(do_count_cookies_in_db(db, "hither.com"), 0);
+  do_check_eq(do_count_cookies_in_db(db), 0);
   db.close();
 
   // Check that the original database was renamed.
   do_check_true(do_get_backup_file(profile).exists());
   do_check_eq(do_get_backup_file(profile).fileSize, size);
 
   // Rename it back, and try loading the entire database synchronously.
   do_get_backup_file(profile).moveTo(null, "cookies.sqlite");
@@ -341,23 +319,16 @@ function* run_test_3(generator)
 
   // At this point, the database connection should be open. Ensure that it
   // succeeded.
   do_check_false(do_get_backup_file(profile).exists());
 
   // Synchronously read in everything.
   do_check_eq(do_count_cookies(), 0);
 
-  // Wait for the backup file to be created and the database rebuilt.
-  do_check_false(do_get_backup_file(profile).exists());
-  new _observer(sub_generator, "cookie-db-rebuilding");
-  yield;
-  do_execute_soon(function() { do_run_generator(sub_generator); });
-  yield;
-
   // Close the profile.
   do_close_profile(sub_generator);
   yield;
   db = Services.storage.openDatabase(do_get_cookie_file(profile));
   do_check_eq(do_count_cookies_in_db(db), 0);
   db.close();
 
   // Check that the original database was renamed.
@@ -392,52 +363,40 @@ function* run_test_4(generator)
 
   // Load the profile.
   do_load_profile();
 
   // At this point, the database connection should be open. Ensure that it
   // succeeded.
   do_check_false(do_get_backup_file(profile).exists());
 
-  // Synchronously read in the first cookie. This will cause it to go into the
-  // cookie table, whereupon it will be written out during database rebuild.
-  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
+  // Recreate a new database since it was corrupted
+  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 0);
 
   // Queue up an INSERT for the same base domain. This should also go into
   // memory and be written out during database rebuild.
   let uri = NetUtil.newURI("http://0.com/");
   Services.cookies.setCookieString(uri, null, "oh2=hai; max-age=1000", null);
 
-  // Wait for the asynchronous read to choke and the insert to fail shortly
-  // thereafter, at which point the backup file will be created and the database
-  // rebuilt.
-  new _observer(sub_generator, "cookie-db-rebuilding");
-  yield;
-  do_execute_soon(function() { do_run_generator(sub_generator); });
-  yield;
-
   // At this point, the cookies should still be in memory.
-  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 2);
-  do_check_eq(do_count_cookies(), 2);
+  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
+  do_check_eq(do_count_cookies(), 1);
 
   // Close the profile.
   do_close_profile(sub_generator);
   yield;
 
   // Check that the original database was renamed.
   do_check_true(do_get_backup_file(profile).exists());
   do_check_eq(do_get_backup_file(profile).fileSize, size);
-  let db = Services.storage.openDatabase(do_get_cookie_file(profile));
-  do_check_eq(do_count_cookies_in_db(db, "0.com"), 2);
-  db.close();
 
   // Load the profile, and check that it contains the new cookie.
   do_load_profile();
-  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 2);
-  do_check_eq(do_count_cookies(), 2);
+  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
+  do_check_eq(do_count_cookies(), 1);
 
   // Close the profile.
   do_close_profile(sub_generator);
   yield;
 
   // Clean up.
   do_get_cookie_file(profile).remove(false);
   do_get_backup_file(profile).remove(false);
@@ -469,72 +428,44 @@ function* run_test_5(generator)
 
   // Load the profile.
   do_load_profile();
 
   // At this point, the database connection should be open. Ensure that it
   // succeeded.
   do_check_false(do_get_backup_file(profile).exists());
 
-  // Synchronously read in the first two cookies. This will cause them to go
-  // into the cookie table, whereupon it will be written out during database
-  // rebuild.
-  do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 1);
-  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
-
-  // Wait for the asynchronous read to choke, at which point the backup file
-  // will be created and a new connection opened.
-  new _observer(sub_generator, "cookie-db-rebuilding");
-  yield;
-
-  // At this point, the cookies should still be in memory. (Note that these
-  // calls are re-entrant into the cookie service, but it's OK!)
-  do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 1);
-  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
-  do_check_eq(do_count_cookies(), 2);
+  // Recreate a new database since it was corrupted
+  do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 0);
+  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 0);
+  do_check_eq(do_count_cookies(), 0);
   do_check_true(do_get_backup_file(profile).exists());
   do_check_eq(do_get_backup_file(profile).fileSize, size);
   do_check_false(do_get_rebuild_backup_file(profile).exists());
 
   // Open a database connection, and write a row that will trigger a constraint
   // violation.
   let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 4);
   db.insertCookie(cookie);
   do_check_eq(do_count_cookies_in_db(db.db, "bar.com"), 1);
   do_check_eq(do_count_cookies_in_db(db.db), 1);
   db.close();
 
-  // Wait for the rebuild to bail and the database to be closed.
-  new _observer(sub_generator, "cookie-db-closed");
-  yield;
-
   // Check that the original backup and the database itself are gone.
-  do_check_true(do_get_rebuild_backup_file(profile).exists());
   do_check_true(do_get_backup_file(profile).exists());
   do_check_eq(do_get_backup_file(profile).fileSize, size);
-  do_check_false(do_get_cookie_file(profile).exists());
 
-  // Check that the rebuild backup has the original bar.com cookie, and possibly
-  // a 0.com cookie depending on whether it got written out first or second.
-  db = new CookieDatabaseConnection(do_get_rebuild_backup_file(profile), 4);
-  do_check_eq(do_count_cookies_in_db(db.db, "bar.com"), 1);
-  let count = do_count_cookies_in_db(db.db);
-  do_check_true(count == 1 ||
-    count == 2 && do_count_cookies_in_db(db.db, "0.com") == 1);
-  db.close();
-
-  do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 1);
-  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
-  do_check_eq(do_count_cookies(), 2);
+  do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 0);
+  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 0);
+  do_check_eq(do_count_cookies(), 0);
 
   // Close the profile. We do not need to wait for completion, because the
   // database has already been closed.
   do_close_profile();
 
   // Clean up.
+  do_get_cookie_file(profile).remove(false);
   do_get_backup_file(profile).remove(false);
-  do_get_rebuild_backup_file(profile).remove(false);
   do_check_false(do_get_cookie_file(profile).exists());
   do_check_false(do_get_backup_file(profile).exists());
-  do_check_false(do_get_rebuild_backup_file(profile).exists());
   do_run_generator(generator);
 }
 
--- a/extensions/cookie/test/unit/test_cookies_read.js
+++ b/extensions/cookie/test/unit/test_cookies_read.js
@@ -22,17 +22,18 @@ function finish_test() {
 function* do_run_test() {
   // Set up a profile.
   let profile = do_get_profile();
 
   // Allow all cookies.
   Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
 
   // Start the cookieservice, to force creation of a database.
-  Services.cookies;
+  // Get the sessionEnumerator to join the initialization in cookie thread
+  Services.cookiemgr.sessionEnumerator;
 
   // Open a database connection now, after synchronous initialization has
   // completed. We may not be able to open one later once asynchronous writing
   // begins.
   do_check_true(do_get_cookie_file(profile).exists());
   let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 4);
 
   for (let i = 0; i < CMAX; ++i) {
--- a/extensions/cookie/test/unit/test_schema_2_migration.js
+++ b/extensions/cookie/test/unit/test_schema_2_migration.js
@@ -17,16 +17,27 @@ function finish_test() {
     do_test_finished();
   });
 }
 
 function* do_run_test() {
   // Set up a profile.
   let profile = do_get_profile();
 
+  // Start the cookieservice, to force creation of a database.
+  // Get the sessionEnumerator to join the initialization in cookie thread
+  Services.cookiemgr.sessionEnumerator;
+
+  // Close the profile.
+  do_close_profile(test_generator);
+  yield;
+
+  // Remove the cookie file in order to create another database file.
+  do_get_cookie_file(profile).remove(false);
+
   // Create a schema 2 database.
   let schema2db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
 
   let now = Date.now() * 1000;
   let futureExpiry = Math.round(now / 1e6 + 1000);
   let pastExpiry = Math.round(now / 1e6 - 1000);
 
   // Populate it, with:
@@ -74,16 +85,18 @@ function* do_run_test() {
   }
 
   // Close it.
   schema2db.close();
   schema2db = null;
 
   // Load the database, forcing migration to the current schema version. Then
   // test the expected set of cookies:
+  do_load_profile();
+
   // 1) All unexpired, unique cookies exist.
   do_check_eq(Services.cookiemgr.countCookiesFromHost("foo.com"), 20);
 
   // 2) All expired, unique cookies exist.
   do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 20);
 
   // 3) Only one cookie remains, and it's the one with the highest expiration
   // time.
--- a/extensions/cookie/test/unit/test_schema_3_migration.js
+++ b/extensions/cookie/test/unit/test_schema_3_migration.js
@@ -17,16 +17,27 @@ function finish_test() {
     do_test_finished();
   });
 }
 
 function* do_run_test() {
   // Set up a profile.
   let profile = do_get_profile();
 
+  // Start the cookieservice, to force creation of a database.
+  // Get the sessionEnumerator to join the initialization in cookie thread
+  Services.cookiemgr.sessionEnumerator;
+
+  // Close the profile.
+  do_close_profile(test_generator);
+  yield;
+
+  // Remove the cookie file in order to create another database file.
+  do_get_cookie_file(profile).remove(false);
+
   // Create a schema 3 database.
   let schema3db = new CookieDatabaseConnection(do_get_cookie_file(profile), 3);
 
   let now = Date.now() * 1000;
   let futureExpiry = Math.round(now / 1e6 + 1000);
   let pastExpiry = Math.round(now / 1e6 - 1000);
 
   // Populate it, with:
@@ -74,16 +85,18 @@ function* do_run_test() {
   }
 
   // Close it.
   schema3db.close();
   schema3db = null;
 
   // Load the database, forcing migration to the current schema version. Then
   // test the expected set of cookies:
+  do_load_profile();
+
   // 1) All unexpired, unique cookies exist.
   do_check_eq(Services.cookiemgr.countCookiesFromHost("foo.com"), 20);
 
   // 2) All expired, unique cookies exist.
   do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 20);
 
   // 3) Only one cookie remains, and it's the one with the highest expiration
   // time.
--- a/netwerk/base/nsIOService.cpp
+++ b/netwerk/base/nsIOService.cpp
@@ -1484,16 +1484,21 @@ nsIOService::Observe(nsISupports *subjec
             // Set up the initilization flag regardless the actuall result.
             // If we fail here, we will fail always on.
             mNetworkLinkServiceInitialized = true;
 
             // And now reflect the preference setting
             nsCOMPtr<nsIPrefBranch> prefBranch;
             GetPrefBranch(getter_AddRefs(prefBranch));
             PrefsChanged(prefBranch, MANAGE_OFFLINE_STATUS_PREF);
+
+            // Bug 870460 - Read cookie database at an early-as-possible time
+            // off main thread. Hence, we have more chance to finish db query
+            // before something calls into the cookie service.
+            nsCOMPtr<nsISupports> cookieServ = do_GetService(NS_COOKIESERVICE_CONTRACTID);
         }
     } else if (!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
         // Remember we passed XPCOM shutdown notification to prevent any
         // changes of the offline status from now. We must not allow going
         // online after this point.
         mShutdown = true;
 
         if (!mHttpHandlerAlreadyShutingDown && !mOfflineForProfileChange) {
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -29,16 +29,17 @@
 #include "nsIURI.h"
 #include "nsIURL.h"
 #include "nsIChannel.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
 #include "nsILineInputStream.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIIDNService.h"
+#include "nsIThread.h"
 #include "mozIThirdPartyUtil.h"
 
 #include "nsTArray.h"
 #include "nsCOMArray.h"
 #include "nsIMutableArray.h"
 #include "nsArrayEnumerator.h"
 #include "nsEnumeratorUtils.h"
 #include "nsAutoPtr.h"
@@ -77,18 +78,17 @@ static StaticRefPtr<nsCookieService> gCo
 
 // XXX_hack. See bug 178993.
 // This is a hack to hide HttpOnly cookies from older browsers
 #define HTTP_ONLY_PREFIX "#HttpOnly_"
 
 #define COOKIES_FILE "cookies.sqlite"
 #define COOKIES_SCHEMA_VERSION 9
 
-// parameter indexes; see EnsureReadDomain, EnsureReadComplete and
-// ReadCookieDBListener::HandleResult
+// parameter indexes; see |Read|
 #define IDX_NAME 0
 #define IDX_VALUE 1
 #define IDX_HOST 2
 #define IDX_PATH 3
 #define IDX_EXPIRY 4
 #define IDX_LAST_ACCESSED 5
 #define IDX_CREATION_TIME 6
 #define IDX_SECURE 7
@@ -453,102 +453,16 @@ public:
   {
     return NS_OK;
   }
 };
 
 NS_IMPL_ISUPPORTS(RemoveCookieDBListener, mozIStorageStatementCallback)
 
 /******************************************************************************
- * ReadCookieDBListener impl:
- * mozIStorageStatementCallback used to track asynchronous removal operations.
- ******************************************************************************/
-class ReadCookieDBListener final : public DBListenerErrorHandler
-{
-private:
-  const char *GetOpType() override { return "READ"; }
-  bool mCanceled;
-
-  ~ReadCookieDBListener() = default;
-
-public:
-  NS_DECL_ISUPPORTS
-
-  explicit ReadCookieDBListener(DBState* dbState)
-    : DBListenerErrorHandler(dbState)
-    , mCanceled(false)
-  {
-  }
-
-  void Cancel() { mCanceled = true; }
-
-  NS_IMETHOD HandleResult(mozIStorageResultSet *aResult) override
-  {
-    nsCOMPtr<mozIStorageRow> row;
-
-    while (true) {
-      DebugOnly<nsresult> rv = aResult->GetNextRow(getter_AddRefs(row));
-      NS_ASSERT_SUCCESS(rv);
-
-      if (!row)
-        break;
-
-      CookieDomainTuple *tuple = mDBState->hostArray.AppendElement();
-      row->GetUTF8String(IDX_BASE_DOMAIN, tuple->key.mBaseDomain);
-
-      nsAutoCString suffix;
-      row->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix);
-      DebugOnly<bool> success = tuple->key.mOriginAttributes.PopulateFromSuffix(suffix);
-      MOZ_ASSERT(success);
-
-      tuple->cookie =
-        gCookieService->GetCookieFromRow(row, tuple->key.mOriginAttributes);
-    }
-
-    return NS_OK;
-  }
-  NS_IMETHOD HandleCompletion(uint16_t aReason) override
-  {
-    // Process the completion of the read operation. If we have been canceled,
-    // we cannot assume that the cookieservice still has an open connection
-    // or that it even refers to the same database, so we must return early.
-    // Conversely, the cookieservice guarantees that if we have not been
-    // canceled, the database connection is still alive and we can safely
-    // operate on it.
-
-    if (mCanceled) {
-      // We may receive a REASON_FINISHED after being canceled;
-      // tweak the reason accordingly.
-      aReason = mozIStorageStatementCallback::REASON_CANCELED;
-    }
-
-    switch (aReason) {
-    case mozIStorageStatementCallback::REASON_FINISHED:
-      gCookieService->AsyncReadComplete();
-      break;
-    case mozIStorageStatementCallback::REASON_CANCELED:
-      // Nothing more to do here. The partially read data has already been
-      // thrown away.
-      COOKIE_LOGSTRING(LogLevel::Debug, ("Read canceled"));
-      break;
-    case mozIStorageStatementCallback::REASON_ERROR:
-      // Nothing more to do here. DBListenerErrorHandler::HandleError()
-      // can handle it.
-      COOKIE_LOGSTRING(LogLevel::Debug, ("Read error"));
-      break;
-    default:
-      NS_NOTREACHED("invalid reason");
-    }
-    return NS_OK;
-  }
-};
-
-NS_IMPL_ISUPPORTS(ReadCookieDBListener, mozIStorageStatementCallback)
-
-/******************************************************************************
  * CloseCookieDBListener imp:
  * Static mozIStorageCompletionCallback used to notify when the database is
  * successfully closed.
  ******************************************************************************/
 class CloseCookieDBListener final :  public mozIStorageCompletionCallback
 {
   ~CloseCookieDBListener() = default;
 
@@ -604,38 +518,22 @@ nsCookieEntry::SizeOfExcludingThis(Mallo
   for (uint32_t i = 0; i < mCookies.Length(); ++i) {
     amount += mCookies[i]->SizeOfIncludingThis(aMallocSizeOf);
   }
 
   return amount;
 }
 
 size_t
-CookieDomainTuple::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
-{
-  size_t amount = 0;
-
-  amount += key.SizeOfExcludingThis(aMallocSizeOf);
-  amount += cookie->SizeOfIncludingThis(aMallocSizeOf);
-
-  return amount;
-}
-
-size_t
 DBState::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
 {
   size_t amount = 0;
 
   amount += aMallocSizeOf(this);
   amount += hostTable.SizeOfExcludingThis(aMallocSizeOf);
-  amount += hostArray.ShallowSizeOfExcludingThis(aMallocSizeOf);
-  for (uint32_t i = 0; i < hostArray.Length(); ++i) {
-    amount += hostArray[i].SizeOfExcludingThis(aMallocSizeOf);
-  }
-  amount += readSet.SizeOfExcludingThis(aMallocSizeOf);
 
   return amount;
 }
 
 /******************************************************************************
  * nsCookieService impl:
  * singleton instance ctor/dtor methods
  ******************************************************************************/
@@ -701,16 +599,21 @@ NS_IMPL_ISUPPORTS(nsCookieService,
 nsCookieService::nsCookieService()
  : mDBState(nullptr)
  , mCookieBehavior(nsICookieService::BEHAVIOR_ACCEPT)
  , mThirdPartySession(false)
  , mLeaveSecureAlone(true)
  , mMaxNumberOfCookies(kMaxNumberOfCookies)
  , mMaxCookiesPerHost(kMaxCookiesPerHost)
  , mCookiePurgeAge(kCookiePurgeAge)
+ , mThread(nullptr)
+ , mMonitor("CookieThread")
+ , mInitializedDBStates(false)
+ , mInitializedDBConn(false)
+ , mAccumulatedWaitTelemetry(false)
 {
 }
 
 nsresult
 nsCookieService::Init()
 {
   nsresult rv;
   mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
@@ -732,16 +635,19 @@ nsCookieService::Init()
     prefBranch->AddObserver(kPrefThirdPartySession,     this, true);
     prefBranch->AddObserver(kCookieLeaveSecurityAlone,  this, true);
     PrefChanged(prefBranch);
   }
 
   mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  rv = NS_NewNamedThread("Cookie", getter_AddRefs(mThread));
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // Init our default, and possibly private DBStates.
   InitDBStates();
 
   RegisterWeakMemoryReporter(this);
 
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   NS_ENSURE_STATE(os);
   os->AddObserver(this, "profile-before-change", true);
@@ -758,59 +664,86 @@ nsCookieService::Init()
 }
 
 void
 nsCookieService::InitDBStates()
 {
   NS_ASSERTION(!mDBState, "already have a DBState");
   NS_ASSERTION(!mDefaultDBState, "already have a default DBState");
   NS_ASSERTION(!mPrivateDBState, "already have a private DBState");
+  NS_ASSERTION(!mInitializedDBStates, "already initialized");
 
   // Create a new default DBState and set our current one.
   mDefaultDBState = new DBState();
   mDBState = mDefaultDBState;
 
   mPrivateDBState = new DBState();
 
   // Get our cookie file.
   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
     getter_AddRefs(mDefaultDBState->cookieFile));
   if (NS_FAILED(rv)) {
     // We've already set up our DBStates appropriately; nothing more to do.
     COOKIE_LOGSTRING(LogLevel::Warning,
       ("InitDBStates(): couldn't get cookie file"));
+
+    mInitializedDBConn = true;
+    mInitializedDBStates = true;
     return;
   }
   mDefaultDBState->cookieFile->AppendNative(NS_LITERAL_CSTRING(COOKIES_FILE));
 
-  // Attempt to open and read the database. If TryInitDB() returns RESULT_RETRY,
-  // do so.
-  OpenDBResult result = TryInitDB(false);
-  if (result == RESULT_RETRY) {
-    // Database may be corrupt. Synchronously close the connection, clean up the
-    // default DBState, and try again.
-    COOKIE_LOGSTRING(LogLevel::Warning, ("InitDBStates(): retrying TryInitDB()"));
-    CleanupCachedStatements();
-    CleanupDefaultDBConnection();
-    result = TryInitDB(true);
+  nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction("InitDBStates.TryInitDB", [] {
+    NS_ENSURE_TRUE_VOID(gCookieService &&
+                        gCookieService->mDBState &&
+                        gCookieService->mDefaultDBState);
+
+    MonitorAutoLock lock(gCookieService->mMonitor);
+
+    // Attempt to open and read the database. If TryInitDB() returns RESULT_RETRY,
+    // do so.
+    OpenDBResult result = gCookieService->TryInitDB(false);
     if (result == RESULT_RETRY) {
-      // We're done. Change the code to failure so we clean up below.
-      result = RESULT_FAILURE;
+      // Database may be corrupt. Synchronously close the connection, clean up the
+      // default DBState, and try again.
+      COOKIE_LOGSTRING(LogLevel::Warning, ("InitDBStates(): retrying TryInitDB()"));
+      gCookieService->CleanupCachedStatements();
+      gCookieService->CleanupDefaultDBConnection();
+      result = gCookieService->TryInitDB(true);
+      if (result == RESULT_RETRY) {
+        // We're done. Change the code to failure so we clean up below.
+        result = RESULT_FAILURE;
+      }
     }
-  }
-
-  if (result == RESULT_FAILURE) {
-    COOKIE_LOGSTRING(LogLevel::Warning,
-      ("InitDBStates(): TryInitDB() failed, closing connection"));
-
-    // Connection failure is unrecoverable. Clean up our connection. We can run
-    // fine without persistent storage -- e.g. if there's no profile.
-    CleanupCachedStatements();
-    CleanupDefaultDBConnection();
-  }
+
+    if (result == RESULT_FAILURE) {
+      COOKIE_LOGSTRING(LogLevel::Warning,
+        ("InitDBStates(): TryInitDB() failed, closing connection"));
+
+      // Connection failure is unrecoverable. Clean up our connection. We can run
+      // fine without persistent storage -- e.g. if there's no profile.
+      gCookieService->CleanupCachedStatements();
+      gCookieService->CleanupDefaultDBConnection();
+
+      // No need to initialize dbConn
+      gCookieService->mInitializedDBConn = true;
+    }
+
+    gCookieService->mInitializedDBStates = true;
+
+    NS_DispatchToMainThread(
+      NS_NewRunnableFunction("TryInitDB.InitDBConn", [] {
+        NS_ENSURE_TRUE_VOID(gCookieService);
+        gCookieService->InitDBConn();
+      })
+    );
+    gCookieService->mMonitor.Notify();
+  });
+
+  mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
 }
 
 namespace {
 
 class ConvertAppIdToOriginAttrsSQLFunction final : public mozIStorageFunction
 {
   ~ConvertAppIdToOriginAttrsSQLFunction() = default;
 
@@ -925,16 +858,17 @@ SetInBrowserFromOriginAttributesSQLFunct
  */
 OpenDBResult
 nsCookieService::TryInitDB(bool aRecreateDB)
 {
   NS_ASSERTION(!mDefaultDBState->dbConn, "nonnull dbConn");
   NS_ASSERTION(!mDefaultDBState->stmtInsert, "nonnull stmtInsert");
   NS_ASSERTION(!mDefaultDBState->insertListener, "nonnull insertListener");
   NS_ASSERTION(!mDefaultDBState->syncConn, "nonnull syncConn");
+  NS_ASSERTION(NS_GetCurrentThread() == mThread, "non cookie thread");
 
   // Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't
   // want to delete it outright, since it may be useful for debugging purposes,
   // so we move it out of the way.
   nsresult rv;
   if (aRecreateDB) {
     nsCOMPtr<nsIFile> backupFile;
     mDefaultDBState->cookieFile->Clone(getter_AddRefs(backupFile));
@@ -948,81 +882,72 @@ nsCookieService::TryInitDB(bool aRecreat
     Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_COOKIES_OPEN_READAHEAD_MS>
       telemetry;
     ReadAheadFile(mDefaultDBState->cookieFile);
 
     // open a connection to the cookie database, and only cache our connection
     // and statements upon success. The connection is opened unshared to eliminate
     // cache contention between the main and background threads.
     rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
-      getter_AddRefs(mDefaultDBState->dbConn));
+      getter_AddRefs(mDefaultDBState->syncConn));
     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   }
 
-  // Set up our listeners.
-  mDefaultDBState->insertListener = new InsertCookieDBListener(mDefaultDBState);
-  mDefaultDBState->updateListener = new UpdateCookieDBListener(mDefaultDBState);
-  mDefaultDBState->removeListener = new RemoveCookieDBListener(mDefaultDBState);
-  mDefaultDBState->closeListener = new CloseCookieDBListener(mDefaultDBState);
-
-  // Grow cookie db in 512KB increments
-  mDefaultDBState->dbConn->SetGrowthIncrement(512 * 1024, EmptyCString());
-
   bool tableExists = false;
-  mDefaultDBState->dbConn->TableExists(NS_LITERAL_CSTRING("moz_cookies"),
+  mDefaultDBState->syncConn->TableExists(NS_LITERAL_CSTRING("moz_cookies"),
     &tableExists);
   if (!tableExists) {
     rv = CreateTable();
     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
   } else {
     // table already exists; check the schema version before reading
     int32_t dbSchemaVersion;
-    rv = mDefaultDBState->dbConn->GetSchemaVersion(&dbSchemaVersion);
+    rv = mDefaultDBState->syncConn->GetSchemaVersion(&dbSchemaVersion);
     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
     // Start a transaction for the whole migration block.
-    mozStorageTransaction transaction(mDefaultDBState->dbConn, true);
+    mozStorageTransaction transaction(mDefaultDBState->syncConn, true);
 
     switch (dbSchemaVersion) {
     // Upgrading.
     // Every time you increment the database schema, you need to implement
     // the upgrading code from the previous version to the new one. If migration
     // fails for any reason, it's a bug -- so we return RESULT_RETRY such that
     // the original database will be saved, in the hopes that we might one day
     // see it and fix it.
     case 1:
       {
         // Add the lastAccessed column to the table.
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
       }
       // Fall through to the next upgrade.
       MOZ_FALLTHROUGH;
 
     case 2:
       {
         // Add the baseDomain column and index to the table.
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "ALTER TABLE moz_cookies ADD baseDomain TEXT"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Compute the baseDomains for the table. This must be done eagerly
         // otherwise we won't be able to synchronously read in individual
         // domains on demand.
         const int64_t SCHEMA2_IDX_ID  =  0;
         const int64_t SCHEMA2_IDX_HOST = 1;
         nsCOMPtr<mozIStorageStatement> select;
-        rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
           "SELECT id, host FROM moz_cookies"), getter_AddRefs(select));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         nsCOMPtr<mozIStorageStatement> update;
-        rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
           "UPDATE moz_cookies SET baseDomain = :baseDomain WHERE id = :id"),
           getter_AddRefs(update));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         nsCString baseDomain, host;
         bool hasResult;
         while (true) {
           rv = select->ExecuteStep(&hasResult);
@@ -1046,17 +971,17 @@ nsCookieService::TryInitDB(bool aRecreat
                                        id);
           NS_ASSERT_SUCCESS(rv);
 
           rv = update->ExecuteStep(&hasResult);
           NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
         }
 
         // Create an index on baseDomain.
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
       }
       // Fall through to the next upgrade.
       MOZ_FALLTHROUGH;
 
     case 3:
       {
@@ -1071,24 +996,24 @@ nsCookieService::TryInitDB(bool aRecreat
         // Select the whole table, and order by the fields we're interested in.
         // This means we can simply do a linear traversal of the results and
         // check for duplicates as we go.
         const int64_t SCHEMA3_IDX_ID =   0;
         const int64_t SCHEMA3_IDX_NAME = 1;
         const int64_t SCHEMA3_IDX_HOST = 2;
         const int64_t SCHEMA3_IDX_PATH = 3;
         nsCOMPtr<mozIStorageStatement> select;
-        rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
           "SELECT id, name, host, path FROM moz_cookies "
             "ORDER BY name ASC, host ASC, path ASC, expiry ASC"),
           getter_AddRefs(select));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         nsCOMPtr<mozIStorageStatement> deleteExpired;
-        rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
           "DELETE FROM moz_cookies WHERE id = :id"),
           getter_AddRefs(deleteExpired));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Read the first row.
         bool hasResult;
         rv = select->ExecuteStep(&hasResult);
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
@@ -1131,28 +1056,28 @@ nsCookieService::TryInitDB(bool aRecreat
             name1 = name2;
             host1 = host2;
             path1 = path2;
             id1 = id2;
           }
         }
 
         // Add the creationTime column to the table.
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "ALTER TABLE moz_cookies ADD creationTime INTEGER"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Copy the id of each row into the new creationTime column.
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "UPDATE moz_cookies SET creationTime = "
             "(SELECT id WHERE id = moz_cookies.id)"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Create a unique index on (name, host, path) to allow fast lookup.
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "CREATE UNIQUE INDEX moz_uniqueid "
           "ON moz_cookies (name, host, path)"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
       }
       // Fall through to the next upgrade.
       MOZ_FALLTHROUGH;
 
     case 4:
@@ -1164,41 +1089,41 @@ nsCookieService::TryInitDB(bool aRecreat
         //
         // Why we made this change: appId/inBrowserElement allow "cookie jars"
         // for Firefox OS. We create a separate cookie namespace per {appId,
         // inBrowserElement}.  When upgrading, we convert existing cookies
         // (which imply we're on desktop/mobile) to use {0, false}, as that is
         // the only namespace used by a non-Firefox-OS implementation.
 
         // Rename existing table
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Drop existing index (CreateTable will create new one for new table)
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "DROP INDEX moz_basedomain"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Create new table (with new fields and new unique constraint)
         rv = CreateTableForSchemaVersion5();
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Copy data from old table, using appId/inBrowser=0 for existing rows
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "INSERT INTO moz_cookies "
           "(baseDomain, appId, inBrowserElement, name, value, host, path, expiry,"
           " lastAccessed, creationTime, isSecure, isHttpOnly) "
           "SELECT baseDomain, 0, 0, name, value, host, path, expiry,"
           " lastAccessed, creationTime, isSecure, isHttpOnly "
           "FROM moz_cookies_old"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Drop old table
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "DROP TABLE moz_cookies_old"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         COOKIE_LOGSTRING(LogLevel::Debug,
           ("Upgraded database to schema version 5"));
       }
       // Fall through to the next upgrade.
       MOZ_FALLTHROUGH;
@@ -1214,58 +1139,58 @@ nsCookieService::TryInitDB(bool aRecreat
         //
         // We do the migration in several steps:
         // 1. Rename the old table.
         // 2. Create a new table.
         // 3. Copy data from the old table to the new table; convert appId and
         //    inBrowserElement to originAttributes in the meantime.
 
         // Rename existing table.
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
              "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Drop existing index (CreateTable will create new one for new table).
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
              "DROP INDEX moz_basedomain"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Create new table with new fields and new unique constraint.
         rv = CreateTableForSchemaVersion6();
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Copy data from old table without the two deprecated columns appId and
         // inBrowserElement.
         nsCOMPtr<mozIStorageFunction>
           convertToOriginAttrs(new ConvertAppIdToOriginAttrsSQLFunction());
         NS_ENSURE_TRUE(convertToOriginAttrs, RESULT_RETRY);
 
         NS_NAMED_LITERAL_CSTRING(convertToOriginAttrsName,
                                  "CONVERT_TO_ORIGIN_ATTRIBUTES");
 
-        rv = mDefaultDBState->dbConn->CreateFunction(convertToOriginAttrsName,
+        rv = mDefaultDBState->syncConn->CreateFunction(convertToOriginAttrsName,
                                                      2, convertToOriginAttrs);
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "INSERT INTO moz_cookies "
           "(baseDomain, originAttributes, name, value, host, path, expiry,"
           " lastAccessed, creationTime, isSecure, isHttpOnly) "
           "SELECT baseDomain, "
           " CONVERT_TO_ORIGIN_ATTRIBUTES(appId, inBrowserElement),"
           " name, value, host, path, expiry, lastAccessed, creationTime, "
           " isSecure, isHttpOnly "
           "FROM moz_cookies_old"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
-        rv = mDefaultDBState->dbConn->RemoveFunction(convertToOriginAttrsName);
+        rv = mDefaultDBState->syncConn->RemoveFunction(convertToOriginAttrsName);
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Drop old table
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
              "DROP TABLE moz_cookies_old"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         COOKIE_LOGSTRING(LogLevel::Debug,
           ("Upgraded database to schema version 6"));
       }
       MOZ_FALLTHROUGH;
 
@@ -1274,81 +1199,81 @@ nsCookieService::TryInitDB(bool aRecreat
         // We made a mistake in schema version 6. We cannot remove expected
         // columns of any version (checked in the default case) from cookie
         // database, because doing this would destroy the possibility of
         // downgrading database.
         //
         // This version simply restores appId and inBrowserElement columns in
         // order to fix downgrading issue even though these two columns are no
         // longer used in the latest schema.
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "ALTER TABLE moz_cookies ADD appId INTEGER DEFAULT 0;"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "ALTER TABLE moz_cookies ADD inBrowserElement INTEGER DEFAULT 0;"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Compute and populate the values of appId and inBrwoserElement from
         // originAttributes.
         nsCOMPtr<mozIStorageFunction>
           setAppId(new SetAppIdFromOriginAttributesSQLFunction());
         NS_ENSURE_TRUE(setAppId, RESULT_RETRY);
 
         NS_NAMED_LITERAL_CSTRING(setAppIdName, "SET_APP_ID");
 
-        rv = mDefaultDBState->dbConn->CreateFunction(setAppIdName, 1, setAppId);
+        rv = mDefaultDBState->syncConn->CreateFunction(setAppIdName, 1, setAppId);
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         nsCOMPtr<mozIStorageFunction>
           setInBrowser(new SetInBrowserFromOriginAttributesSQLFunction());
         NS_ENSURE_TRUE(setInBrowser, RESULT_RETRY);
 
         NS_NAMED_LITERAL_CSTRING(setInBrowserName, "SET_IN_BROWSER");
 
-        rv = mDefaultDBState->dbConn->CreateFunction(setInBrowserName, 1,
+        rv = mDefaultDBState->syncConn->CreateFunction(setInBrowserName, 1,
                                                      setInBrowser);
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "UPDATE moz_cookies SET appId = SET_APP_ID(originAttributes), "
           "inBrowserElement = SET_IN_BROWSER(originAttributes);"
         ));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
-        rv = mDefaultDBState->dbConn->RemoveFunction(setAppIdName);
+        rv = mDefaultDBState->syncConn->RemoveFunction(setAppIdName);
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
-        rv = mDefaultDBState->dbConn->RemoveFunction(setInBrowserName);
+        rv = mDefaultDBState->syncConn->RemoveFunction(setInBrowserName);
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         COOKIE_LOGSTRING(LogLevel::Debug,
           ("Upgraded database to schema version 7"));
       }
       MOZ_FALLTHROUGH;
 
     case 7:
       {
         // Remove the appId field from moz_cookies.
         //
         // Unfortunately sqlite doesn't support dropping columns using ALTER
         // TABLE, so we need to go through the procedure documented in
         // https://www.sqlite.org/lang_altertable.html.
 
         // Drop existing index
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
              "DROP INDEX moz_basedomain"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Create a new_moz_cookies table without the appId field.
         rv = CreateTableWorker("new_moz_cookies");
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Move the data over.
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "INSERT INTO new_moz_cookies ("
               "id, "
               "baseDomain, "
               "originAttributes, "
               "name, "
               "value, "
               "host, "
               "path, "
@@ -1371,77 +1296,77 @@ nsCookieService::TryInitDB(bool aRecreat
               "creationTime, "
               "isSecure, "
               "isHttpOnly, "
               "inBrowserElement "
             "FROM moz_cookies;"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Drop the old table
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "DROP TABLE moz_cookies;"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Rename new_moz_cookies to moz_cookies.
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "ALTER TABLE new_moz_cookies RENAME TO moz_cookies;"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         // Recreate our index.
         rv = CreateIndex();
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         COOKIE_LOGSTRING(LogLevel::Debug,
           ("Upgraded database to schema version 8"));
       }
       MOZ_FALLTHROUGH;
 
     case 8:
       {
         // Add the sameSite column to the table.
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
           NS_LITERAL_CSTRING("ALTER TABLE moz_cookies ADD sameSite INTEGER"));
         COOKIE_LOGSTRING(LogLevel::Debug,
           ("Upgraded database to schema version 9"));
       }
 
       // No more upgrades. Update the schema version.
-      rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
+      rv = mDefaultDBState->syncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
       NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
       MOZ_FALLTHROUGH;
 
     case COOKIES_SCHEMA_VERSION:
       break;
 
     case 0:
       {
         NS_WARNING("couldn't get schema version!");
 
         // the table may be usable; someone might've just clobbered the schema
         // version. we can treat this case like a downgrade using the codepath
         // below, by verifying the columns we care about are all there. for now,
         // re-set the schema version in the db, in case the checks succeed (if
         // they don't, we're dropping the table anyway).
-        rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
+        rv = mDefaultDBState->syncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
       }
       // fall through to downgrade check
       MOZ_FALLTHROUGH;
 
     // downgrading.
     // if columns have been added to the table, we can still use the ones we
     // understand safely. if columns have been deleted or altered, just
     // blow away the table and start from scratch! if you change the way
     // a column is interpreted, make sure you also change its name so this
     // check will catch it.
     default:
       {
         // check if all the expected columns exist
         nsCOMPtr<mozIStorageStatement> stmt;
-        rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
           "SELECT "
             "id, "
             "baseDomain, "
             "originAttributes, "
             "name, "
             "value, "
             "host, "
             "path, "
@@ -1451,27 +1376,139 @@ nsCookieService::TryInitDB(bool aRecreat
             "isSecure, "
             "isHttpOnly, "
             "sameSite "
           "FROM moz_cookies"), getter_AddRefs(stmt));
         if (NS_SUCCEEDED(rv))
           break;
 
         // our columns aren't there - drop the table!
-        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "DROP TABLE moz_cookies"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         rv = CreateTable();
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
       }
       break;
     }
   }
 
+  // if we deleted a corrupt db, don't attempt to import - return now
+  if (aRecreateDB) {
+    return RESULT_OK;
+  }
+
+  // check whether to import or just read in the db
+  if (tableExists) {
+    return Read();
+  }
+
+  nsCOMPtr<nsIRunnable> runnable =
+    NS_NewRunnableFunction("TryInitDB.ImportCookies", [] {
+      NS_ENSURE_TRUE_VOID(gCookieService);
+      NS_ENSURE_TRUE_VOID(gCookieService->mDefaultDBState);
+      nsCOMPtr<nsIFile> oldCookieFile;
+      nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+        getter_AddRefs(oldCookieFile));
+      if (NS_FAILED(rv)) {
+        return;
+      }
+
+      // Import cookies, and clean up the old file regardless of success or failure.
+      // Note that we have to switch out our DBState temporarily, in case we're in
+      // private browsing mode; otherwise ImportCookies() won't be happy.
+      DBState* initialState = gCookieService->mDBState;
+      gCookieService->mDBState = gCookieService->mDefaultDBState;
+      oldCookieFile->AppendNative(NS_LITERAL_CSTRING(OLD_COOKIE_FILE_NAME));
+      gCookieService->ImportCookies(oldCookieFile);
+      oldCookieFile->Remove(false);
+      gCookieService->mDBState = initialState;
+    });
+
+  NS_DispatchToMainThread(runnable);
+
+  return RESULT_OK;
+}
+
+void
+nsCookieService::InitDBConn()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // We should skip InitDBConn if we close profile during initializing DBStates
+  // and then InitDBConn is called after we close the DBStates.
+  if (!mInitializedDBStates || mInitializedDBConn || !mDefaultDBState) {
+    return;
+  }
+
+  if (!mAccumulatedWaitTelemetry) {
+    mAccumulatedWaitTelemetry = true;
+    Telemetry::Accumulate(Telemetry::MOZ_SQLITE_COOKIES_BLOCK_MAIN_THREAD_MS, 0);
+  }
+  for (uint32_t i = 0; i < mReadArray.Length(); ++i) {
+    CookieDomainTuple& tuple = mReadArray[i];
+    RefPtr<nsCookie> cookie = nsCookie::Create(tuple.cookie->name,
+                                               tuple.cookie->value,
+                                               tuple.cookie->host,
+                                               tuple.cookie->path,
+                                               tuple.cookie->expiry,
+                                               tuple.cookie->lastAccessed,
+                                               tuple.cookie->creationTime,
+                                               false,
+                                               tuple.cookie->isSecure,
+                                               tuple.cookie->isHttpOnly,
+                                               tuple.cookie->originAttributes,
+                                               tuple.cookie->sameSite);
+
+    AddCookieToList(tuple.key, cookie, mDefaultDBState, nullptr, false);
+  }
+
+  if (NS_FAILED(InitDBConnInternal())) {
+    COOKIE_LOGSTRING(LogLevel::Warning, ("InitDBConn(): retrying InitDBConnInternal()"));
+    CleanupCachedStatements();
+    CleanupDefaultDBConnection();
+    if (NS_FAILED(InitDBConnInternal())) {
+      COOKIE_LOGSTRING(LogLevel::Warning,
+        ("InitDBConn(): InitDBConnInternal() failed, closing connection"));
+
+      // Game over, clean the connections.
+      CleanupCachedStatements();
+      CleanupDefaultDBConnection();
+    }
+  }
+  mInitializedDBConn = true;
+
+  COOKIE_LOGSTRING(LogLevel::Debug, ("InitDBConn(): mInitializedDBConn = true"));
+
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  if (os && !mReadArray.IsEmpty()) {
+    os->NotifyObservers(nullptr, "cookie-db-read", nullptr);
+    mReadArray.Clear();
+  }
+}
+
+nsresult
+nsCookieService::InitDBConnInternal()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsresult rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
+    getter_AddRefs(mDefaultDBState->dbConn));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Set up our listeners.
+  mDefaultDBState->insertListener = new InsertCookieDBListener(mDefaultDBState);
+  mDefaultDBState->updateListener = new UpdateCookieDBListener(mDefaultDBState);
+  mDefaultDBState->removeListener = new RemoveCookieDBListener(mDefaultDBState);
+  mDefaultDBState->closeListener = new CloseCookieDBListener(mDefaultDBState);
+
+  // Grow cookie db in 512KB increments
+  mDefaultDBState->dbConn->SetGrowthIncrement(512 * 1024, EmptyCString());
+
   // make operations on the table asynchronous, for performance
   mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "PRAGMA synchronous = OFF"));
 
   // Use write-ahead-logging for performance. We cap the autocheckpoint limit at
   // 16 pages (around 500KB).
   mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = WAL"));
@@ -1503,54 +1540,29 @@ nsCookieService::TryInitDB(bool aRecreat
       ":expiry, "
       ":lastAccessed, "
       ":creationTime, "
       ":isSecure, "
       ":isHttpOnly, "
       ":sameSite"
     ")"),
     getter_AddRefs(mDefaultDBState->stmtInsert));
-  NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
     "DELETE FROM moz_cookies "
     "WHERE name = :name AND host = :host AND path = :path AND originAttributes = :originAttributes"),
     getter_AddRefs(mDefaultDBState->stmtDelete));
-  NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
     "UPDATE moz_cookies SET lastAccessed = :lastAccessed "
     "WHERE name = :name AND host = :host AND path = :path AND originAttributes = :originAttributes"),
     getter_AddRefs(mDefaultDBState->stmtUpdate));
-  NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
-
-  // if we deleted a corrupt db, don't attempt to import - return now
-  if (aRecreateDB)
-    return RESULT_OK;
-
-  // check whether to import or just read in the db
-  if (tableExists)
-    return Read();
-
-  nsCOMPtr<nsIFile> oldCookieFile;
-  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
-    getter_AddRefs(oldCookieFile));
-  if (NS_FAILED(rv)) return RESULT_OK;
-
-  // Import cookies, and clean up the old file regardless of success or failure.
-  // Note that we have to switch out our DBState temporarily, in case we're in
-  // private browsing mode; otherwise ImportCookies() won't be happy.
-  DBState* initialState = mDBState;
-  mDBState = mDefaultDBState;
-  oldCookieFile->AppendNative(NS_LITERAL_CSTRING(OLD_COOKIE_FILE_NAME));
-  ImportCookies(oldCookieFile);
-  oldCookieFile->Remove(false);
-  mDBState = initialState;
-
-  return RESULT_OK;
+  return rv;
 }
 
 // Sets the schema version and creates the moz_cookies table.
 nsresult
 nsCookieService::CreateTableWorker(const char* aName)
 {
   // Create the table.
   // We default originAttributes to empty string: this is so if users revert to
@@ -1570,56 +1582,56 @@ nsCookieService::CreateTableWorker(const
       "lastAccessed INTEGER, "
       "creationTime INTEGER, "
       "isSecure INTEGER, "
       "isHttpOnly INTEGER, "
       "inBrowserElement INTEGER DEFAULT 0, "
       "sameSite INTEGER DEFAULT 0, "
       "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
     ")");
-  return mDefaultDBState->dbConn->ExecuteSimpleSQL(command);
+  return mDefaultDBState->syncConn->ExecuteSimpleSQL(command);
 }
 
 // Sets the schema version and creates the moz_cookies table.
 nsresult
 nsCookieService::CreateTable()
 {
   // Set the schema version, before creating the table.
-  nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(
+  nsresult rv = mDefaultDBState->syncConn->SetSchemaVersion(
     COOKIES_SCHEMA_VERSION);
   if (NS_FAILED(rv)) return rv;
 
   rv = CreateTableWorker("moz_cookies");
   if (NS_FAILED(rv)) return rv;
 
   return CreateIndex();
 }
 
 nsresult
 nsCookieService::CreateIndex()
 {
   // Create an index on baseDomain.
-  return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+  return mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
                                                 "originAttributes)"));
 }
 
 // Sets the schema version and creates the moz_cookies table.
 nsresult
 nsCookieService::CreateTableForSchemaVersion6()
 {
   // Set the schema version, before creating the table.
-  nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(6);
+  nsresult rv = mDefaultDBState->syncConn->SetSchemaVersion(6);
   if (NS_FAILED(rv)) return rv;
 
   // Create the table.
   // We default originAttributes to empty string: this is so if users revert to
   // an older Firefox version that doesn't know about this field, any cookies
   // set will still work once they upgrade back.
-  rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+  rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "CREATE TABLE moz_cookies ("
       "id INTEGER PRIMARY KEY, "
       "baseDomain TEXT, "
       "originAttributes TEXT NOT NULL DEFAULT '', "
       "name TEXT, "
       "value TEXT, "
       "host TEXT, "
       "path TEXT, "
@@ -1628,33 +1640,33 @@ nsCookieService::CreateTableForSchemaVer
       "creationTime INTEGER, "
       "isSecure INTEGER, "
       "isHttpOnly INTEGER, "
       "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
     ")"));
   if (NS_FAILED(rv)) return rv;
 
   // Create an index on baseDomain.
-  return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+  return mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
                                                 "originAttributes)"));
 }
 
 // Sets the schema version and creates the moz_cookies table.
 nsresult
 nsCookieService::CreateTableForSchemaVersion5()
 {
   // Set the schema version, before creating the table.
-  nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(5);
+  nsresult rv = mDefaultDBState->syncConn->SetSchemaVersion(5);
   if (NS_FAILED(rv)) return rv;
 
   // Create the table. We default appId/inBrowserElement to 0: this is so if
   // users revert to an older Firefox version that doesn't know about these
   // fields, any cookies set will still work once they upgrade back.
-  rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+  rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "CREATE TABLE moz_cookies ("
       "id INTEGER PRIMARY KEY, "
       "baseDomain TEXT, "
       "appId INTEGER DEFAULT 0, "
       "inBrowserElement INTEGER DEFAULT 0, "
       "name TEXT, "
       "value TEXT, "
       "host TEXT, "
@@ -1664,50 +1676,52 @@ nsCookieService::CreateTableForSchemaVer
       "creationTime INTEGER, "
       "isSecure INTEGER, "
       "isHttpOnly INTEGER, "
       "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, appId, inBrowserElement)"
     ")"));
   if (NS_FAILED(rv)) return rv;
 
   // Create an index on baseDomain.
-  return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+  return mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
                                                 "appId, "
                                                 "inBrowserElement)"));
 }
 
 void
 nsCookieService::CloseDBStates()
 {
+  // return if we already closed
+  if (!mDBState) {
+    return;
+  }
+
+  EnsureReadComplete(false);
+
   // Null out our private and pointer DBStates regardless.
   mPrivateDBState = nullptr;
   mDBState = nullptr;
 
   // If we don't have a default DBState, we're done.
   if (!mDefaultDBState)
     return;
 
   // Cleanup cached statements before we can close anything.
   CleanupCachedStatements();
 
   if (mDefaultDBState->dbConn) {
-    // Cancel any pending read. No further results will be received by our
-    // read listener.
-    if (mDefaultDBState->pendingRead) {
-      CancelAsyncRead(true);
-    }
-
     // Asynchronously close the connection. We will null it below.
     mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
   }
 
   CleanupDefaultDBConnection();
 
   mDefaultDBState = nullptr;
+  mInitializedDBStates = false;
 }
 
 // Null out the statements.
 // This must be done before closing the connection.
 void
 nsCookieService::CleanupCachedStatements()
 {
   mDefaultDBState->stmtInsert = nullptr;
@@ -1730,21 +1744,22 @@ nsCookieService::CleanupDefaultDBConnect
   // asynchronous operations yet, this will synchronously close it; otherwise,
   // it's expected that the caller has performed an AsyncClose prior.
   mDefaultDBState->dbConn = nullptr;
   mDefaultDBState->syncConn = nullptr;
 
   // Manually null out our listeners. This is necessary because they hold a
   // strong ref to the DBState itself. They'll stay alive until whatever
   // statements are still executing complete.
-  mDefaultDBState->readListener = nullptr;
   mDefaultDBState->insertListener = nullptr;
   mDefaultDBState->updateListener = nullptr;
   mDefaultDBState->removeListener = nullptr;
   mDefaultDBState->closeListener = nullptr;
+
+  mInitializedDBConn = false;
 }
 
 void
 nsCookieService::HandleDBClosed(DBState* aDBState)
 {
   COOKIE_LOGSTRING(LogLevel::Debug,
     ("HandleDBClosed(): DBState %p closed", aDBState));
 
@@ -1802,26 +1817,16 @@ nsCookieService::HandleCorruptDB(DBState
 
   // Mark the database corrupt, so the close listener can begin reconstructing
   // it.
   switch (mDefaultDBState->corruptFlag) {
   case DBState::OK: {
     // Move to 'closing' state.
     mDefaultDBState->corruptFlag = DBState::CLOSING_FOR_REBUILD;
 
-    // Cancel any pending read and close the database. If we do have an
-    // in-flight read we want to throw away all the results so far -- we have no
-    // idea how consistent the database is. Note that we may have already
-    // canceled the read but not emptied our readSet; do so now.
-    mDefaultDBState->readSet.Clear();
-    if (mDefaultDBState->pendingRead) {
-      CancelAsyncRead(true);
-      mDefaultDBState->syncConn = nullptr;
-    }
-
     CleanupCachedStatements();
     mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
     CleanupDefaultDBConnection();
     break;
   }
   case DBState::CLOSING_FOR_REBUILD: {
     // We had an error while waiting for close completion. That's OK, just
     // ignore it -- we're rebuilding anyway.
@@ -1862,80 +1867,102 @@ nsCookieService::RebuildCorruptDB(DBStat
       os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
     }
     return;
   }
 
   COOKIE_LOGSTRING(LogLevel::Debug,
     ("RebuildCorruptDB(): creating new database"));
 
-  // The database has been closed, and we're ready to rebuild. Open a
-  // connection.
-  OpenDBResult result = TryInitDB(true);
-  if (result != RESULT_OK) {
-    // We're done. Reset our DB connection and statements, and notify of
-    // closure.
-    COOKIE_LOGSTRING(LogLevel::Warning,
-      ("RebuildCorruptDB(): TryInitDB() failed with result %u", result));
-    CleanupCachedStatements();
-    CleanupDefaultDBConnection();
-    mDefaultDBState->corruptFlag = DBState::OK;
-    if (os) {
-      os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
-    }
-    return;
-  }
-
-  // Notify observers that we're beginning the rebuild.
-  if (os) {
-    os->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr);
-  }
-
-  // Enumerate the hash, and add cookies to the params array.
-  mozIStorageAsyncStatement* stmt = aDBState->stmtInsert;
-  nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
-  stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
-  for (auto iter = aDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
-    nsCookieEntry* entry = iter.Get();
-
-    const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
-    for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
-      nsCookie* cookie = cookies[i];
-
-      if (!cookie->IsSession()) {
-        bindCookieParameters(paramsArray, nsCookieKey(entry), cookie);
-      }
-    }
-  }
-
-  // Make sure we've got something to write. If we don't, we're done.
-  uint32_t length;
-  paramsArray->GetLength(&length);
-  if (length == 0) {
-    COOKIE_LOGSTRING(LogLevel::Debug,
-      ("RebuildCorruptDB(): nothing to write, rebuild complete"));
-    mDefaultDBState->corruptFlag = DBState::OK;
-    return;
-  }
-
-  // Execute the statement. If any errors crop up, we won't try again.
-  DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
-  NS_ASSERT_SUCCESS(rv);
-  nsCOMPtr<mozIStoragePendingStatement> handle;
-  rv = stmt->ExecuteAsync(aDBState->insertListener, getter_AddRefs(handle));
-  NS_ASSERT_SUCCESS(rv);
+  nsCOMPtr<nsIRunnable> runnable =
+    NS_NewRunnableFunction("RebuildCorruptDB.TryInitDB", [] {
+      NS_ENSURE_TRUE_VOID(gCookieService && gCookieService->mDefaultDBState);
+
+      // The database has been closed, and we're ready to rebuild. Open a
+      // connection.
+      OpenDBResult result = gCookieService->TryInitDB(true);
+
+      nsCOMPtr<nsIRunnable> innerRunnable =
+        NS_NewRunnableFunction("RebuildCorruptDB.TryInitDBComplete", [result] {
+          NS_ENSURE_TRUE_VOID(gCookieService && gCookieService->mDefaultDBState);
+
+          nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+          if (result != RESULT_OK) {
+            // We're done. Reset our DB connection and statements, and notify of
+            // closure.
+            COOKIE_LOGSTRING(LogLevel::Warning,
+              ("RebuildCorruptDB(): TryInitDB() failed with result %u", result));
+            gCookieService->CleanupCachedStatements();
+            gCookieService->CleanupDefaultDBConnection();
+            gCookieService->mDefaultDBState->corruptFlag = DBState::OK;
+            if (os) {
+              os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
+            }
+            return;
+          }
+
+          // Notify observers that we're beginning the rebuild.
+          if (os) {
+            os->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr);
+          }
+
+          gCookieService->InitDBConn();
+
+          // Enumerate the hash, and add cookies to the params array.
+          mozIStorageAsyncStatement* stmt = gCookieService->mDefaultDBState->stmtInsert;
+          nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+          stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+          for (auto iter = gCookieService->mDefaultDBState->hostTable.Iter();
+               !iter.Done();
+               iter.Next()) {
+            nsCookieEntry* entry = iter.Get();
+
+            const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
+            for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+              nsCookie* cookie = cookies[i];
+
+              if (!cookie->IsSession()) {
+                bindCookieParameters(paramsArray, nsCookieKey(entry), cookie);
+              }
+            }
+          }
+
+          // Make sure we've got something to write. If we don't, we're done.
+          uint32_t length;
+          paramsArray->GetLength(&length);
+          if (length == 0) {
+            COOKIE_LOGSTRING(LogLevel::Debug,
+              ("RebuildCorruptDB(): nothing to write, rebuild complete"));
+            gCookieService->mDefaultDBState->corruptFlag = DBState::OK;
+            return;
+          }
+
+          // Execute the statement. If any errors crop up, we won't try again.
+          DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
+          NS_ASSERT_SUCCESS(rv);
+          nsCOMPtr<mozIStoragePendingStatement> handle;
+          rv = stmt->ExecuteAsync(gCookieService->mDefaultDBState->insertListener,
+                                  getter_AddRefs(handle));
+          NS_ASSERT_SUCCESS(rv);
+        });
+      NS_DispatchToMainThread(innerRunnable);
+    });
+  mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
 }
 
 nsCookieService::~nsCookieService()
 {
   CloseDBStates();
 
   UnregisterWeakMemoryReporter(this);
 
   gCookieService = nullptr;
+  if (mThread) {
+    mThread->Shutdown();
+  }
 }
 
 NS_IMETHODIMP
 nsCookieService::Observe(nsISupports     *aSubject,
                          const char      *aTopic,
                          const char16_t *aData)
 {
   // check the topic
@@ -2119,16 +2146,18 @@ nsCookieService::SetCookieStringInternal
 {
   NS_ASSERTION(aHostURI, "null host!");
 
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return;
   }
 
+  EnsureReadComplete(true);
+
   AutoRestore<DBState*> savePrevDBState(mDBState);
   mDBState = (aOriginAttrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
 
   // get the base domain for the host URI.
   // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
   // file:// URI's (i.e. with an empty host) are allowed, but any other
   // scheme must have a non-empty host. A trailing dot in the host
   // is acceptable.
@@ -2303,16 +2332,23 @@ nsCookieService::CreatePurgeList(nsICook
  * nsCookieService:
  * public transaction helper impl
  ******************************************************************************/
 
 NS_IMETHODIMP
 nsCookieService::RunInTransaction(nsICookieTransactionCallback* aCallback)
 {
   NS_ENSURE_ARG(aCallback);
+  if (!mDBState) {
+    NS_WARNING("No DBState! Profile already closed?");
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  EnsureReadComplete(true);
+
   if (NS_WARN_IF(!mDefaultDBState->dbConn)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   mozStorageTransaction transaction(mDefaultDBState->dbConn, true);
 
   if (NS_FAILED(aCallback->Callback())) {
     Unused << transaction.Rollback();
     return NS_ERROR_FAILURE;
@@ -2359,28 +2395,24 @@ nsCookieService::PrefChanged(nsIPrefBran
 NS_IMETHODIMP
 nsCookieService::RemoveAll()
 {
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  EnsureReadComplete(true);
+
   RemoveAllFromMemory();
 
   // clear the cookie file
   if (mDBState->dbConn) {
     NS_ASSERTION(mDBState == mDefaultDBState, "not in default DB state");
 
-    // Cancel any pending read. No further results will be received by our
-    // read listener.
-    if (mDefaultDBState->pendingRead) {
-      CancelAsyncRead(true);
-    }
-
     nsCOMPtr<mozIStorageAsyncStatement> stmt;
     nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
       "DELETE FROM moz_cookies"), getter_AddRefs(stmt));
     if (NS_SUCCEEDED(rv)) {
       nsCOMPtr<mozIStoragePendingStatement> handle;
       rv = stmt->ExecuteAsync(mDefaultDBState->removeListener,
         getter_AddRefs(handle));
       NS_ASSERT_SUCCESS(rv);
@@ -2399,17 +2431,17 @@ nsCookieService::RemoveAll()
 NS_IMETHODIMP
 nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator)
 {
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  EnsureReadComplete();
+  EnsureReadComplete(true);
 
   nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
   for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
     const nsCookieEntry::ArrayType& cookies = iter.Get()->GetCookies();
     for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
       cookieList.AppendObject(cookies[i]);
     }
   }
@@ -2420,17 +2452,17 @@ nsCookieService::GetEnumerator(nsISimple
 NS_IMETHODIMP
 nsCookieService::GetSessionEnumerator(nsISimpleEnumerator **aEnumerator)
 {
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  EnsureReadComplete();
+  EnsureReadComplete(true);
 
   nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
   for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
     const nsCookieEntry::ArrayType& cookies = iter.Get()->GetCookies();
     for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
       nsCookie* cookie = cookies[i];
       // Filter out non-session cookies.
       if (cookie->IsSession()) {
@@ -2526,16 +2558,18 @@ nsCookieService::AddNative(const nsACStr
     return NS_ERROR_FAILURE;
   }
 
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  EnsureReadComplete(true);
+
   AutoRestore<DBState*> savePrevDBState(mDBState);
   mDBState = (aOriginAttributes->mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
 
   // first, normalize the hostname, and fail if it contains illegal characters.
   nsAutoCString host(aHost);
   nsresult rv = NormalizeHost(host);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -2572,16 +2606,18 @@ nsCookieService::Remove(const nsACString
                         const nsACString& aName, const nsACString& aPath,
                         bool aBlocked)
 {
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  EnsureReadComplete(true);
+
   AutoRestore<DBState*> savePrevDBState(mDBState);
   mDBState = (aAttrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
 
   // first, normalize the hostname, and fail if it contains illegal characters.
   nsAutoCString host(aHost);
   nsresult rv = NormalizeHost(host);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -2665,80 +2701,20 @@ nsCookieService::RemoveNative(const nsAC
   return NS_OK;
 }
 
 /******************************************************************************
  * nsCookieService impl:
  * private file I/O functions
  ******************************************************************************/
 
-// Begin an asynchronous read from the database.
-OpenDBResult
-nsCookieService::Read()
-{
-  // Set up a statement for the read. Note that our query specifies that
-  // 'baseDomain' not be nullptr -- see below for why.
-  nsCOMPtr<mozIStorageAsyncStatement> stmtRead;
-  nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
-    "SELECT "
-      "name, "
-      "value, "
-      "host, "
-      "path, "
-      "expiry, "
-      "lastAccessed, "
-      "creationTime, "
-      "isSecure, "
-      "isHttpOnly, "
-      "baseDomain, "
-      "originAttributes, "
-      "sameSite "
-    "FROM moz_cookies "
-    "WHERE baseDomain NOTNULL"), getter_AddRefs(stmtRead));
-  NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
-
-  // Set up a statement to delete any rows with a nullptr 'baseDomain'
-  // column. This takes care of any cookies set by browsers that don't
-  // understand the 'baseDomain' column, where the database schema version
-  // is from one that does. (This would occur when downgrading.)
-  nsCOMPtr<mozIStorageAsyncStatement> stmtDeleteNull;
-  rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
-    "DELETE FROM moz_cookies WHERE baseDomain ISNULL"),
-    getter_AddRefs(stmtDeleteNull));
-  NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
-
-  // Start a new connection for sync reads, to reduce contention with the
-  // background thread. We need to do this before we kick off write statements,
-  // since they can lock the database and prevent connections from being opened.
-  rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
-    getter_AddRefs(mDefaultDBState->syncConn));
-  NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
-
-  // Init our readSet hash and execute the statements. Note that, after this
-  // point, we cannot fail without altering the cleanup code in InitDBStates()
-  // to handle closing of the now-asynchronous connection.
-  mDefaultDBState->hostArray.SetCapacity(kMaxNumberOfCookies);
-
-  mDefaultDBState->readListener = new ReadCookieDBListener(mDefaultDBState);
-  rv = stmtRead->ExecuteAsync(mDefaultDBState->readListener,
-    getter_AddRefs(mDefaultDBState->pendingRead));
-  NS_ASSERT_SUCCESS(rv);
-
-  nsCOMPtr<mozIStoragePendingStatement> handle;
-  rv = stmtDeleteNull->ExecuteAsync(mDefaultDBState->removeListener,
-    getter_AddRefs(handle));
-  NS_ASSERT_SUCCESS(rv);
-
-  return RESULT_OK;
-}
-
 // Extract data from a single result row and create an nsCookie.
-// This is templated since 'T' is different for sync vs async results.
-template<class T> nsCookie*
-nsCookieService::GetCookieFromRow(T &aRow, const OriginAttributes& aOriginAttributes)
+mozilla::UniquePtr<ConstCookie>
+nsCookieService::GetCookieFromRow(mozIStorageStatement *aRow,
+                                  const OriginAttributes &aOriginAttributes)
 {
   // Skip reading 'baseDomain' -- up to the caller.
   nsCString name, value, host, path;
   DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name);
   NS_ASSERT_SUCCESS(rv);
   rv = aRow->GetUTF8String(IDX_VALUE, value);
   NS_ASSERT_SUCCESS(rv);
   rv = aRow->GetUTF8String(IDX_HOST, host);
@@ -2748,328 +2724,151 @@ nsCookieService::GetCookieFromRow(T &aRo
 
   int64_t expiry = aRow->AsInt64(IDX_EXPIRY);
   int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED);
   int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME);
   bool isSecure = 0 != aRow->AsInt32(IDX_SECURE);
   bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY);
   int32_t sameSite = aRow->AsInt32(IDX_SAME_SITE);
 
-  // Create a new nsCookie and assign the data.
-  return nsCookie::Create(name, value, host, path,
-                          expiry,
-                          lastAccessed,
-                          creationTime,
-                          false,
-                          isSecure,
-                          isHttpOnly,
-                          aOriginAttributes,
-                          sameSite);
-}
-
-void
-nsCookieService::AsyncReadComplete()
-{
-  // We may be in the private browsing DB state, with a pending read on the
-  // default DB state. (This would occur if we started up in private browsing
-  // mode.) As long as we do all our operations on the default state, we're OK.
-  NS_ASSERTION(mDefaultDBState, "no default DBState");
-  NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
-  NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
-
-  mozStorageTransaction transaction(mDefaultDBState->dbConn, false);
-  // Merge the data read on the background thread with the data synchronously
-  // read on the main thread. Note that transactions on the cookie table may
-  // have occurred on the main thread since, making the background data stale.
-  for (uint32_t i = 0; i < mDefaultDBState->hostArray.Length(); ++i) {
-    const CookieDomainTuple &tuple = mDefaultDBState->hostArray[i];
-
-    // Tiebreak: if the given base domain has already been read in, ignore
-    // the background data. Note that readSet may contain domains that were
-    // queried but found not to be in the db -- that's harmless.
-    if (mDefaultDBState->readSet.GetEntry(tuple.key))
-      continue;
-
-    AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr, false);
-  }
-  DebugOnly<nsresult> rv = transaction.Commit();
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
-
-  mDefaultDBState->stmtReadDomain = nullptr;
-  mDefaultDBState->pendingRead = nullptr;
-  mDefaultDBState->readListener = nullptr;
-
-  // Close sync connection asynchronously: if we let destructor close, it may
-  // cause an expensive fsync operation on the main-thread.
-  if (mDefaultDBState->syncConn) {
-    mDefaultDBState->syncConn->AsyncClose(nullptr);
-    mDefaultDBState->syncConn = nullptr;
-  }
-  mDefaultDBState->hostArray.Clear();
-  mDefaultDBState->readSet.Clear();
-
-  COOKIE_LOGSTRING(LogLevel::Debug, ("Read(): %" PRIu32 " cookies read",
-                                  mDefaultDBState->cookieCount));
-
-  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-  if (os) {
-    os->NotifyObservers(nullptr, "cookie-db-read", nullptr);
-  }
-}
-
-void
-nsCookieService::CancelAsyncRead(bool aPurgeReadSet)
-{
-  // We may be in the private browsing DB state, with a pending read on the
-  // default DB state. (This would occur if we started up in private browsing
-  // mode.) As long as we do all our operations on the default state, we're OK.
-  NS_ASSERTION(mDefaultDBState, "no default DBState");
-  NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
-  NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
-
-  // Cancel the pending read, kill the read listener, and empty the array
-  // of data already read in on the background thread.
-  mDefaultDBState->readListener->Cancel();
-  DebugOnly<nsresult> rv = mDefaultDBState->pendingRead->Cancel();
-  NS_ASSERT_SUCCESS(rv);
-
-  mDefaultDBState->stmtReadDomain = nullptr;
-  mDefaultDBState->pendingRead = nullptr;
-  mDefaultDBState->readListener = nullptr;
-  mDefaultDBState->hostArray.Clear();
-
-  // Only clear the 'readSet' table if we no longer need to know what set of
-  // data is already accounted for.
-  if (aPurgeReadSet)
-    mDefaultDBState->readSet.Clear();
+  // Create a new constCookie and assign the data.
+  return mozilla::MakeUnique<ConstCookie>(name,
+                                          value,
+                                          host,
+                                          path,
+                                          expiry,
+                                          lastAccessed,
+                                          creationTime,
+                                          isSecure,
+                                          isHttpOnly,
+                                          aOriginAttributes,
+                                          sameSite);
 }
 
 void
-nsCookieService::EnsureReadDomain(const nsCookieKey &aKey)
+nsCookieService::EnsureReadComplete(bool aInitDBConn)
 {
-  NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
-    "not in default db state");
-
-  // Fast path 1: nothing to read, or we've already finished reading.
-  if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
-    return;
-
-  // Fast path 2: already read in this particular domain.
-  if (MOZ_LIKELY(mDefaultDBState->readSet.GetEntry(aKey)))
-    return;
-
-  // Read in the data synchronously.
-  // see IDX_NAME, etc. for parameter indexes
-  nsresult rv;
-  if (!mDefaultDBState->stmtReadDomain) {
-    // Cache the statement, since it's likely to be used again.
-    rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
-      "SELECT "
-        "name, "
-        "value, "
-        "host, "
-        "path, "
-        "expiry, "
-        "lastAccessed, "
-        "creationTime, "
-        "isSecure, "
-        "isHttpOnly, "
-        "baseDomain, "
-        "originAttributes, "
-        "sameSite "
-      "FROM moz_cookies "
-      "WHERE baseDomain = :baseDomain "
-      "  AND originAttributes = :originAttributes"),
-      getter_AddRefs(mDefaultDBState->stmtReadDomain));
-
-    if (NS_FAILED(rv)) {
-      // Recreate the database.
-      COOKIE_LOGSTRING(LogLevel::Debug,
-        ("EnsureReadDomain(): corruption detected when creating statement "
-         "with rv 0x%" PRIx32, static_cast<uint32_t>(rv)));
-      HandleCorruptDB(mDefaultDBState);
-      return;
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mInitializedDBStates) {
+    TimeStamp startBlockTime = TimeStamp::Now();
+    MonitorAutoLock lock(mMonitor);
+
+    while (!mInitializedDBStates) {
+      mMonitor.Wait();
     }
+    Telemetry::AccumulateTimeDelta(Telemetry::MOZ_SQLITE_COOKIES_BLOCK_MAIN_THREAD_MS,
+                                   startBlockTime);
+    mAccumulatedWaitTelemetry = true;
   }
-
-  NS_ASSERTION(mDefaultDBState->syncConn, "should have a sync db connection");
-
-  mozStorageStatementScoper scoper(mDefaultDBState->stmtReadDomain);
-
-  rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName(
-    NS_LITERAL_CSTRING("baseDomain"), aKey.mBaseDomain);
-  NS_ASSERT_SUCCESS(rv);
-
-  nsAutoCString suffix;
-  aKey.mOriginAttributes.CreateSuffix(suffix);
-  rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName(
-    NS_LITERAL_CSTRING("originAttributes"), suffix);
-  NS_ASSERT_SUCCESS(rv);
-
-  bool hasResult;
-  nsCString name, value, host, path;
-  AutoTArray<RefPtr<nsCookie>, kMaxCookiesPerHost> array;
-  while (true) {
-    rv = mDefaultDBState->stmtReadDomain->ExecuteStep(&hasResult);
-    if (NS_FAILED(rv)) {
-      // Recreate the database.
-      COOKIE_LOGSTRING(LogLevel::Debug,
-        ("EnsureReadDomain(): corruption detected when reading result "
-         "with rv 0x%" PRIx32, static_cast<uint32_t>(rv)));
-      HandleCorruptDB(mDefaultDBState);
-      return;
-    }
-
-    if (!hasResult)
-      break;
-
-    array.AppendElement(GetCookieFromRow(mDefaultDBState->stmtReadDomain,
-                                         aKey.mOriginAttributes));
+  if (!mInitializedDBConn && aInitDBConn && mDefaultDBState) {
+    InitDBConn();
   }
-
-  mozStorageTransaction transaction(mDefaultDBState->dbConn, false);
-  // Add the cookies to the table in a single operation. This makes sure that
-  // either all the cookies get added, or in the case of corruption, none.
-  for (uint32_t i = 0; i < array.Length(); ++i) {
-    AddCookieToList(aKey, array[i], mDefaultDBState, nullptr, false);
-  }
-  rv = transaction.Commit();
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
-
-  // Add it to the hashset of read entries, so we don't read it again.
-  mDefaultDBState->readSet.PutEntry(aKey);
-
-  COOKIE_LOGSTRING(LogLevel::Debug,
-    ("EnsureReadDomain(): %zu cookies read for base domain %s, "
-     " originAttributes = %s", array.Length(), aKey.mBaseDomain.get(),
-     suffix.get()));
 }
 
-void
-nsCookieService::EnsureReadComplete()
+OpenDBResult
+nsCookieService::Read()
 {
-  NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
-    "not in default db state");
-
-  // Fast path 1: nothing to read, or we've already finished reading.
-  if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
-    return;
-
-  // Cancel the pending read, so we don't get any more results.
-  CancelAsyncRead(false);
+  MOZ_ASSERT(NS_GetCurrentThread() == mThread);
+
+  // Set up a statement to delete any rows with a nullptr 'baseDomain'
+  // column. This takes care of any cookies set by browsers that don't
+  // understand the 'baseDomain' column, where the database schema version
+  // is from one that does. (This would occur when downgrading.)
+  nsresult rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+                  "DELETE FROM moz_cookies WHERE baseDomain ISNULL"));
+  NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
   // Read in the data synchronously.
   // see IDX_NAME, etc. for parameter indexes
   nsCOMPtr<mozIStorageStatement> stmt;
-  nsresult rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
+  rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT "
       "name, "
       "value, "
       "host, "
       "path, "
       "expiry, "
       "lastAccessed, "
       "creationTime, "
       "isSecure, "
       "isHttpOnly, "
       "baseDomain, "
       "originAttributes, "
       "sameSite "
     "FROM moz_cookies "
     "WHERE baseDomain NOTNULL"), getter_AddRefs(stmt));
 
-  if (NS_FAILED(rv)) {
-    // Recreate the database.
-    COOKIE_LOGSTRING(LogLevel::Debug,
-      ("EnsureReadComplete(): corruption detected when creating statement "
-       "with rv 0x%" PRIx32, static_cast<uint32_t>(rv)));
-    HandleCorruptDB(mDefaultDBState);
-    return;
+  NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+  if (NS_WARN_IF(!mReadArray.IsEmpty())) {
+    mReadArray.Clear();
   }
+  mReadArray.SetCapacity(kMaxNumberOfCookies);
 
   nsCString baseDomain, name, value, host, path;
   bool hasResult;
-  nsTArray<CookieDomainTuple> array(kMaxNumberOfCookies);
   while (true) {
     rv = stmt->ExecuteStep(&hasResult);
-    if (NS_FAILED(rv)) {
-      // Recreate the database.
-      COOKIE_LOGSTRING(LogLevel::Debug,
-        ("EnsureReadComplete(): corruption detected when reading result "
-         "with rv 0x%" PRIx32, static_cast<uint32_t>(rv)));
-      HandleCorruptDB(mDefaultDBState);
-      return;
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mReadArray.Clear();
+      return RESULT_RETRY;
     }
 
     if (!hasResult)
       break;
 
     // Make sure we haven't already read the data.
     stmt->GetUTF8String(IDX_BASE_DOMAIN, baseDomain);
 
     nsAutoCString suffix;
     OriginAttributes attrs;
     stmt->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix);
     // If PopulateFromSuffix failed we just ignore the OA attributes
     // that we don't support
     Unused << attrs.PopulateFromSuffix(suffix);
 
     nsCookieKey key(baseDomain, attrs);
-    if (mDefaultDBState->readSet.GetEntry(key))
-      continue;
-
-    CookieDomainTuple* tuple = array.AppendElement();
+    CookieDomainTuple* tuple = mReadArray.AppendElement();
     tuple->key = key;
     tuple->cookie = GetCookieFromRow(stmt, attrs);
   }
 
-  mozStorageTransaction transaction(mDefaultDBState->dbConn, false);
-  // Add the cookies to the table in a single operation. This makes sure that
-  // either all the cookies get added, or in the case of corruption, none.
-  for (uint32_t i = 0; i < array.Length(); ++i) {
-    CookieDomainTuple& tuple = array[i];
-    AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr,
-      false);
-  }
-  rv = transaction.Commit();
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
-
   mDefaultDBState->syncConn = nullptr;
-  mDefaultDBState->readSet.Clear();
-
-  COOKIE_LOGSTRING(LogLevel::Debug,
-    ("EnsureReadComplete(): %zu cookies read", array.Length()));
+
+  COOKIE_LOGSTRING(LogLevel::Debug, ("Read(): %zu cookies read", mReadArray.Length()));
+
+  return RESULT_OK;
 }
 
 NS_IMETHODIMP
 nsCookieService::ImportCookies(nsIFile *aCookieFile)
 {
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  EnsureReadComplete(true);
+
   // Make sure we're in the default DB state. We don't want people importing
   // cookies into a private browsing session!
   if (mDBState != mDefaultDBState) {
     NS_WARNING("Trying to import cookies in a private browsing session!");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   nsresult rv;
   nsCOMPtr<nsIInputStream> fileInputStream;
   rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), aCookieFile);
   if (NS_FAILED(rv)) return rv;
 
   nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
   if (NS_FAILED(rv)) return rv;
 
-  // First, ensure we've read in everything from the database, if we have one.
-  EnsureReadComplete();
-
   static const char kTrue[] = "TRUE";
 
   nsAutoCString buffer, baseDomain;
   bool isMore = true;
   int32_t hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex;
   nsACString::char_iterator iter;
   int32_t numInts;
   int64_t expires;
@@ -3278,16 +3077,18 @@ nsCookieService::GetCookiesForURI(nsIURI
 {
   NS_ASSERTION(aHostURI, "null host!");
 
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return;
   }
 
+  EnsureReadComplete(true);
+
   AutoRestore<DBState*> savePrevDBState(mDBState);
   mDBState = (aOriginAttrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
 
   // get the base domain, host, and path from the URI.
   // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
   // file:// URI's (i.e. with an empty host) are allowed, but any other
   // scheme must have a non-empty host. A trailing dot in the host
   // is acceptable.
@@ -3333,17 +3134,16 @@ nsCookieService::GetCookiesForURI(nsIURI
   }
 
   nsCookie *cookie;
   int64_t currentTimeInUsec = PR_Now();
   int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
   bool stale = false;
 
   nsCookieKey key(baseDomain, aOriginAttrs);
-  EnsureReadDomain(key);
 
   // perform the hash lookup
   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
   if (!entry)
     return;
 
   // iterate the cookies!
   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
@@ -3668,16 +3468,19 @@ nsCookieService::SetCookieInternal(nsIUR
 void
 nsCookieService::AddInternal(const nsCookieKey &aKey,
                              nsCookie          *aCookie,
                              int64_t            aCurrentTimeInUsec,
                              nsIURI            *aHostURI,
                              const char        *aCookieHeader,
                              bool               aFromHttp)
 {
+  MOZ_ASSERT(mInitializedDBStates);
+  MOZ_ASSERT(mInitializedDBConn);
+
   int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
 
   nsListIter exactIter;
   bool foundCookie = false;
   foundCookie = FindCookie(aKey, aCookie->Host(),
                            aCookie->Name(), aCookie->Path(), exactIter);
   bool foundSecureExact = foundCookie && exactIter.Cookie()->IsSecure();
   bool isSecure = true;
@@ -4571,17 +4374,16 @@ public:
   }
 };
 
 // purges expired and old cookies in a batch operation.
 already_AddRefed<nsIArray>
 nsCookieService::PurgeCookies(int64_t aCurrentTimeInUsec)
 {
   NS_ASSERTION(mDBState->hostTable.Count() > 0, "table is empty");
-  EnsureReadComplete();
 
   uint32_t initialCookieCount = mDBState->cookieCount;
   COOKIE_LOGSTRING(LogLevel::Debug,
     ("PurgeCookies(): beginning purge with %" PRIu32 " cookies and %" PRId64 " oldest age",
      mDBState->cookieCount, aCurrentTimeInUsec - mDBState->cookieOldestTime));
 
   typedef nsTArray<nsListIter> PurgeList;
   PurgeList purgeList(kMaxNumberOfCookies);
@@ -4721,16 +4523,18 @@ nsCookieService::CookieExistsNative(nsIC
   NS_ENSURE_ARG_POINTER(aOriginAttributes);
   NS_ENSURE_ARG_POINTER(aFoundCookie);
 
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  EnsureReadComplete(true);
+
   AutoRestore<DBState*> savePrevDBState(mDBState);
   mDBState = (aOriginAttributes->mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
 
   nsAutoCString host, name, path;
   nsresult rv = aCookie->GetHost(host);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = aCookie->GetName(name);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -4862,27 +4666,28 @@ NS_IMETHODIMP
 nsCookieService::CountCookiesFromHost(const nsACString &aHost,
                                       uint32_t         *aCountFromHost)
 {
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  EnsureReadComplete(true);
+
   // first, normalize the hostname, and fail if it contains illegal characters.
   nsAutoCString host(aHost);
   nsresult rv = NormalizeHost(host);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString baseDomain;
   rv = GetBaseDomainFromHost(mTLDService, host, baseDomain);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
-  EnsureReadDomain(key);
 
   // Return a count of all cookies, including expired.
   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
   *aCountFromHost = entry ? entry->GetCookies().Length() : 0;
   return NS_OK;
 }
 
 // get an enumerator of cookies stored by a particular host. this is provided by the
@@ -4896,16 +4701,18 @@ nsCookieService::GetCookiesFromHost(cons
 {
   MOZ_ASSERT(aArgc == 0 || aArgc == 1);
 
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  EnsureReadComplete(true);
+
   // first, normalize the hostname, and fail if it contains illegal characters.
   nsAutoCString host(aHost);
   nsresult rv = NormalizeHost(host);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString baseDomain;
   rv = GetBaseDomainFromHost(mTLDService, host, baseDomain);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -4918,17 +4725,16 @@ nsCookieService::GetCookiesFromHost(cons
                                   u"nsICookieManager2.getCookiesFromHost()",
                                   u"2");
   NS_ENSURE_SUCCESS(rv, rv);
 
   AutoRestore<DBState*> savePrevDBState(mDBState);
   mDBState = (attrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
 
   nsCookieKey key = nsCookieKey(baseDomain, attrs);
-  EnsureReadDomain(key);
 
   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
   if (!entry)
     return NS_NewEmptyEnumerator(aEnumerator);
 
   nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost);
   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
@@ -4964,16 +4770,17 @@ nsCookieService::GetCookiesWithOriginAtt
     const mozilla::OriginAttributesPattern& aPattern,
     const nsCString& aBaseDomain,
     nsISimpleEnumerator **aEnumerator)
 {
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return NS_ERROR_NOT_AVAILABLE;
   }
+  EnsureReadComplete(true);
 
   AutoRestore<DBState*> savePrevDBState(mDBState);
   mDBState = (aPattern.mPrivateBrowsingId.WasPassed() &&
       aPattern.mPrivateBrowsingId.Value() > 0) ? mPrivateDBState : mDefaultDBState;
 
   nsCOMArray<nsICookie> cookies;
   for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
     nsCookieEntry* entry = iter.Get();
@@ -5023,16 +4830,18 @@ nsCookieService::RemoveCookiesWithOrigin
     const mozilla::OriginAttributesPattern& aPattern,
     const nsCString& aBaseDomain)
 {
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already close?");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  EnsureReadComplete(true);
+
   AutoRestore<DBState*> savePrevDBState(mDBState);
   mDBState = (aPattern.mPrivateBrowsingId.WasPassed() &&
       aPattern.mPrivateBrowsingId.Value() > 0) ? mPrivateDBState : mDefaultDBState;
 
   mozStorageTransaction transaction(mDBState->dbConn, false);
   // Iterate the hash table of nsCookieEntry.
   for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
     nsCookieEntry* entry = iter.Get();
@@ -5071,18 +4880,16 @@ nsCookieService::RemoveCookiesWithOrigin
   return NS_OK;
 }
 
 // find an secure cookie specified by host and name
 bool
 nsCookieService::FindSecureCookie(const nsCookieKey &aKey,
                                   nsCookie          *aCookie)
 {
-  EnsureReadDomain(aKey);
-
   nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
   if (!entry)
     return false;
 
   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
     nsCookie *cookie = cookies[i];
     // isn't a match if insecure or a different name
@@ -5108,17 +4915,19 @@ nsCookieService::FindSecureCookie(const 
 // find an exact cookie specified by host, name, and path that hasn't expired.
 bool
 nsCookieService::FindCookie(const nsCookieKey    &aKey,
                             const nsCString& aHost,
                             const nsCString& aName,
                             const nsCString& aPath,
                             nsListIter           &aIter)
 {
-  EnsureReadDomain(aKey);
+  // Should |EnsureReadComplete| before.
+  MOZ_ASSERT(mInitializedDBStates);
+  MOZ_ASSERT(mInitializedDBConn);
 
   nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
   if (!entry)
     return false;
 
   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
     nsCookie *cookie = cookies[i];
--- a/netwerk/cookie/nsCookieService.h
+++ b/netwerk/cookie/nsCookieService.h
@@ -24,30 +24,35 @@
 #include "mozIStoragePendingStatement.h"
 #include "mozIStorageConnection.h"
 #include "mozIStorageRow.h"
 #include "mozIStorageCompletionCallback.h"
 #include "mozIStorageStatementCallback.h"
 #include "mozIStorageFunction.h"
 #include "nsIVariant.h"
 #include "nsIFile.h"
+#include "mozilla/Atomics.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Maybe.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/UniquePtr.h"
+
 
 using mozilla::OriginAttributes;
 
 class nsICookiePermission;
 class nsIEffectiveTLDService;
 class nsIIDNService;
 class nsIPrefBranch;
 class nsIObserverService;
 class nsIURI;
 class nsIChannel;
 class nsIArray;
+class nsIThread;
 class mozIStorageService;
 class mozIThirdPartyUtil;
 class ReadCookieDBListener;
 
 struct nsListIter;
 
 namespace mozilla {
 namespace net {
@@ -83,23 +88,62 @@ class nsCookieEntry : public nsCookieKey
     inline ArrayType& GetCookies() { return mCookies; }
 
     size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
   private:
     ArrayType mCookies;
 };
 
+// struct for a constant cookie for threadsafe
+struct ConstCookie
+{
+  ConstCookie(const nsCString& aName,
+              const nsCString& aValue,
+              const nsCString& aHost,
+              const nsCString& aPath,
+              int64_t aExpiry,
+              int64_t aLastAccessed,
+              int64_t aCreationTime,
+              bool aIsSecure,
+              bool aIsHttpOnly,
+              const OriginAttributes &aOriginAttributes,
+              int32_t aSameSite)
+    : name(aName)
+    , value(aValue)
+    , host(aHost)
+    , path(aPath)
+    , expiry(aExpiry)
+    , lastAccessed(aLastAccessed)
+    , creationTime(aCreationTime)
+    , isSecure(aIsSecure)
+    , isHttpOnly(aIsHttpOnly)
+    , originAttributes(aOriginAttributes)
+    , sameSite(aSameSite)
+  {
+  }
+
+  const nsCString name;
+  const nsCString value;
+  const nsCString host;
+  const nsCString path;
+  const int64_t expiry;
+  const int64_t lastAccessed;
+  const int64_t creationTime;
+  const bool isSecure;
+  const bool isHttpOnly;
+  const OriginAttributes originAttributes;
+  const int32_t sameSite;
+};
+
 // encapsulates a (key, nsCookie) tuple for temporary storage purposes.
 struct CookieDomainTuple
 {
   nsCookieKey key;
-  RefPtr<nsCookie> cookie;
-
-  size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+  mozilla::UniquePtr<ConstCookie> cookie;
 };
 
 // encapsulates in-memory and on-disk DB states, so we can
 // conveniently switch state when entering or exiting private browsing.
 struct DBState final
 {
   DBState() : cookieCount(0), cookieOldestTime(INT64_MAX), corruptFlag(OK)
   {
@@ -132,27 +176,19 @@ public:
   nsCOMPtr<mozIStorageAsyncStatement> stmtDelete;
   nsCOMPtr<mozIStorageAsyncStatement> stmtUpdate;
   CorruptFlag                     corruptFlag;
 
   // Various parts representing asynchronous read state. These are useful
   // while the background read is taking place.
   nsCOMPtr<mozIStorageConnection>       syncConn;
   nsCOMPtr<mozIStorageStatement>        stmtReadDomain;
-  nsCOMPtr<mozIStoragePendingStatement> pendingRead;
   // The asynchronous read listener. This is a weak ref (storage has ownership)
   // since it may need to outlive the DBState's database connection.
   ReadCookieDBListener*                 readListener;
-  // An array of (baseDomain, cookie) tuples representing data read in
-  // asynchronously. This is merged into hostTable once read is complete.
-  nsTArray<CookieDomainTuple>           hostArray;
-  // A hashset of baseDomains read in synchronously, while the async read is
-  // in flight. This is used to keep track of which data in hostArray is stale
-  // when the time comes to merge.
-  nsTHashtable<nsCookieKey>        readSet;
 
   // DB completion handlers.
   nsCOMPtr<mozIStorageStatementCallback>  insertListener;
   nsCOMPtr<mozIStorageStatementCallback>  updateListener;
   nsCOMPtr<mozIStorageStatementCallback>  removeListener;
   nsCOMPtr<mozIStorageCompletionCallback> closeListener;
 };
 
@@ -237,33 +273,32 @@ class nsCookieService final : public nsI
   void GetCookiesForURI(nsIURI *aHostURI, bool aIsForeign, bool aHttpBound, const OriginAttributes& aOriginAttrs, nsTArray<nsCookie*>& aCookieList);
 
   protected:
     virtual ~nsCookieService();
 
     void                          PrefChanged(nsIPrefBranch *aPrefBranch);
     void                          InitDBStates();
     OpenDBResult                  TryInitDB(bool aDeleteExistingDB);
+    void                          InitDBConn();
+    nsresult                      InitDBConnInternal();
     nsresult                      CreateTableWorker(const char* aName);
     nsresult                      CreateIndex();
     nsresult                      CreateTable();
     nsresult                      CreateTableForSchemaVersion6();
     nsresult                      CreateTableForSchemaVersion5();
     void                          CloseDBStates();
     void                          CleanupCachedStatements();
     void                          CleanupDefaultDBConnection();
     void                          HandleDBClosed(DBState* aDBState);
     void                          HandleCorruptDB(DBState* aDBState);
     void                          RebuildCorruptDB(DBState* aDBState);
     OpenDBResult                  Read();
-    template<class T> nsCookie*   GetCookieFromRow(T &aRow, const OriginAttributes& aOriginAttributes);
-    void                          AsyncReadComplete();
-    void                          CancelAsyncRead(bool aPurgeReadSet);
-    void                          EnsureReadDomain(const nsCookieKey &aKey);
-    void                          EnsureReadComplete();
+    mozilla::UniquePtr<ConstCookie> GetCookieFromRow(mozIStorageStatement *aRow, const OriginAttributes &aOriginAttributes);
+    void                          EnsureReadComplete(bool aInitDBConn);
     nsresult                      NormalizeHost(nsCString &aHost);
     nsresult                      GetCookieStringCommon(nsIURI *aHostURI, nsIChannel *aChannel, bool aHttpBound, char** aCookie);
     void                          GetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, bool aHttpBound, const OriginAttributes& aOriginAttrs, nsCString &aCookie);
     nsresult                      SetCookieStringCommon(nsIURI *aHostURI, const char *aCookieHeader, const char *aServerTime, nsIChannel *aChannel, bool aFromHttp);
     void                          SetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, nsDependentCString &aCookieHeader, const nsCString &aServerTime, bool aFromHttp, const OriginAttributes &aOriginAttrs, nsIChannel* aChannel);
     bool                          SetCookieInternal(nsIURI *aHostURI, const nsCookieKey& aKey, bool aRequireHostMatch, CookieStatus aStatus, nsDependentCString &aCookieHeader, int64_t aServerTime, bool aFromHttp, nsIChannel* aChannel);
     void                          AddInternal(const nsCookieKey& aKey, nsCookie *aCookie, int64_t aCurrentTimeInUsec, nsIURI *aHostURI, const char *aCookieHeader, bool aFromHttp);
     void                          RemoveCookieFromList(const nsListIter &aIter, mozIStorageBindingParamsArray *aParamsArray = nullptr);
@@ -321,16 +356,24 @@ class nsCookieService final : public nsI
     // cached prefs
     uint8_t                       mCookieBehavior; // BEHAVIOR_{ACCEPT, REJECTFOREIGN, REJECT, LIMITFOREIGN}
     bool                          mThirdPartySession;
     bool                          mLeaveSecureAlone;
     uint16_t                      mMaxNumberOfCookies;
     uint16_t                      mMaxCookiesPerHost;
     int64_t                       mCookiePurgeAge;
 
+    // thread
+    nsCOMPtr<nsIThread>           mThread;
+    mozilla::Monitor              mMonitor;
+    mozilla::Atomic<bool>         mInitializedDBStates;
+    mozilla::Atomic<bool>         mInitializedDBConn;
+    bool                          mAccumulatedWaitTelemetry;
+    nsTArray<CookieDomainTuple>   mReadArray;
+
     // friends!
     friend class DBListenerErrorHandler;
     friend class ReadCookieDBListener;
     friend class CloseCookieDBListener;
 
     static already_AddRefed<nsCookieService> GetSingleton();
     friend class mozilla::net::CookieServiceParent;
 };
--- a/netwerk/cookie/test/unit/test_bug1321912.js
+++ b/netwerk/cookie/test/unit/test_bug1321912.js
@@ -45,19 +45,19 @@ conn.executeSimpleSQL("PRAGMA wal_autoch
 let now = Date.now();
 conn.executeSimpleSQL("INSERT INTO moz_cookies(" +
   "baseDomain, host, name, value, path, expiry, " +
   "lastAccessed, creationTime, isSecure, isHttpOnly) VALUES (" +
   "'foo.com', '.foo.com', 'foo', 'bar=baz', '/', " +
   now + ", " + now + ", " + now + ", 1, 1)");
 
 // Now start the cookie service, and then check the fields in the table.
-
-const cs = Cc["@mozilla.org/cookieService;1"].
-           getService(Ci.nsICookieService);
+// Get sessionEnumerator to wait for the initialization in cookie thread
+const enumerator = Cc["@mozilla.org/cookieService;1"].
+                   getService(Ci.nsICookieManager).sessionEnumerator;
 
 do_check_true(conn.schemaVersion, 8);
 let stmt = conn.createStatement("SELECT sql FROM sqlite_master " +
                                   "WHERE type = 'table' AND " +
                                   "      name = 'moz_cookies'");
 try {
   do_check_true(stmt.executeStep());
   let sql = stmt.getString(0);
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -4399,16 +4399,26 @@
   "MOZ_SQLITE_COOKIES_READ_MS": {
     "record_in_processes": ["main", "content"],
     "expires_in_version": "40",
     "kind": "exponential",
     "high": 3000,
     "n_buckets": 10,
     "description": "Time spent on SQLite read() (ms) *** No longer needed (bug 1156565). Delete histogram and accumulation code! ***"
   },
+  "MOZ_SQLITE_COOKIES_BLOCK_MAIN_THREAD_MS": {
+    "record_in_processes": ["main"],
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "alert_emails": ["necko@mozilla.com", "junior@mozilla.com"],
+    "bug_numbers": [870460],
+    "high": 3000,
+    "n_buckets": 10,
+    "description": "Time spent on blocking main thread by startup cookie database read (ms)"
+  },
   "MOZ_SQLITE_COOKIES_READ_MAIN_THREAD_MS": {
     "record_in_processes": ["main", "content"],
     "expires_in_version": "40",
     "kind": "exponential",
     "high": 3000,
     "n_buckets": 10,
     "description": "Time spent on SQLite read() (ms) *** No longer needed (bug 1156565). Delete histogram and accumulation code! ***"
   },