Bug 1361322 - Don't try to open the favicons database on startup for existing profiles. r=adw draft
authorMarco Bonardo <mbonardo@mozilla.com>
Wed, 24 May 2017 15:19:39 +0200
changeset 584492 9732c0ea59579f1f1e64cfaab7ef7497212e8531
parent 584216 f81bcc23d37d7bec48f08b19a9327e93c54d37b5
child 630401 562af6008a39c969538f9a99d07c4c4d85a6444a
push id60761
push usermak77@bonardo.net
push dateThu, 25 May 2017 15:35:27 +0000
reviewersadw
bugs1361322
milestone55.0a1
Bug 1361322 - Don't try to open the favicons database on startup for existing profiles. r=adw Instead of opening and closing the favicons database at every startup to check whether it's corrupt, just use the ATTACH DATABASE query result to achieve that. Also avoid replacing places.sqlite in case of non-fatal errors during the connection initialization. MozReview-Commit-ID: 4VW3dtoSH07
toolkit/components/places/Database.cpp
toolkit/components/places/Database.h
toolkit/components/places/Shutdown.cpp
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -371,32 +371,21 @@ SetupDurability(nsCOMPtr<mozIStorageConn
     Preferences::GetInt(PREF_GROWTH_INCREMENT_KIB, 5 * BYTES_PER_KIBIBYTE);
   if (growthIncrementKiB > 0) {
     (void)aDBConn->SetGrowthIncrement(growthIncrementKiB * BYTES_PER_KIBIBYTE, EmptyCString());
   }
   return NS_OK;
 }
 
 nsresult
