Bug 1540777 - LSNG: Move creation of empty databases to the connection thread; r=asuth
☠☠ backed out by df3eadfa74a8 ☠ ☠
authorJan Varga <jan.varga@gmail.com>
Mon, 29 Apr 2019 04:42:37 +0200
changeset 531469 b5d8d57838a32c46a5579fab4eb15ffef62b7432
parent 531468 d1bd31177b1456ec930df76ba7806806bc919891
child 531470 688f361e8bb6aee726c30e3eb89cea01bca56183
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1540777
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1540777 - LSNG: Move creation of empty databases to the connection thread; r=asuth Differential Revision: https://phabricator.services.mozilla.com/D29134
dom/localstorage/ActorsParent.cpp
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -295,17 +295,17 @@ int32_t MakeSchemaVersion(uint32_t aMajo
 }
 
 nsCString GetArchivedOriginHashKey(const nsACString& aOriginSuffix,
                                    const nsACString& aOriginNoSuffix) {
   return aOriginSuffix + NS_LITERAL_CSTRING(":") + aOriginNoSuffix;
 }
 
 nsresult CreateTables(mozIStorageConnection* aConnection) {
-  AssertIsOnIOThread();
+  MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread());
   MOZ_ASSERT(aConnection);
 
   // Table `database`
   nsresult rv = aConnection->ExecuteSimpleSQL(
       NS_LITERAL_CSTRING("CREATE TABLE database"
                          "( origin TEXT NOT NULL"
                          ", usage INTEGER NOT NULL DEFAULT 0"
                          ", last_vacuum_time INTEGER NOT NULL DEFAULT 0"
@@ -425,17 +425,17 @@ nsresult SetDefaultPragmas(mozIStorageCo
 
   return NS_OK;
 }
 
 nsresult CreateStorageConnection(nsIFile* aDBFile, nsIFile* aUsageFile,
                                  const nsACString& aOrigin,
                                  mozIStorageConnection** aConnection,
                                  bool* aRemovedUsageFile) {
-  AssertIsOnIOThread();
+  MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread());
   MOZ_ASSERT(aDBFile);
   MOZ_ASSERT(aUsageFile);
   MOZ_ASSERT(aConnection);
   MOZ_ASSERT(aRemovedUsageFile);
 
   // aRemovedUsageFile has to be initialized even when this method fails.
   *aRemovedUsageFile = false;
 
@@ -839,17 +839,17 @@ nsresult GetShadowFile(const nsAString& 
     return rv;
   }
 
   archiveFile.forget(aArchiveFile);
   return NS_OK;
 }
 
 nsresult SetShadowJournalMode(mozIStorageConnection* aConnection) {
-  AssertIsOnIOThread();
+  MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread());
   MOZ_ASSERT(aConnection);
 
   // Try enabling WAL mode. This can fail in various circumstances so we have to
   // check the results here.
   NS_NAMED_LITERAL_CSTRING(journalModeQueryStart, "PRAGMA journal_mode = ");
   NS_NAMED_LITERAL_CSTRING(journalModeWAL, "wal");
 
   nsCOMPtr<mozIStorageStatement> stmt;
@@ -927,17 +927,17 @@ nsresult SetShadowJournalMode(mozIStorag
     }
   }
 
   return NS_OK;
 }
 
 nsresult CreateShadowStorageConnection(const nsAString& aBasePath,
                                        mozIStorageConnection** aConnection) {
-  AssertIsOnIOThread();
+  MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread());
   MOZ_ASSERT(!aBasePath.IsEmpty());
   MOZ_ASSERT(aConnection);
 
   nsCOMPtr<nsIFile> shadowFile;
   nsresult rv = GetShadowFile(aBasePath, getter_AddRefs(shadowFile));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -1439,25 +1439,30 @@ class DatastoreOperationBase : public Ru
         mMayProceed(true) {}
 
   ~DatastoreOperationBase() override { MOZ_ASSERT(!mMayProceed); }
 };
 
 class ConnectionDatastoreOperationBase : public DatastoreOperationBase {
  protected:
   RefPtr<Connection> mConnection;
+  /**
+   * This boolean flag is used by the CloseOp to avoid creating empty databases.
+   */
+  const bool mEnsureStorageConnection;
 
  public:
   // This callback will be called on the background thread before releasing the
   // final reference to this request object. Subclasses may perform any
   // additional cleanup here but must always call the base class implementation.
   virtual void Cleanup();
 
  protected:
-  ConnectionDatastoreOperationBase(Connection* aConnection);
+  ConnectionDatastoreOperationBase(Connection* aConnection,
+                                   bool aEnsureStorageConnection = true);
 
   ~ConnectionDatastoreOperationBase();
 
   // Must be overridden in subclasses. Called on the target thread to allow the
   // subclass to perform necessary datastore operations. A successful return
   // value will trigger an OnSuccess callback on the background thread while
   // while a failure value will trigger an OnFailure callback.
   virtual nsresult DoDatastoreWork() = 0;
@@ -1482,24 +1487,34 @@ class Connection final {
  public:
   class CachedStatement;
 
  private:
   class FlushOp;
   class CloseOp;
 
   RefPtr<ConnectionThread> mConnectionThread;
+  RefPtr<QuotaClient> mQuotaClient;
   nsCOMPtr<nsITimer> mFlushTimer;
   nsCOMPtr<mozIStorageConnection> mStorageConnection;
   nsAutoPtr<ArchivedOriginScope> mArchivedOriginScope;
   nsInterfaceHashtable<nsCStringHashKey, mozIStorageStatement>
       mCachedStatements;
   WriteOptimizer mWriteOptimizer;
   const nsCString mOrigin;
   const nsString mDirectoryPath;
+  /**
+   * Propagated from PrepareDatastoreOp. PrepareDatastoreOp may defer the
+   * creation of the localstorage client directory and database on the
+   * QuotaManager IO thread in its DatabaseWork method to
+   * Connection::EnsureStorageConnection, in which case the method needs to know
+   * it is responsible for taking those actions (without redundantly performing
+   * the existence checks).
+   */
+  const bool mDatabaseNotAvailable;
   bool mFlushScheduled;
 #ifdef DEBUG
   bool mInUpdateBatch;
 #endif
 
  public:
   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Connection)
 
@@ -1538,31 +1553,31 @@ class Connection final {
 
   //////////////////////////////////////////////////////////////////////////////
   // Methods which can only be called on the connection thread.
 
   nsresult EnsureStorageConnection();
 
   mozIStorageConnection* StorageConnection() const {
     AssertIsOnConnectionThread();
-    MOZ_ASSERT(mStorageConnection);
 
     return mStorageConnection;
   }
 
   void CloseStorageConnection();
 
   nsresult GetCachedStatement(const nsACString& aQuery,
                               CachedStatement* aCachedStatement);
 
  private:
   // Only created by ConnectionThread.
   Connection(ConnectionThread* aConnectionThread, const nsACString& aOrigin,
              const nsAString& aDirectoryPath,
-             nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope);
+             nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
+             bool aDatabaseNotAvailable);
 
   ~Connection();
 
   void ScheduleFlush();
 
   void Flush();
 
   static void FlushTimerCallback(nsITimer* aTimer, void* aClosure);
@@ -1588,33 +1603,34 @@ class Connection::CachedStatement final 
               already_AddRefed<mozIStorageStatement> aStatement);
 
   // No funny business allowed.
   CachedStatement(const CachedStatement&) = delete;
   CachedStatement& operator=(const CachedStatement&) = delete;
 };
 
 class Connection::FlushOp final : public ConnectionDatastoreOperationBase {
-  RefPtr<QuotaClient> mQuotaClient;
   WriteOptimizer mWriteOptimizer;
   bool mShadowWrites;
 
  public:
   FlushOp(Connection* aConnection, WriteOptimizer&& aWriteOptimizer);
 
  private:
   nsresult DoDatastoreWork() override;
 };
 
 class Connection::CloseOp final : public ConnectionDatastoreOperationBase {
   nsCOMPtr<nsIRunnable> mCallback;
 
  public:
   CloseOp(Connection* aConnection, nsIRunnable* aCallback)
-      : ConnectionDatastoreOperationBase(aConnection), mCallback(aCallback) {}
+      : ConnectionDatastoreOperationBase(aConnection,
+                                         /* aEnsureStorageConnection */ false),
+        mCallback(aCallback) {}
 
  private:
   nsresult DoDatastoreWork() override;
 
   void Cleanup() override;
 };
 
 class ConnectionThread final {
@@ -1631,17 +1647,18 @@ class ConnectionThread final {
   }
 
   bool IsOnConnectionThread();
 
   void AssertIsOnConnectionThread();
 
   already_AddRefed<Connection> CreateConnection(
       const nsACString& aOrigin, const nsAString& aDirectoryPath,
-      nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope);
+      nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
+      bool aDatabaseNotAvailable);
 
   void Shutdown();
 
   NS_INLINE_DECL_REFCOUNTING(ConnectionThread)
 
  private:
   ~ConnectionThread();
 };
