Bug 1286798 - Part 34: Queue database operations until update batch ends; r=asuth
authorJan Varga <jan.varga@gmail.com>
Thu, 29 Nov 2018 21:49:04 +0100
changeset 508032 3adfef6668aae569ca9086120be540308c005a70
parent 508031 eba8447d393532c4f429c84a14c9a3a3acb5c447
child 508033 6214aafc061f09377c40eca490cc12621169c892
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1286798
milestone65.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 1286798 - Part 34: Queue database operations until update batch ends; r=asuth This avoids dispatching to the connection thread for every database operation and paves a way for database shadowing.
dom/localstorage/ActorsParent.cpp
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -108,18 +108,16 @@ const uint32_t kSQLiteGrowthIncrement = 
 static_assert(kSQLiteGrowthIncrement >= 0 &&
               kSQLiteGrowthIncrement % kSQLitePageSizeOverride == 0 &&
               kSQLiteGrowthIncrement < uint32_t(INT32_MAX),
               "Must be 0 (disabled) or a positive multiple of the page size!");
 
 #define DATA_FILE_NAME "data.sqlite"
 #define JOURNAL_FILE_NAME "data.sqlite-journal"
 
-const uint32_t kAutoCommitTimeoutMs = 5000;
-
 const char kPrivateBrowsingObserverTopic[] = "last-pb-context-exited";
 
 const uint32_t kDefaultOriginLimitKB = 5 * 1024;
 const uint32_t kDefaultSnapshotPrefill = 4096;
 const char kDefaultQuotaPref[] = "dom.storage.default_quota";
 const char kSnapshotPrefillPref[] = "dom.storage.snapshot_prefill";
 
 const uint32_t kPreparedDatastoreTimeoutMs = 20000;
@@ -775,66 +773,71 @@ private:
 class Connection final
 {
   friend class ConnectionThread;
 
 public:
   class CachedStatement;
 
 private:
-  class BeginOp;
-  class CommitOp;
+  class WriteInfo;
+  class SetItemInfo;
+  class RemoveItemInfo;
+  class ClearInfo;
+  class EndUpdateBatchOp;
   class CloseOp;
 
   RefPtr<ConnectionThread> mConnectionThread;
   nsCOMPtr<mozIStorageConnection> mStorageConnection;
+  nsTArray<nsAutoPtr<WriteInfo>> mWriteInfos;
   nsInterfaceHashtable<nsCStringHashKey, mozIStorageStatement>
     mCachedStatements;
   const nsCString mOrigin;
   const nsString mFilePath;
-  bool mInTransaction;
+#ifdef DEBUG
+  bool mInUpdateBatch;
+#endif
 
 public:
   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Connection)
 
   void
   AssertIsOnOwningThread() const
   {
     NS_ASSERT_OWNINGTHREAD(Connection);
   }
 
   // Methods which can only be called on the owning thread.
 
-  bool
-  InTransaction()
-  {
-    AssertIsOnOwningThread();
-    return mInTransaction;
-  }
-
   // This method is used to asynchronously execute a connection datastore
   // operation on the connection thread.
   void
   Dispatch(ConnectionDatastoreOperationBase* aOp);
 
-  // This method is used to asynchronously start a transaction on the connection
-  // thread.
-  void
-  Begin(bool aReadonly);
-
-  // This method is used to asynchronously end a transaction on the connection
-  // thread.
-  void
-  Commit();
-
   // This method is used to asynchronously close the storage connection on the
   // connection thread.
   void
   Close(nsIRunnable* aCallback);
 
+  void
+  SetItem(const nsString& aKey,
+          const nsString& aValue);
+
+  void
+  RemoveItem(const nsString& aKey);
+
+  void
+  Clear();
+
+  void
+  BeginUpdateBatch();
+
+  void
+  EndUpdateBatch();
+
   // Methods which can only be called on the connection thread.
 
   nsresult
   EnsureStorageConnection();
 
   mozIStorageConnection*
   StorageConnection() const
   {
@@ -880,40 +883,82 @@ private:
   Assign(Connection* aConnection,
          already_AddRefed<mozIStorageStatement> aStatement);
 
   // No funny business allowed.
   CachedStatement(const CachedStatement&) = delete;
   CachedStatement& operator=(const CachedStatement&) = delete;
 };
 