-AttachFaviconsDatabase(nsCOMPtr<mozIStorageConnection>& aDBConn) {
-  // Attach the favicons database to the main connection.
-  nsString path;
-  nsCOMPtr<nsIFile> file;
-  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file));
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = file->Append(DATABASE_FAVICONS_FILENAME);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = file->GetPath(path);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ATTACH DATABASE '") +
-    NS_ConvertUTF16toUTF8(path) + NS_LITERAL_CSTRING("' AS favicons"));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = aDBConn->ExecuteSimpleSQL(CREATE_ICONS_AFTERINSERT_TRIGGER);
+AttachDatabase(nsCOMPtr<mozIStorageConnection>& aDBConn,
+               const nsACString& aPath,
+               const nsACString& aName) {
+  nsresult rv = aDBConn->ExecuteSimpleSQL(
+    NS_LITERAL_CSTRING("ATTACH DATABASE '") + aPath + NS_LITERAL_CSTRING("' AS ") + aName);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // The journal limit must be set apart for each database.
   nsAutoCString journalSizePragma("PRAGMA favicons.journal_size_limit = ");
   journalSizePragma.AppendInt(DATABASE_MAX_WAL_BYTES + DATABASE_JOURNAL_OVERHEAD_BYTES);
   Unused << aDBConn->ExecuteSimpleSQL(journalSizePragma);
 
   return NS_OK;
@@ -525,17 +514,17 @@ Database::Init()
   MOZ_ASSERT(NS_IsMainThread());
 
   bool initSucceeded = false;
   auto guard = MakeScopeExit([&]() {
     if (!initSucceeded) {
       // If we bail out early here without doing anything else, the Database object
       // will leak due to the cycle between the shutdown blockers and itself, so
       // let's break the cycle first.
-      Shutdown();
+      Shutdown(false);
     }
   });
 
   nsCOMPtr<mozIStorageService> storage =
     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
   NS_ENSURE_STATE(storage);
 
   // Init the database file and connect to it.
@@ -554,34 +543,47 @@ Database::Init()
   // If the database connection still cannot be opened, it may just be locked
   // by third parties.  Send out a notification and interrupt initialization.
   if (NS_FAILED(rv)) {
     RefPtr<PlacesEvent> lockedEvent = new PlacesEvent(TOPIC_DATABASE_LOCKED);
     (void)NS_DispatchToMainThread(lockedEvent);
     return rv;
   }
 
-  // Initialize the icons database.
-  rv = InitFaviconsDatabaseFile(storage);
+  // Ensure the icons database exists.
+  rv = EnsureFaviconsDatabaseFile(storage);
   if (NS_FAILED(rv)) {
     RefPtr<PlacesEvent> lockedEvent = new PlacesEvent(TOPIC_DATABASE_LOCKED);
     (void)NS_DispatchToMainThread(lockedEvent);
     return rv;
   }
 
   // Initialize the database schema.  In case of failure the existing schema is
   // is corrupt or incoherent, thus the database should be replaced.
   bool databaseMigrated = false;
-  rv = InitSchema(&databaseMigrated);
+  rv = SetupDatabaseConnection(storage);
+  if (NS_SUCCEEDED(rv)) {
+    // Failing to initialize the schema always indicates a corruption.
+    if (NS_FAILED(InitSchema(&databaseMigrated))) {
+      rv = NS_ERROR_FILE_CORRUPTED;
+    }
+  }
   if (NS_FAILED(rv)) {
     mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
-    rv = BackupAndReplaceDatabaseFile(storage);
-    NS_ENSURE_SUCCESS(rv, rv);
-    // Try to initialize the schema again on the new database.
-    rv = InitSchema(&databaseMigrated);
+    // Some errors may not indicate a database corruption, for those cases we
+    // just bail out without throwing away a possibly valid places.sqlite.
+    if (rv == NS_ERROR_FILE_CORRUPTED) {
+      rv = BackupAndReplaceDatabaseFile(storage);
+      NS_ENSURE_SUCCESS(rv, rv);
+      // Try to initialize the new database again.
+      rv = SetupDatabaseConnection(storage);
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = InitSchema(&databaseMigrated);
+    }
+    // Bail out if we couldn't fix the database.
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (databaseMigrated) {
     mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_UPGRADED;
   }
 
   if (mDatabaseStatus != nsINavHistoryService::DATABASE_STATUS_OK) {
@@ -640,93 +642,79 @@ Database::Init()
   if (os) {
     (void)os->AddObserver(this, TOPIC_PROFILE_CHANGE_TEARDOWN, true);
   }
 
   return NS_OK;
 }
 
 nsresult
-Database::InitFaviconsDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage)
+Database::EnsureFaviconsDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIFile> databaseFile;
   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                                        getter_AddRefs(databaseFile));
   NS_ENSURE_SUCCESS(rv, rv);
   rv = databaseFile->Append(DATABASE_FAVICONS_FILENAME);
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool databaseFileExists = false;
   rv = databaseFile->Exists(&databaseFileExists);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Open the database file.  If it does not exist a new one will be created.
-  // Use an unshared connection, it will consume more memory but avoid shared
-  // cache contentions across threads.
-  // This also checks the database sanity, so we must do it regardless.
+  if (databaseFileExists) {
+    return NS_OK;
+  }
+
+  // Open the database file, this will also create it.
   nsCOMPtr<mozIStorageConnection> conn;
   rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(conn));
-  if (rv == NS_ERROR_FILE_CORRUPTED) {
-    rv = databaseFile->Remove(true);
-    NS_ENSURE_SUCCESS(rv, rv);
-    databaseFileExists = false;
-    rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(conn));
-  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Ensure we'll close the connection when done.
+  auto cleanup = MakeScopeExit([&] () {
+    // We cannot use AsyncClose() here, because by the time we try to ATTACH
+    // this database, its transaction could be still be running and that would
+    // cause the ATTACH query to fail.
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(conn->Close()));
+  });
+
+  int32_t defaultPageSize;
+  rv = conn->GetDefaultPageSize(&defaultPageSize);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = SetupDurability(conn, defaultPageSize);
   NS_ENSURE_SUCCESS(rv, rv);
 