@@ -2349,17 +2366,17 @@ class PrepareDatastoreOp
   nsString mDirectoryPath;
   nsString mDatabaseFilePath;
   uint32_t mPrivateBrowsingId;
   int64_t mUsage;
   int64_t mSizeOfKeys;
   int64_t mSizeOfItems;
   uint64_t mDatastoreId;
   NestedState mNestedState;
-  const bool mCreateIfNotExists;
+  const bool mForPreload;
   bool mDatabaseNotAvailable;
   bool mRequestedDirectoryLock;
   bool mInvalidated;
 
 #ifdef DEBUG
   int64_t mDEBUGUsage;
 #endif
 
@@ -4000,18 +4017,19 @@ nsresult WriteOptimizer::ClearInfo::Perf
  * DatastoreOperationBase
  ******************************************************************************/
 
 /*******************************************************************************
  * ConnectionDatastoreOperationBase
  ******************************************************************************/
 
 ConnectionDatastoreOperationBase::ConnectionDatastoreOperationBase(
-    Connection* aConnection)
-    : mConnection(aConnection) {
+    Connection* aConnection, bool aEnsureStorageConnection)
+    : mConnection(aConnection),
+      mEnsureStorageConnection(aEnsureStorageConnection) {
   MOZ_ASSERT(aConnection);
 }
 
 ConnectionDatastoreOperationBase::~ConnectionDatastoreOperationBase() {
   MOZ_ASSERT(!mConnection,
              "ConnectionDatabaseOperationBase::Cleanup() was not called by a "
              "subclass!");
 }