-class Connection::BeginOp final
-  : public ConnectionDatastoreOperationBase
-{
-  const bool mReadonly;
+class Connection::WriteInfo
+{
+public:
+  virtual nsresult
+  Perform(Connection* aConnection) = 0;
+
+  virtual ~WriteInfo() = default;
+};
+
+class Connection::SetItemInfo final
+  : public WriteInfo
+{
+  nsString mKey;
+  nsString mValue;
 
 public:
-  BeginOp(Connection* aConnection,
-          bool aReadonly)
-    : ConnectionDatastoreOperationBase(aConnection)
-    , mReadonly(aReadonly)
+  SetItemInfo(const nsAString& aKey,
+              const nsAString& aValue)
+    : mKey(aKey)
+    , mValue(aValue)
   { }
 
 private:
   nsresult
-  DoDatastoreWork() override;
+  Perform(Connection* aConnection) override;
 };
 
-class Connection::CommitOp final
-  : public ConnectionDatastoreOperationBase
+class Connection::RemoveItemInfo final
+  : public WriteInfo
+{
+  nsString mKey;
+
+public:
+  explicit RemoveItemInfo(const nsAString& aKey)
+    : mKey(aKey)
+  { }
+
+private:
+  nsresult
+  Perform(Connection* aConnection) override;
+};
+
+class Connection::ClearInfo final
+  : public WriteInfo
 {
 public:
-  explicit CommitOp(Connection* aConnection)
+  ClearInfo()
+  { }
+
+private:
+  nsresult
+  Perform(Connection* aConnection) override;
+};
+
+class Connection::EndUpdateBatchOp final
+  : public ConnectionDatastoreOperationBase
+{
+  nsTArray<nsAutoPtr<WriteInfo>> mWriteInfos;
+
+public:
+  explicit EndUpdateBatchOp(Connection* aConnection,
+                            nsTArray<nsAutoPtr<WriteInfo>>& aWriteInfos)
     : ConnectionDatastoreOperationBase(aConnection)
-  { }
+  {
+    mWriteInfos.SwapElements(aWriteInfos);
+  }
 
 private:
   nsresult
   DoDatastoreWork() override;
 };
 
 class Connection::CloseOp final
   : public ConnectionDatastoreOperationBase
@@ -965,72 +1010,21 @@ public:
   Shutdown();
 
   NS_INLINE_DECL_REFCOUNTING(ConnectionThread)
 
 private:
   ~ConnectionThread();
 };
 