-#define CLOSE_AND_BAILOUT_IF_FAILED(_rv)          \
-  PR_BEGIN_MACRO                                  \
-  if (NS_WARN_IF(NS_FAILED(_rv))) {               \
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(conn->Close())); \
-    return _rv;                                   \
-  }                                               \
-  PR_END_MACRO
-
-  if (!databaseFileExists) {
-    int32_t defaultPageSize;
-    rv = conn->GetDefaultPageSize(&defaultPageSize);
-    CLOSE_AND_BAILOUT_IF_FAILED(rv);
-    rv = SetupDurability(conn, defaultPageSize);
-    CLOSE_AND_BAILOUT_IF_FAILED(rv);
-
-    // Enable incremental vacuum for this database. Since it will contain even
-    // large blobs and can be cleared with history, it's worth to have it.
-    // Note that it will be necessary to manually use PRAGMA incremental_vacuum.
-    rv = conn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      "PRAGMA auto_vacuum = INCREMENTAL"
-    ));
-    CLOSE_AND_BAILOUT_IF_FAILED(rv);
+  // Enable incremental vacuum for this database. Since it will contain even
+  // large blobs and can be cleared with history, it's worth to have it.
+  // Note that it will be necessary to manually use PRAGMA incremental_vacuum.
+  rv = conn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "PRAGMA auto_vacuum = INCREMENTAL"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
 
-    // We are going to update the database, so everything from now on should be
-    // in a transaction for performances.
-    mozStorageTransaction transaction(conn, false);
-
-    rv = conn->ExecuteSimpleSQL(CREATE_MOZ_ICONS);
-    CLOSE_AND_BAILOUT_IF_FAILED(rv);
-    rv = conn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ICONS_ICONURLHASH);
-    CLOSE_AND_BAILOUT_IF_FAILED(rv);
+  // We are going to update the database, so everything from now on should be
+  // in a transaction for performances.
+  mozStorageTransaction transaction(conn, false);
+  rv = conn->ExecuteSimpleSQL(CREATE_MOZ_ICONS);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = conn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ICONS_ICONURLHASH);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = conn->ExecuteSimpleSQL(CREATE_MOZ_PAGES_W_ICONS);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = conn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PAGES_W_ICONS_ICONURLHASH);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = conn->ExecuteSimpleSQL(CREATE_MOZ_ICONS_TO_PAGES);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = transaction.Commit();
+  NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = conn->ExecuteSimpleSQL(CREATE_MOZ_PAGES_W_ICONS);
-    CLOSE_AND_BAILOUT_IF_FAILED(rv);
-    rv = conn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PAGES_W_ICONS_ICONURLHASH);
-    CLOSE_AND_BAILOUT_IF_FAILED(rv);
-
-    rv = conn->ExecuteSimpleSQL(CREATE_MOZ_ICONS_TO_PAGES);
-    CLOSE_AND_BAILOUT_IF_FAILED(rv);
-
-    rv = transaction.Commit();
-    CLOSE_AND_BAILOUT_IF_FAILED(rv);
-  }
-
-#undef CLOSE_AND_BAILOUT_IF_FAILED
-
-  rv = conn->Close();
-  NS_ENSURE_SUCCESS(rv, rv);
+  // The scope exit will take care of closing the connection.
 
   return NS_OK;
 }
 
 nsresult
 Database::InitDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage,
                            bool* aNewDatabaseCreated)
 {
@@ -828,58 +816,57 @@ Database::ForceCrashAndReplaceDatabase(c
     // We could force an application restart here, but we'd like to get these
     // cases reported to us, so let's force a crash instead.
     MOZ_CRASH_UNSAFE_OOL(aReason.get());
   }
   return NS_ERROR_FAILURE;
 }
 
 nsresult
-Database::InitSchema(bool* aDatabaseMigrated)
+Database::SetupDatabaseConnection(nsCOMPtr<mozIStorageService>& aStorage)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  *aDatabaseMigrated = false;
 
   // WARNING: any statement executed before setting the journal mode must be
   // finalized, since SQLite doesn't allow changing the journal mode if there
   // is any outstanding statement.
 
   {
     // Get the page size.  This may be different than the default if the
     // database file already existed with a different page size.
     nsCOMPtr<mozIStorageStatement> statement;
     nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
       MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"
     ), getter_AddRefs(statement));
     NS_ENSURE_SUCCESS(rv, rv);
     bool hasResult = false;
     rv = statement->ExecuteStep(&hasResult);