@@ -4035,22 +4053,30 @@ void ConnectionDatastoreOperationBase::O
 void ConnectionDatastoreOperationBase::RunOnConnectionThread() {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(mConnection);
   MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
 
   if (!MayProceedOnNonOwningThread()) {
     SetFailureCode(NS_ERROR_FAILURE);
   } else {
-    nsresult rv = mConnection->EnsureStorageConnection();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      SetFailureCode(rv);
-    } else {
-      MOZ_ASSERT(mConnection->StorageConnection());
-
+    nsresult rv = NS_OK;
+
+    // The boolean flag is only used by the CloseOp to avoid creating empty
+    // databases.
+    if (mEnsureStorageConnection) {
+      rv = mConnection->EnsureStorageConnection();
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        SetFailureCode(rv);
+      } else {
+        MOZ_ASSERT(mConnection->StorageConnection());
+      }
+    }
+
+    if (NS_SUCCEEDED(rv)) {
       rv = DoDatastoreWork();
       if (NS_FAILED(rv)) {
         SetFailureCode(rv);
       }
     }
   }
 
   MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
@@ -4086,21 +4112,24 @@ ConnectionDatastoreOperationBase::Run() 
 
 /*******************************************************************************
  * Connection implementation
  ******************************************************************************/
 
 Connection::Connection(ConnectionThread* aConnectionThread,
                        const nsACString& aOrigin,
                        const nsAString& aDirectoryPath,
-                       nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope)
+                       nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
+                       bool aDatabaseNotAvailable)
     : mConnectionThread(aConnectionThread),