-class SetItemOp final
-  : public ConnectionDatastoreOperationBase
-{
-  nsString mKey;
-  nsString mValue;
-
-public:
-  SetItemOp(Connection* aConnection,
-            const nsAString& aKey,
-            const nsAString& aValue)
-    : ConnectionDatastoreOperationBase(aConnection)
-    , mKey(aKey)
-    , mValue(aValue)
-  { }
-
-private:
-  nsresult
-  DoDatastoreWork() override;
-};
-
-class RemoveItemOp final
-  : public ConnectionDatastoreOperationBase
-{
-  nsString mKey;
-
-public:
-  RemoveItemOp(Connection* aConnection,
-               const nsAString& aKey)
-    : ConnectionDatastoreOperationBase(aConnection)
-    , mKey(aKey)
-  { }
-
-private:
-  nsresult
-  DoDatastoreWork() override;
-};
-
-class ClearOp final
-  : public ConnectionDatastoreOperationBase
-{
-public:
-  explicit ClearOp(Connection* aConnection)
-    : ConnectionDatastoreOperationBase(aConnection)
-  { }
-
-private:
-  nsresult
-  DoDatastoreWork() override;
-};
-
 class Datastore final
 {
   RefPtr<DirectoryLock> mDirectoryLock;
   RefPtr<Connection> mConnection;
   RefPtr<QuotaObject> mQuotaObject;
-  nsCOMPtr<nsITimer> mAutoCommitTimer;
   nsCOMPtr<nsIRunnable> mCompleteCallback;
   nsTHashtable<nsPtrHashKey<PrepareDatastoreOp>> mPrepareDatastoreOps;
   nsTHashtable<nsPtrHashKey<PreparedDatastore>> mPreparedDatastores;
   nsTHashtable<nsPtrHashKey<Database>> mDatabases;
   nsTHashtable<nsPtrHashKey<Database>> mActiveDatabases;
   nsDataHashtable<nsStringHashKey, nsString> mValues;
   nsDataHashtable<nsStringHashKey, uint32_t> mUpdateBatchRemovals;
   nsTArray<nsString> mUpdateBatchAppends;
@@ -1193,22 +1187,16 @@ private:
                   bool aAffectsOrder);
 
   void
   NotifyObservers(Database* aDatabase,
                   const nsString& aDocumentURI,
                   const nsString& aKey,
                   const nsString& aOldValue,
                   const nsString& aNewValue);
-
-  void
-  EnsureTransaction();
-
-  static void
-  AutoCommitTimerCallback(nsITimer* aTimer, void* aClosure);
 };
 
 class PreparedDatastore
 {
   RefPtr<Datastore> mDatastore;
   nsCOMPtr<nsITimer> mTimer;
   // Strings share buffers if possible, so it's not a problem to duplicate the
   // origin here.
@@ -2734,76 +2722,113 @@ ConnectionDatastoreOperationBase::Run()
  ******************************************************************************/
 
 Connection::Connection(ConnectionThread* aConnectionThread,
                        const nsACString& aOrigin,
                        const nsAString& aFilePath)
   : mConnectionThread(aConnectionThread)
   , mOrigin(aOrigin)
   , mFilePath(aFilePath)
-  , mInTransaction(false)
+#ifdef DEBUG
+  , mInUpdateBatch(false)
+#endif
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!aOrigin.IsEmpty());
   MOZ_ASSERT(!aFilePath.IsEmpty());
 }
 
 Connection::~Connection()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!mStorageConnection);
   MOZ_ASSERT(!mCachedStatements.Count());
-  MOZ_ASSERT(!mInTransaction);
+  MOZ_ASSERT(!mInUpdateBatch);
 }
 
 void
 Connection::Dispatch(ConnectionDatastoreOperationBase* aOp)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mConnectionThread);
 
   MOZ_ALWAYS_SUCCEEDS(mConnectionThread->mThread->Dispatch(aOp,
                                                            NS_DISPATCH_NORMAL));
 }
 
 void
-Connection::Begin(bool aReadonly)
-{
-  AssertIsOnOwningThread();
-
-  RefPtr<BeginOp> op = new BeginOp(this, aReadonly);
-
-  Dispatch(op);
-
-  mInTransaction = true;
-}
-
-void
-Connection::Commit()
-{
-  AssertIsOnOwningThread();
-
-  RefPtr<CommitOp> op = new CommitOp(this);
-
-  Dispatch(op);
-
-  mInTransaction = false;
-}
-
-void
 Connection::Close(nsIRunnable* aCallback)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aCallback);
 
   RefPtr<CloseOp> op = new CloseOp(this, aCallback);
 
   Dispatch(op);
 }
 