-    NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE);
+    NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FILE_CORRUPTED);
     rv = statement->GetInt32(0, &mDBPageSize);
-    NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && mDBPageSize > 0, NS_ERROR_UNEXPECTED);
+    NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && mDBPageSize > 0, NS_ERROR_FILE_CORRUPTED);
   }
 
   // Ensure that temp tables are held in memory, not on disk.
   nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA temp_store = MEMORY")
   );
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = SetupDurability(mMainConn, mDBPageSize);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString busyTimeoutPragma("PRAGMA busy_timeout = ");
   busyTimeoutPragma.AppendInt(DATABASE_BUSY_TIMEOUT_MS);
   (void)mMainConn->ExecuteSimpleSQL(busyTimeoutPragma);
 
-  // Enable FOREIGN KEY support.
+  // Enable FOREIGN KEY support. This is a strict requirement.
   rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA foreign_keys = ON")
   );
-  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_FILE_CORRUPTED);
 #ifdef DEBUG
   {
     // There are a few cases where setting foreign_keys doesn't work:
     //  * in the middle of a multi-statement transaction
     //  * if the SQLite library in use doesn't support them
     // Since we need foreign_keys, let's at least assert in debug mode.
     nsCOMPtr<mozIStorageStatement> stmt;
     mMainConn->CreateStatement(NS_LITERAL_CSTRING("PRAGMA foreign_keys"),
@@ -887,26 +874,59 @@ Database::InitSchema(bool* aDatabaseMigr
     bool hasResult = false;
     if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
       int32_t fkState = stmt->AsInt32(0);
       MOZ_ASSERT(fkState, "Foreign keys should be enabled");
     }
   }
 #endif
 
-  rv = AttachFaviconsDatabase(mMainConn);
+  // Attach the favicons database to the main connection.
+  nsCOMPtr<nsIFile> iconsFile;
+  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+                              getter_AddRefs(iconsFile));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = iconsFile->Append(DATABASE_FAVICONS_FILENAME);
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsString iconsPath;
+  rv = iconsFile->GetPath(iconsPath);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = AttachDatabase(mMainConn, NS_ConvertUTF16toUTF8(iconsPath),
+                      NS_LITERAL_CSTRING("favicons"));
+  if (NS_FAILED(rv)) {
+    // The favicons database may be corrupt. Try to replace and reattach it.
+    rv = iconsFile->Remove(true);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = EnsureFaviconsDatabaseFile(aStorage);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = AttachDatabase(mMainConn, NS_ConvertUTF16toUTF8(iconsPath),
+                        NS_LITERAL_CSTRING("favicons"));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // Create favicons temp entities.
+  rv = mMainConn->ExecuteSimpleSQL(CREATE_ICONS_AFTERINSERT_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // We use our functions during migration, so initialize them now.
   rv = InitFunctions();
   NS_ENSURE_SUCCESS(rv, rv);
 
+  return NS_OK;
+}
+
+nsresult
+Database::InitSchema(bool* aDatabaseMigrated)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  *aDatabaseMigrated = false;
+
   // Get the database schema version.
   int32_t currentSchemaVersion;
-  rv = mMainConn->GetSchemaVersion(&currentSchemaVersion);
+  nsresult rv = mMainConn->GetSchemaVersion(&currentSchemaVersion);
   NS_ENSURE_SUCCESS(rv, rv);
   bool databaseInitialized = currentSchemaVersion > 0;
 
   if (databaseInitialized && currentSchemaVersion == DATABASE_SCHEMA_VERSION) {
     // The database is up to date and ready to go.
     return NS_OK;
   }
 
@@ -2443,17 +2463,17 @@ Database::CreateMobileRoot()
 
   rv = addAnnoStmt->Execute();
   if (NS_FAILED(rv)) return -1;
 
   return rootId;
 }
 
 void