+      mQuotaClient(QuotaClient::GetInstance()),
       mArchivedOriginScope(std::move(aArchivedOriginScope)),
       mOrigin(aOrigin),
       mDirectoryPath(aDirectoryPath),
+      mDatabaseNotAvailable(aDatabaseNotAvailable),
       mFlushScheduled(false)
 #ifdef DEBUG
       ,
       mInUpdateBatch(false)
 #endif
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!aOrigin.IsEmpty());
@@ -4198,31 +4227,93 @@ nsresult Connection::EnsureStorageConnec
 
   if (!mStorageConnection) {
     nsCOMPtr<nsIFile> file;
     nsresult rv = NS_NewLocalFile(mDirectoryPath, false, getter_AddRefs(file));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
+    if (mDatabaseNotAvailable) {
+      bool exists;
+      rv = file->Exists(&exists);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      if (!exists) {
+        rv = file->Create(nsIFile::DIRECTORY_TYPE, 0755);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+      }
+    }
+
     rv = file->Append(NS_LITERAL_STRING(DATA_FILE_NAME));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    nsString filePath;
-    rv = file->GetPath(filePath);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
     nsCOMPtr<mozIStorageConnection> storageConnection;
-    rv = GetStorageConnection(filePath, getter_AddRefs(storageConnection));
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
+
+    if (mDatabaseNotAvailable) {
+      QuotaManager* quotaManager = QuotaManager::Get();
+      MOZ_ASSERT(quotaManager);
+
+      RefPtr<Runnable> runnable =
+          NS_NewRunnableFunction("dom::localstorage::InitUsageRunnable",
+                                 [origin = mOrigin]() {
+                                   InitUsageForOrigin(origin, 0);
+                                 });
+
+      MOZ_ALWAYS_SUCCEEDS(
+          quotaManager->IOThread()->Dispatch(runnable, NS_DISPATCH_NORMAL));
+
+      nsCOMPtr<nsIFile> usageFile;
+      rv = GetUsageFile(mDirectoryPath, getter_AddRefs(usageFile));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      bool removedUsageFile;
+      rv = CreateStorageConnection(file, usageFile, mOrigin,
+                                   getter_AddRefs(storageConnection),
+                                   &removedUsageFile);
+
+      MOZ_ASSERT(!removedUsageFile);
+
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      MOZ_ASSERT(mQuotaClient);
+
+      MutexAutoLock shadowDatabaseLock(mQuotaClient->ShadowDatabaseMutex());
+
+      nsCOMPtr<mozIStorageConnection> shadowConnection;
+      if (!gInitializedShadowStorage) {
+        rv = CreateShadowStorageConnection(quotaManager->GetBasePath(),
+                                           getter_AddRefs(shadowConnection));
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+
+        gInitializedShadowStorage = true;
+      }
+    } else {
+      nsString filePath;
+      rv = file->GetPath(filePath);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      rv = GetStorageConnection(filePath, getter_AddRefs(storageConnection));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
     }
 
     mStorageConnection = storageConnection;
   }
 
   return NS_OK;
 }
 
@@ -4348,21 +4439,18 @@ void Connection::CachedStatement::Assign
   if (mStatement) {
     mScoper.emplace(mStatement);
   }
 }
 
 Connection::FlushOp::FlushOp(Connection* aConnection,
                              WriteOptimizer&& aWriteOptimizer)
     : ConnectionDatastoreOperationBase(aConnection),
-      mQuotaClient(QuotaClient::GetInstance()),
       mWriteOptimizer(std::move(aWriteOptimizer)),