+void
+Connection::SetItem(const nsString& aKey,
+                    const nsString& aValue)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mInUpdateBatch);
+
+  nsAutoPtr<WriteInfo> writeInfo(new SetItemInfo(aKey, aValue));
+  mWriteInfos.AppendElement(writeInfo.forget());
+}
+
+void
+Connection::RemoveItem(const nsString& aKey)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mInUpdateBatch);
+
+  nsAutoPtr<WriteInfo> writeInfo(new RemoveItemInfo(aKey));
+  mWriteInfos.AppendElement(writeInfo.forget());
+}
+
+void
+Connection::Clear()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mInUpdateBatch);
+
+  nsAutoPtr<WriteInfo> writeInfo(new ClearInfo());
+  mWriteInfos.AppendElement(writeInfo.forget());
+}
+
+void
+Connection::BeginUpdateBatch()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!mInUpdateBatch);
+
+#ifdef DEBUG
+  mInUpdateBatch = true;
+#endif
+}
+
+void
+Connection::EndUpdateBatch()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mInUpdateBatch);
+
+  if (!mWriteInfos.IsEmpty()) {
+    RefPtr<EndUpdateBatchOp> op = new EndUpdateBatchOp(this, mWriteInfos);
+
+    Dispatch(op);
+  }
+
+#ifdef DEBUG
+  mInUpdateBatch = false;
+#endif
+}
+
 nsresult
 Connection::EnsureStorageConnection()
 {
   AssertIsOnConnectionThread();
 
   if (!mStorageConnection) {
     nsCOMPtr<mozIStorageConnection> storageConnection;
     nsresult rv =
@@ -2906,51 +2931,125 @@ CachedStatement::Assign(Connection* aCon
 
   if (mStatement) {
     mScoper.emplace(mStatement);
   }
 }
 
 nsresult
 Connection::
-BeginOp::DoDatastoreWork()
+SetItemInfo::Perform(Connection* aConnection)
 {
   AssertIsOnConnectionThread();
-  MOZ_ASSERT(mConnection);
-
-  CachedStatement stmt;
-  nsresult rv;
-  if (mReadonly) {
-    rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING("BEGIN;"), &stmt);
-  } else {
-    rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING("BEGIN IMMEDIATE;"),
-                                         &stmt);
-  }
+  MOZ_ASSERT(aConnection);
+
+  Connection::CachedStatement stmt;
+  nsresult rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+    "INSERT OR REPLACE INTO data (key, value) "
+    "VALUES(:key, :value)"),
+    &stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"), mKey);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->BindStringByName(NS_LITERAL_CSTRING("value"), mValue);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = stmt->Execute();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 nsresult
 Connection::
-CommitOp::DoDatastoreWork()
+RemoveItemInfo::Perform(Connection* aConnection)
+{
+  AssertIsOnConnectionThread();
+  MOZ_ASSERT(aConnection);
+
+  Connection::CachedStatement stmt;
+  nsresult rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+    "DELETE FROM data "
+      "WHERE key = :key;"),
+    &stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"), mKey);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+Connection::
+ClearInfo::Perform(Connection* aConnection)
+{
+  AssertIsOnConnectionThread();
+  MOZ_ASSERT(aConnection);
+
+  Connection::CachedStatement stmt;
+  nsresult rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+    "DELETE FROM data;"),
+    &stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+Connection::
+EndUpdateBatchOp::DoDatastoreWork()
 {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(mConnection);
 
   CachedStatement stmt;
   nsresult rv =
-    mConnection->GetCachedStatement(NS_LITERAL_CSTRING("COMMIT;"), &stmt);
+    mConnection->GetCachedStatement(NS_LITERAL_CSTRING("BEGIN IMMEDIATE;"),
+                                    &stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  for (auto writeInfo : mWriteInfos) {
+    writeInfo->Perform(mConnection);
+  }
+
+  rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING("COMMIT;"), &stmt);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = stmt->Execute();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -3038,99 +3137,16 @@ void
 ConnectionThread::Shutdown()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mThread);
 
   mThread->Shutdown();
 }
 