-Database::Shutdown()
+Database::Shutdown(bool aInitSucceeded)
 {
   // As the last step in the shutdown path, finalize the database handle.
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mClosed);
 
   // Break cycles with the shutdown blockers.
   mClientsShutdown = nullptr;
   nsCOMPtr<mozIStorageCompletionCallback> connectionShutdown = mConnectionShutdown.forget();
@@ -2461,79 +2481,69 @@ Database::Shutdown()
   if (!mMainConn) {
     // The connection has never been initialized. Just mark it as closed.
     mClosed = true;
     (void)connectionShutdown->Complete(NS_OK, nullptr);
     return;
   }
 
 #ifdef DEBUG
-  { // Sanity check for missing guids.
-    bool haveNullGuids = false;
+  if (aInitSucceeded) {
+    bool hasResult;
     nsCOMPtr<mozIStorageStatement> stmt;
 
+    // Sanity check for missing guids.
     nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
       "SELECT 1 "
       "FROM moz_places "
       "WHERE guid IS NULL "
     ), getter_AddRefs(stmt));
     MOZ_ASSERT(NS_SUCCEEDED(rv));
-    rv = stmt->ExecuteStep(&haveNullGuids);
+    rv = stmt->ExecuteStep(&hasResult);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
-    MOZ_ASSERT(!haveNullGuids, "Found a page without a GUID!");
-
+    MOZ_ASSERT(!hasResult, "Found a page without a GUID!");
     rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
       "SELECT 1 "
       "FROM moz_bookmarks "
       "WHERE guid IS NULL "
     ), getter_AddRefs(stmt));
     MOZ_ASSERT(NS_SUCCEEDED(rv));
-    rv = stmt->ExecuteStep(&haveNullGuids);
+    rv = stmt->ExecuteStep(&hasResult);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
-    MOZ_ASSERT(!haveNullGuids, "Found a bookmark without a GUID!");
-  }
+    MOZ_ASSERT(!hasResult, "Found a bookmark without a GUID!");
 
-  { // Sanity check for unrounded dateAdded and lastModified values (bug
+    // Sanity check for unrounded dateAdded and lastModified values (bug
     // 1107308).
-    bool hasUnroundedDates = false;
-    nsCOMPtr<mozIStorageStatement> stmt;
-
-    nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+    rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
         "SELECT 1 "
         "FROM moz_bookmarks "
         "WHERE dateAdded % 1000 > 0 OR lastModified % 1000 > 0 LIMIT 1"
       ), getter_AddRefs(stmt));
     MOZ_ASSERT(NS_SUCCEEDED(rv));
-    rv = stmt->ExecuteStep(&hasUnroundedDates);
+    rv = stmt->ExecuteStep(&hasResult);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
-    MOZ_ASSERT(!hasUnroundedDates, "Found unrounded dates!");
-  }
+    MOZ_ASSERT(!hasResult, "Found unrounded dates!");
 
-  { // Sanity check url_hash
-    bool hasNullHash = false;
-    nsCOMPtr<mozIStorageStatement> stmt;
-    nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+    // Sanity check url_hash
+    rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
       "SELECT 1 FROM moz_places WHERE url_hash = 0"
     ), getter_AddRefs(stmt));
     MOZ_ASSERT(NS_SUCCEEDED(rv));
-    rv = stmt->ExecuteStep(&hasNullHash);
+    rv = stmt->ExecuteStep(&hasResult);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
-    MOZ_ASSERT(!hasNullHash, "Found a place without a hash!");
-  }
+    MOZ_ASSERT(!hasResult, "Found a place without a hash!");
 
-  { // Sanity check unique urls
-    bool hasDupeUrls = false;
-    nsCOMPtr<mozIStorageStatement> stmt;
-    nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+    // Sanity check unique urls
+    rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
       "SELECT 1 FROM moz_places GROUP BY url HAVING count(*) > 1 "
     ), getter_AddRefs(stmt));
     MOZ_ASSERT(NS_SUCCEEDED(rv));