-      mShadowWrites(gShadowWrites) {
-  MOZ_ASSERT(mQuotaClient);
-}
+      mShadowWrites(gShadowWrites) {}
 
 nsresult Connection::FlushOp::DoDatastoreWork() {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(mConnection);
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
@@ -4370,19 +4458,20 @@ nsresult Connection::FlushOp::DoDatastor
       mConnection->StorageConnection();
   MOZ_ASSERT(storageConnection);
 
   nsresult rv;
 
   Maybe<MutexAutoLock> shadowDatabaseLock;
 
   if (mShadowWrites) {
-    MOZ_ASSERT(mQuotaClient);
-
-    shadowDatabaseLock.emplace(mQuotaClient->ShadowDatabaseMutex());
+    MOZ_ASSERT(mConnection->mQuotaClient);
+
+    shadowDatabaseLock.emplace(
+        mConnection->mQuotaClient->ShadowDatabaseMutex());
 
     rv = AttachShadowDatabase(quotaManager->GetBasePath(), storageConnection);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   CachedStatement stmt;
@@ -4456,17 +4545,19 @@ nsresult Connection::FlushOp::DoDatastor
 
   return NS_OK;
 }
 
 nsresult Connection::CloseOp::DoDatastoreWork() {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(mConnection);
 
-  mConnection->CloseStorageConnection();
+  if (mConnection->StorageConnection()) {
+    mConnection->CloseStorageConnection();
+  }
 
   return NS_OK;
 }
 
 void Connection::CloseOp::Cleanup() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mConnection);
 
@@ -4504,23 +4595,25 @@ bool ConnectionThread::IsOnConnectionThr
 }
 
 void ConnectionThread::AssertIsOnConnectionThread() {
   MOZ_ASSERT(IsOnConnectionThread());
 }
 
 already_AddRefed<Connection> ConnectionThread::CreateConnection(
     const nsACString& aOrigin, const nsAString& aDirectoryPath,
-    nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope) {
+    nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
+    bool aDatabaseNotAvailable) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!aOrigin.IsEmpty());
   MOZ_ASSERT(!mConnections.GetWeak(aOrigin));
 
-  RefPtr<Connection> connection = new Connection(
-      this, aOrigin, aDirectoryPath, std::move(aArchivedOriginScope));
+  RefPtr<Connection> connection =
+      new Connection(this, aOrigin, aDirectoryPath,
+                     std::move(aArchivedOriginScope), aDatabaseNotAvailable);
   mConnections.Put(aOrigin, connection);
 
   return connection.forget();
 }
 
 void ConnectionThread::Shutdown() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mThread);
@@ -6285,18 +6378,18 @@ PrepareDatastoreOp::PrepareDatastoreOp(
               : aParams.get_LSRequestPrepareDatastoreParams().commonParams()),
       mContentParentId(aContentParentId),
       mPrivateBrowsingId(0),
       mUsage(0),
       mSizeOfKeys(0),
       mSizeOfItems(0),
       mDatastoreId(0),
       mNestedState(NestedState::BeforeNesting),
-      mCreateIfNotExists(aParams.type() ==
-                         LSRequestParams::TLSRequestPrepareDatastoreParams),
+      mForPreload(aParams.type() ==
+                  LSRequestParams::TLSRequestPreloadDatastoreParams),
       mDatabaseNotAvailable(false),
       mRequestedDirectoryLock(false),
       mInvalidated(false)
 #ifdef DEBUG
       ,
       mDEBUGUsage(0)
 #endif
 {
@@ -6610,77 +6703,109 @@ nsresult PrepareDatastoreOp::DatabaseWor
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     MOZ_ASSERT(gArchivedOrigins);
   }
 
   bool hasDataForMigration = mArchivedOriginScope->HasMatches(gArchivedOrigins);
 