-nsresult
-SetItemOp::DoDatastoreWork()
-{
-  AssertIsOnConnectionThread();
-  MOZ_ASSERT(mConnection);
-
-  Connection::CachedStatement stmt;
-  nsresult rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING(
-    "INSERT OR REPLACE INTO data (key, value) "
-    "VALUES(:key, :value)"),
-    &stmt);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"), mKey);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = stmt->BindStringByName(NS_LITERAL_CSTRING("value"), mValue);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = stmt->Execute();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
-}
-
-nsresult
-RemoveItemOp::DoDatastoreWork()
-{
-  AssertIsOnConnectionThread();
-  MOZ_ASSERT(mConnection);
-
-  Connection::CachedStatement stmt;
-  nsresult rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING(
-    "DELETE FROM data "
-      "WHERE key = :key;"),
-    &stmt);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"), mKey);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = stmt->Execute();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
-}
-
-nsresult
-ClearOp::DoDatastoreWork()
-{
-  AssertIsOnConnectionThread();
-  MOZ_ASSERT(mConnection);
-
-  Connection::CachedStatement stmt;
-  nsresult rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING(
-    "DELETE FROM data;"),
-    &stmt);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = stmt->Execute();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
-}
-
 /*******************************************************************************
  * Datastore
  ******************************************************************************/
 
 Datastore::Datastore(const nsACString& aOrigin,
                      uint32_t aPrivateBrowsingId,
                      int64_t aUsage,
                      already_AddRefed<DirectoryLock>&& aDirectoryLock,
@@ -3171,25 +3187,16 @@ Datastore::Close()
   MOZ_ASSERT(mDirectoryLock);
 
   mClosed = true;
 
   if (IsPersistent()) {
     MOZ_ASSERT(mConnection);
     MOZ_ASSERT(mQuotaObject);
 
-    if (mConnection->InTransaction()) {
-      MOZ_ASSERT(mAutoCommitTimer);
-      MOZ_ALWAYS_SUCCEEDS(mAutoCommitTimer->Cancel());
-
-      mConnection->Commit();
-
-      mAutoCommitTimer = nullptr;
-    }
-
     // We can't release the directory lock and unregister itself from the
     // hashtable until the connection is fully closed.
     nsCOMPtr<nsIRunnable> callback =
       NewRunnableMethod("dom::Datastore::ConnectionClosedCallback",
                         this,
                         &Datastore::ConnectionClosedCallback);
     mConnection->Close(callback);
   } else {
@@ -3450,20 +3457,17 @@ Datastore::SetItem(Database* aDatabase,
 
     mUpdateBatchUsage += delta;
 
     NotifySnapshots(aDatabase, aKey, oldValue, affectsOrder);
 
     mValues.Put(aKey, aValue);
 
     if (IsPersistent()) {
-      EnsureTransaction();
-
-      RefPtr<SetItemOp> op = new SetItemOp(mConnection, aKey, aValue);
-      mConnection->Dispatch(op);
+      mConnection->SetItem(aKey, aValue);
     }
   }
 
   NotifyObservers(aDatabase, aDocumentURI, aKey, aOldValue, aValue);
 }
 
 void
 Datastore::RemoveItem(Database* aDatabase,
@@ -3492,20 +3496,17 @@ Datastore::RemoveItem(Database* aDatabas
 
     mUpdateBatchUsage += delta;
 
     NotifySnapshots(aDatabase, aKey, oldValue, /* aAffectsOrder */ true);
 
     mValues.Remove(aKey);
 
     if (IsPersistent()) {
-      EnsureTransaction();
-
-      RefPtr<RemoveItemOp> op = new RemoveItemOp(mConnection, aKey);
-      mConnection->Dispatch(op);
+      mConnection->RemoveItem(aKey);
     }
   }
 
   NotifyObservers(aDatabase, aDocumentURI, aKey, aOldValue, VoidString());
 }
 
 void
 Datastore::Clear(Database* aDatabase,
@@ -3531,20 +3532,17 @@ Datastore::Clear(Database* aDatabase,
 
       NotifySnapshots(aDatabase, key, value, /* aAffectsOrder */ true);
     }
 
     mValues.Clear();
     mKeys.Clear();
 
     if (IsPersistent()) {
-      EnsureTransaction();
-
-      RefPtr<ClearOp> op = new ClearOp(mConnection);
-      mConnection->Dispatch(op);
+      mConnection->Clear();
     }
   }
 
   NotifyObservers(aDatabase,
                   aDocumentURI,
                   VoidString(),
                   VoidString(),
                   VoidString());
@@ -3572,16 +3570,20 @@ Datastore::BeginUpdateBatch(int64_t aSna
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aSnapshotInitialUsage >= 0);
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(mUpdateBatchUsage == -1);
   MOZ_ASSERT(!mInUpdateBatch);
 
   mUpdateBatchUsage = aSnapshotInitialUsage;
 
+  if (IsPersistent()) {
+    mConnection->BeginUpdateBatch();
+  }
+
 #ifdef DEBUG
   mInUpdateBatch = true;
 #endif
 }
 
 void
 Datastore::EndUpdateBatch(int64_t aSnapshotPeakUsage)
 {
@@ -3625,16 +3627,20 @@ Datastore::EndUpdateBatch(int64_t aSnaps
     if (delta != 0) {
       DebugOnly<bool> ok = UpdateUsage(delta);
       MOZ_ASSERT(ok);
     }
   }
 
   mUpdateBatchUsage = -1;
 
+  if (IsPersistent()) {
+    mConnection->EndUpdateBatch();
+  }
+
 #ifdef DEBUG
   mInUpdateBatch = false;
 #endif
 }
 
 bool
 Datastore::UpdateUsage(int64_t aDelta)
 {
@@ -3777,55 +3783,16 @@ Datastore::NotifyObservers(Database* aDa
 
   for (Observer* observer : *array) {
     if (observer->Manager() != databaseBackgroundActor) {
       observer->Observe(aDatabase, aDocumentURI, aKey, aOldValue, aNewValue);
     }
   }
 }
 
-void
-Datastore::EnsureTransaction()
-{
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(mConnection);
-
-  if (!mConnection->InTransaction()) {
-    mConnection->Begin(/* aReadonly */ false);
-
-    if (!mAutoCommitTimer) {
-      mAutoCommitTimer = NS_NewTimer();
-      MOZ_ASSERT(mAutoCommitTimer);
-    }
-
-    MOZ_ALWAYS_SUCCEEDS(
-      mAutoCommitTimer->InitWithNamedFuncCallback(
-                                          AutoCommitTimerCallback,
-                                          this,
-                                          kAutoCommitTimeoutMs,
-                                          nsITimer::TYPE_ONE_SHOT,
-                                          "Database::AutoCommitTimerCallback"));
-  }
-}
-
-// static
-void
-Datastore::AutoCommitTimerCallback(nsITimer* aTimer, void* aClosure)
-{
-  MOZ_ASSERT(aClosure);
-
-  auto* self = static_cast<Datastore*>(aClosure);
-  MOZ_ASSERT(self);
-
-  MOZ_ASSERT(self->mConnection);
-  MOZ_ASSERT(self->mConnection->InTransaction());
-
-  self->mConnection->Commit();
-}
-
 /*******************************************************************************
  * PreparedDatastore
  ******************************************************************************/
 
 void
 PreparedDatastore::Destroy()
 {
   AssertIsOnBackgroundThread();
@@ -5317,18 +5284,16 @@ PrepareDatastoreOp::BeginLoadData()
   if (!gConnectionThread) {
     gConnectionThread = new ConnectionThread();
   }
 
   mConnection = gConnectionThread->CreateConnection(mOrigin,
                                                     mDatabaseFilePath);
   MOZ_ASSERT(mConnection);
 
-  MOZ_ASSERT(!mConnection->InTransaction());
-
   // 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.
   RefPtr<LoadDataOp> loadDataOp = new LoadDataOp(this);