-    rv = stmt->ExecuteStep(&hasDupeUrls);
+    rv = stmt->ExecuteStep(&hasResult);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
-    MOZ_ASSERT(!hasDupeUrls, "Found a duplicate url!");
+    MOZ_ASSERT(!hasResult, "Found a duplicate url!");
   }
 #endif
 
   mMainThreadStatements.FinalizeStatements();
   mMainThreadAsyncStatements.FinalizeStatements();
 
   RefPtr< FinalizeStatementCacheProxy<mozIStorageStatement> > event =
     new FinalizeStatementCacheProxy<mozIStorageStatement>(
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -192,18 +192,20 @@ public:
   already_AddRefed<mozIStorageAsyncStatement> GetAsyncStatement(const nsACString& aQuery) const;
 
   uint32_t MaxUrlLength();
 
 protected:
   /**
    * Finalizes the cached statements and closes the database connection.
    * A TOPIC_PLACES_CONNECTION_CLOSED notification is fired when done.
+   *
+   * @param Whether database init succeeded.
    */
-  void Shutdown();
+  void Shutdown(bool aInitSucceeded);
 
   bool IsShutdownStarted() const;
 
   /**
    * Initializes the database file.  If the database does not exist or is
    * corrupt, a new one is created.  In case of corruption it also creates a
    * backup copy of the database.
    *
@@ -211,23 +213,22 @@ protected:
    *        mozStorage service instance.
    * @param aNewDatabaseCreated
    *        whether a new database file has been created.
    */
   nsresult InitDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage,
                             bool* aNewDatabaseCreated);
 
   /**
-   * Initializes the favicons database file.  If it does not exist or is
-   * corrupt, a new one is created.
+   * Ensure the favicons database file exists.
    *
    * @param aStorage
    *        mozStorage service instance.
    */
-  nsresult InitFaviconsDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage);
+  nsresult EnsureFaviconsDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage);
 
   /**
    * Creates a database backup and replaces the original file with a new
    * one.
    *
    * @param aStorage
    *        mozStorage service instance.
    */
@@ -235,17 +236,26 @@ protected:
 
   /**
    * This should be used as a last resort in case the database is corrupt and
    * there's no way to fix it in-place.
    */
   nsresult ForceCrashAndReplaceDatabase(const nsCString& aReason);
 
   /**
-   * Initializes the database.  This performs any necessary migrations for the
+   * Set up the connection environment through PRAGMAs.
+   * Will return NS_ERROR_FILE_CORRUPTED if any critical setting fails.
+   *
+   * @param aStorage
+   *        mozStorage service instance.
+   */
+  nsresult SetupDatabaseConnection(nsCOMPtr<mozIStorageService>& aStorage);
+
+  /**
+   * Initializes the schema.  This performs any necessary migrations for the
    * database.  All migration is done inside a transaction that is rolled back
    * if any error occurs.
    * @param aDatabaseMigrated
    *        Whether a schema upgrade happened.
    */
   nsresult InitSchema(bool* aDatabaseMigrated);
 
   /**
--- a/toolkit/components/places/Shutdown.cpp
+++ b/toolkit/components/places/Shutdown.cpp
@@ -183,17 +183,17 @@ ConnectionShutdownBlocker::BlockShutdown
 
   // At this stage, any use of this database is forbidden. Get rid of
   // `gDatabase`. Note, however, that the database could be
   // resurrected.  This can happen in particular during tests.
   MOZ_ASSERT(Database::gDatabase == nullptr || Database::gDatabase == mDatabase);
   Database::gDatabase = nullptr;
 
   // Database::Shutdown will invoke Complete once the connection is closed.
-  mDatabase->Shutdown();
+  mDatabase->Shutdown(true);
   mState = CALLED_STORAGESHUTDOWN;
   return NS_OK;
 }
 
 // mozIStorageCompletionCallback
 NS_IMETHODIMP
 ConnectionShutdownBlocker::Complete(nsresult, nsISupports*)
 {