-  bool createIfNotExists = mCreateIfNotExists || hasDataForMigration;
-
+  // Initialize the origin even when the origin directory doesn't exist and we
+  // don't have data for migration (except the case when we are just trying to
+  // preload data). GetQuotaObject in GetResponse would fail otherwise.
   nsCOMPtr<nsIFile> directoryEntry;
   rv = quotaManager->EnsureOriginIsInitialized(
-      PERSISTENCE_TYPE_DEFAULT, mSuffix, mGroup, mOrigin, createIfNotExists,
+      PERSISTENCE_TYPE_DEFAULT, mSuffix, mGroup, mOrigin,
+      /* aCreateIfNotExists */ !mForPreload || hasDataForMigration,
       getter_AddRefs(directoryEntry));
   if (rv == NS_ERROR_NOT_AVAILABLE) {
     return DatabaseNotAvailable();
   }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = directoryEntry->Append(NS_LITERAL_STRING(LS_DIRECTORY_NAME));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  rv = EnsureDirectoryEntry(directoryEntry, createIfNotExists,
+  rv = directoryEntry->GetPath(mDirectoryPath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // The ls directory doesn't need to be created when we don't have data for
+  // migration. It will be created on the connection thread in
+  // Connection::EnsureStorageConnection.
+  rv = EnsureDirectoryEntry(directoryEntry,
+                            /* aCreateIfNotExists */ hasDataForMigration,
                             /* aIsDirectory */ true);
   if (rv == NS_ERROR_NOT_AVAILABLE) {
-    return DatabaseNotAvailable();
-  }
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = directoryEntry->GetPath(mDirectoryPath);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (mForPreload) {
+      // There's nothing to preload. Finish the operation without creating a
+      // datastore in GetResponse (GetResponse won't create a datastore if
+      // mDatatabaseNotAvailable and mForPreload are both true).
+      return DatabaseNotAvailable();
+    }
+
+    // Keep going if !mForPreload so that we will populate mDatabaseFilePath
+    // which is needed by GetQuotaObject. We will still return via call to
+    // DatabaseNotAvailable() below in the !alreadyExisted else branch.
+  } else if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = directoryEntry->Append(NS_LITERAL_STRING(DATA_FILE_NAME));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  rv = directoryEntry->GetPath(mDatabaseFilePath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // The database doesn't need to be created when we don't have data for
+  // migration. It will be created on the connection thread in
+  // Connection::EnsureStorageConnection.
   bool alreadyExisted;
-  rv = EnsureDirectoryEntry(directoryEntry, createIfNotExists,
+  rv = EnsureDirectoryEntry(directoryEntry,
+                            /* aCreateIfNotExists */ hasDataForMigration,
                             /* aIsDirectory */ false, &alreadyExisted);
   if (rv == NS_ERROR_NOT_AVAILABLE) {
-    return DatabaseNotAvailable();
-  }
-  if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (mForPreload) {
+      // There's nothing to preload, finish the operation without creating a
+      // datastore in GetResponse (GetResponse won't create a datastore if
+      // mDatatabaseNotAvailable and mForPreload are both true).
+      return DatabaseNotAvailable();
+    }
+  } else if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (alreadyExisted) {
     MOZ_ASSERT(gUsages);
     DebugOnly<bool> hasUsage = gUsages->Get(mOrigin, &mUsage);
     MOZ_ASSERT(hasUsage);
   } else {
+    MOZ_ASSERT(!mForPreload);
+
+    if (!hasDataForMigration) {
+      // The ls directory or database doesn't exist and we don't have data for
+      // migration. Finish the operation, but create an empty datastore in
+      // GetResponse (GetResponse will create an empty datastore if
+      // mDatabaseNotAvailable is true and mForPreload is false).
+      return DatabaseNotAvailable();
+    }
+
     MOZ_ASSERT(mUsage == 0);
     InitUsageForOrigin(mOrigin, mUsage);
   }
 
-  rv = directoryEntry->GetPath(mDatabaseFilePath);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
   nsCOMPtr<nsIFile> usageFile;
   rv = GetUsageFile(mDirectoryPath, getter_AddRefs(usageFile));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCOMPtr<mozIStorageConnection> connection;
   bool removedUsageFile;
@@ -6865,16 +6990,19 @@ nsresult PrepareDatastoreOp::EnsureDirec
   bool exists;
   nsresult rv = aEntry->Exists(&exists);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (!exists) {
     if (!aCreateIfNotExists) {
+      if (aAlreadyExisted) {
+        *aAlreadyExisted = false;
+      }
       return NS_ERROR_NOT_AVAILABLE;
     }
 
     if (aIsDirectory) {
       rv = aEntry->Create(nsIFile::DIRECTORY_TYPE, 0755);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
@@ -6958,17 +7086,18 @@ nsresult PrepareDatastoreOp::BeginLoadDa
     return NS_ERROR_FAILURE;
   }
 
   if (!gConnectionThread) {
     gConnectionThread = new ConnectionThread();
   }
 
   mConnection = gConnectionThread->CreateConnection(
-      mOrigin, mDirectoryPath, std::move(mArchivedOriginScope));
+      mOrigin, mDirectoryPath, std::move(mArchivedOriginScope),
+      /* aDatabaseNotAvailable */ false);
   MOZ_ASSERT(mConnection);
 
   // Must set this before dispatching otherwise we will race with the
   // connection thread.
   mNestedState = NestedState::DatabaseWorkLoadData;
 
   // Can't assign to mLoadDataOp directly since that's a weak reference and
   // LoadDataOp is reference counted.
@@ -7055,32 +7184,48 @@ nsresult PrepareDatastoreOp::NestedRun()
   return NS_OK;
 }
 
 void PrepareDatastoreOp::GetResponse(LSRequestResponse& aResponse) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::SendingResults);
   MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
 
-  if (mDatabaseNotAvailable) {
-    MOZ_ASSERT(!mCreateIfNotExists);
-
+  // A datastore is not created when we are just trying to preload data and
+  // there's no database file.
+  if (mDatabaseNotAvailable && mForPreload) {
     LSRequestPreloadDatastoreResponse preloadDatastoreResponse;
 
     aResponse = preloadDatastoreResponse;
 
     return;
   }
 
   if (!mDatastore) {
     MOZ_ASSERT(mUsage == mDEBUGUsage);
 
     RefPtr<QuotaObject> quotaObject;
 
     if (mPrivateBrowsingId == 0) {
+      if (!mConnection) {
+        // This can happen when there's no database file.
+        MOZ_ASSERT(mDatabaseNotAvailable);
+
+        // Even though there's no database file, we need to create a connection
+        // and pass it to datastore.
+        if (!gConnectionThread) {
+          gConnectionThread = new ConnectionThread();
+        }
+
+        mConnection = gConnectionThread->CreateConnection(
+            mOrigin, mDirectoryPath, std::move(mArchivedOriginScope),
+            /* aDatabaseNotAvailable */ true);
+        MOZ_ASSERT(mConnection);
+      }
+
       quotaObject = GetQuotaObject();
       MOZ_ASSERT(quotaObject);
     }
 
     mDatastore = new Datastore(mOrigin, mPrivateBrowsingId, mUsage, mSizeOfKeys,
                                mSizeOfItems, mDirectoryLock.forget(),
                                mConnection.forget(), quotaObject.forget(),
                                mValues, mOrderedItems);
@@ -7094,38 +7239,38 @@ void PrepareDatastoreOp::GetResponse(LSR
     MOZ_ASSERT(!gDatastores->Get(mOrigin));
     gDatastores->Put(mOrigin, mDatastore);
   }
 
   mDatastoreId = ++gLastDatastoreId;
 
   nsAutoPtr<PreparedDatastore> preparedDatastore(
       new PreparedDatastore(mDatastore, mContentParentId, mOrigin, mDatastoreId,
-                            /* aForPreload */ !mCreateIfNotExists));
+                            /* aForPreload */ mForPreload));
 
   if (!gPreparedDatastores) {
     gPreparedDatastores = new PreparedDatastoreHashtable();
   }
   gPreparedDatastores->Put(mDatastoreId, preparedDatastore);
 
   if (mInvalidated) {
     preparedDatastore->Invalidate();
   }
 
   preparedDatastore.forget();
 
-  if (mCreateIfNotExists) {
+  if (mForPreload) {
+    LSRequestPreloadDatastoreResponse preloadDatastoreResponse;
+
+    aResponse = preloadDatastoreResponse;
+  } else {
     LSRequestPrepareDatastoreResponse prepareDatastoreResponse;
     prepareDatastoreResponse.datastoreId() = mDatastoreId;
 
     aResponse = prepareDatastoreResponse;
-  } else {
-    LSRequestPreloadDatastoreResponse preloadDatastoreResponse;
-
-    aResponse = preloadDatastoreResponse;
   }
 }
 
 void PrepareDatastoreOp::Cleanup() {
   AssertIsOnOwningThread();
 
   if (mDatastore) {
     MOZ_ASSERT(mDatastoreId > 0);