Bug 1248550 - Part 3: Introduce a new object/runnable for database maintenance; r=khuey
authorJan Varga <jan.varga@gmail.com>
Tue, 01 Mar 2016 10:44:53 +0100
changeset 324495 3022115e2a1ace812133cd355ae6eaa60f5bf5fc
parent 324494 62808b96eb74d341f319a9824e79d562ba659362
child 324496 4814724f97989bc89b11a869143b2b803235612a
push id1128
push userjlund@mozilla.com
push dateWed, 01 Jun 2016 01:31:59 +0000
treeherdermozilla-release@fe0d30de989d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs1248550
milestone47.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 1248550 - Part 3: Introduce a new object/runnable for database maintenance; r=khuey
dom/indexedDB/ActorsParent.cpp
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -126,16 +126,17 @@ using namespace mozilla::ipc;
 namespace {
 
 class ConnectionPool;
 class Cursor;
 class Database;
 struct DatabaseActorInfo;
 class DatabaseFile;
 class DatabaseLoggingInfo;
+class DatabaseMaintenance;
 class Factory;
 class Maintenance;
 class MutableFile;
 class OpenDatabaseOp;
 class TransactionBase;
 class TransactionDatabaseOperationBase;
 class VersionChangeTransaction;
 
@@ -8775,24 +8776,26 @@ class QuotaClient final
   // The number of freelist pages beyond which we will favor an incremental
   // vacuum over a full vacuum.
   static const int32_t kMaxFreelistThreshold = 5;
 
   // If the percent of unused file bytes in the database exceeds this percentage
   // then we will attempt a full vacuum.
   static const int32_t kPercentUnusedThreshold = 20;
 
+public:
   class AutoProgressHandler;
 
   enum class MaintenanceAction
   {
     Nothing = 0,
     IncrementalVacuum,
     FullVacuum
   };
+private:
 
   static QuotaClient* sInstance;
 
   nsTArray<RefPtr<Maintenance>> mMaintenanceQueue;
   RefPtr<Maintenance> mCurrentMaintenance;
   RefPtr<nsThreadPool> mMaintenanceThreadPool;
   bool mShutdownRequested;
 
@@ -8897,45 +8900,38 @@ private:
                const nsACString& aOrigin,
                nsIFile** aDirectory);
 
   nsresult
   GetUsageForDirectoryInternal(nsIFile* aDirectory,
                                UsageInfo* aUsageInfo,
                                bool aDatabaseFiles);
 
-
-  // Runs on mMaintenanceThreadPool. Does maintenance on one database.
-  void
-  PerformIdleMaintenanceOnDatabase(Maintenance* aMaintenance,
-                                   PersistenceType aPersistenceType,
-                                   const nsACString& aGroup,
-                                   const nsACString& aOrigin,
-                                   const nsAString& aDatabasePath);
-
+public:
   // Runs on mMaintenanceThreadPool as part of PerformIdleMaintenanceOnDatabase.
-  nsresult
+  static nsresult
   CheckIntegrity(mozIStorageConnection* aConnection, bool* aOk);
 
   // Runs on mMaintenanceThreadPool as part of PerformIdleMaintenanceOnDatabase.
-  nsresult
+  static nsresult
   DetermineMaintenanceAction(mozIStorageConnection* aConnection,
                              nsIFile* aDatabaseFile,
                              PRTime aStartTime,
                              MaintenanceAction* aMaintenanceAction);
 
   // Runs on mMaintenanceThreadPool as part of PerformIdleMaintenanceOnDatabase.
-  void
+  static void
   IncrementalVacuum(mozIStorageConnection* aConnection);
 
   // Runs on mMaintenanceThreadPool as part of PerformIdleMaintenanceOnDatabase.
-  void
+  static void
   FullVacuum(mozIStorageConnection* aConnection,
              nsIFile* aDatabaseFile);
 
+private:
   // Runs on the PBackground thread. Checks to see if there's a queued
   // maintanance to run.
   void
   ProcessMaintenanceQueue();
 };
 
 class MOZ_STACK_CLASS QuotaClient::AutoProgressHandler final
   : public mozIStorageProgressHandler
@@ -9021,32 +9017,42 @@ class Maintenance final
     IndexedDatabaseManagerOpen,
 
     // Waiting for directory open allowed on the PBackground thread. The next
     // step is either Finishing if directory lock failed to acquire, or
     // DirectoryWorkOpen if directory lock is acquired.
     DirectoryOpenPending,
 
     // Waiting to do/doing work on the QuotaManager IO thread. Its next step is
-    // Finishing.
+    // BeginDatabaseMaintenance.
     DirectoryWorkOpen,
 
+    // Dispatching a runnable for each database on the PBackground thread. The
+    // next state is either WaitingForDatabaseMaintenancesToComplete if at least
+    // one runnable has been dispatched, or Finishing otherwise.
+    BeginDatabaseMaintenance,
+
+    // Waiting for database maintenances to finish on maintenance thread pool.
+    // The next state is Finishing if the last runnable has finished.
+    WaitingForDatabaseMaintenancesToComplete,
+
     // Waiting to finish/finishing on the PBackground thread. The next step is
     // Completed.
     Finishing,
 
     // All done.
     Complete
   };
 
   nsCOMPtr<nsIEventTarget> mOwningThread;
   RefPtr<QuotaClient> mQuotaClient;
   PRTime mStartTime;
   RefPtr<DirectoryLock> mDirectoryLock;
   nsTArray<DirectoryInfo> mDirectoryInfos;
+  nsDataHashtable<nsStringHashKey, DatabaseMaintenance*> mDatabaseMaintenances;
   Atomic<bool> mAbortedOnAnyThread;
   State mState;
   bool mAborted;
 
 public:
   explicit Maintenance(QuotaClient* aQuotaClient)
     : mOwningThread(NS_GetCurrentThread())
     , mQuotaClient(aQuotaClient)
@@ -9113,20 +9119,27 @@ public:
     MOZ_ASSERT(mAborted == mAbortedOnAnyThread);
 
     if (!mAborted) {
       mAborted = true;
       mAbortedOnAnyThread = true;
     }
   }
 
+  void
+  RegisterDatabaseMaintenance(DatabaseMaintenance* aDatabaseMaintenance);
+
+  void
+  UnregisterDatabaseMaintenance(DatabaseMaintenance* aDatabaseMaintenance);
+
 private:
   ~Maintenance()
   {
     MOZ_ASSERT(mState == State::Complete);
+    MOZ_ASSERT(!mDatabaseMaintenances.Count());
   }
 
   // Runs on the PBackground thread. Checks if IndexedDatabaseManager is
   // running. Calls OpenDirectory() or dispatches to the main thread on which
   // CreateIndexedDatabaseManager() is called.
   nsresult
   Start();
 
@@ -9140,20 +9153,25 @@ private:
   nsresult
   OpenDirectory();
 
   // Runs on the PBackground thread. Dispatches to the QuotaManager I/O thread.
   nsresult
   DirectoryOpen();
 
   // Runs on the QuotaManager I/O thread. Once it finds databases it will
-  // dispatch to the PBackground thread on which Finish() is called.
+  // dispatch to the PBackground thread on which BeginDatabaseMaintenance()
+  // is called.
   nsresult
   DirectoryWork();
 
+  // Runs on the PBackground thread. It dispatches a runnable for each database.
+  nsresult
+  BeginDatabaseMaintenance();
+
   // Runs on the PBackground thread. Called when the maintenance is finished or
   // if any of above methods fails.
   void
   Finish();
 
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_DECL_NSIRUNNABLE
@@ -9214,16 +9232,84 @@ struct Maintenance::DirectoryInfo final
   ~DirectoryInfo()
   {
     MOZ_COUNT_DTOR(Maintenance::DirectoryInfo);
   }
 
   DirectoryInfo(const DirectoryInfo& aOther) = delete;
 };
 
+class DatabaseMaintenance final
+  : public nsRunnable
+{
+  nsCOMPtr<nsIEventTarget> mOwningThread;
+  RefPtr<Maintenance> mMaintenance;
+  const nsCString mGroup;
+  const nsCString mOrigin;
+  const nsString mDatabasePath;
+  const PersistenceType mPersistenceType;
+
+public:
+  DatabaseMaintenance(Maintenance* aMaintenance,
+                      PersistenceType aPersistenceType,
+                      const nsCString& aGroup,
+                      const nsCString& aOrigin,
+                      const nsString& aDatabasePath)
+    : mOwningThread(NS_GetCurrentThread())
+    , mMaintenance(aMaintenance)
+    , mGroup(aGroup)
+    , mOrigin(aOrigin)
+    , mDatabasePath(aDatabasePath)
+    , mPersistenceType(aPersistenceType)
+  { }
+
+  bool
+  IsOnOwningThread() const
+  {
+    MOZ_ASSERT(mOwningThread);
+
+    bool current;
+    return NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)) && current;
+  }
+
+  void
+  AssertIsOnOwningThread() const
+  {
+    MOZ_ASSERT(IsOnBackgroundThread());
+    MOZ_ASSERT(IsOnOwningThread());
+  }
+
+  const nsString&
+  DatabasePath() const
+  {
+    return mDatabasePath;
+  }
+
+private:
+  ~DatabaseMaintenance()
+  { }
+
+  // Runs on maintenance thread pool. Does maintenance on the database.
+  void
+  PerformMaintenanceOnDatabase();
+
+  // Runs on the PBackground thread. It dispatches a complete callback and
+  // unregisters from Maintenance.
+  void
+  RunOnOwningThread();
+
+  // Runs on maintenance thread pool. Once it performs database maintenance
+  // it will dispatch to the PBackground thread on which RunOnOwningThread()
+  // is called.
+  void
+  RunOnConnectionThread();
+
+  NS_DECL_NSIRUNNABLE
+};
+
 class IntString : public nsAutoString
 {
 public:
   explicit
   IntString(int64_t aInteger)
   {
     AppendInt(aInteger);
   }
@@ -17207,39 +17293,34 @@ Maintenance::DirectoryWork()
         mDirectoryInfos.AppendElement(DirectoryInfo(persistenceType,
                                                     group,
                                                     origin,
                                                     Move(databasePaths)));
       }
     }
   }
 
-  mState = State::Finishing;
+  mState = State::BeginDatabaseMaintenance;
 
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mOwningThread->Dispatch(this,
                                                        NS_DISPATCH_NORMAL)));
 
   return NS_OK;
 }
 
 void
-QuotaClient::PerformIdleMaintenanceOnDatabase(Maintenance* aMaintenance,
-                                              PersistenceType aPersistenceType,
-                                              const nsACString& aGroup,
-                                              const nsACString& aOrigin,
-                                              const nsAString& aDatabasePath)
+DatabaseMaintenance::PerformMaintenanceOnDatabase()
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(!IsOnBackgroundThread());
-  MOZ_ASSERT(mMaintenanceThreadPool);
-  MOZ_ASSERT(aMaintenance);
-  MOZ_ASSERT(aMaintenance->StartTime());
-  MOZ_ASSERT(!aDatabasePath.IsEmpty());
-  MOZ_ASSERT(!aGroup.IsEmpty());
-  MOZ_ASSERT(!aOrigin.IsEmpty());
+  MOZ_ASSERT(mMaintenance);
+  MOZ_ASSERT(mMaintenance->StartTime());
+  MOZ_ASSERT(!mDatabasePath.IsEmpty());
+  MOZ_ASSERT(!mGroup.IsEmpty());
+  MOZ_ASSERT(!mOrigin.IsEmpty());
 
   class MOZ_STACK_CLASS AutoClose final
   {
     nsCOMPtr<mozIStorageConnection> mConnection;
 
   public:
     explicit AutoClose(mozIStorageConnection* aConnection)
       : mConnection(aConnection)
@@ -17250,81 +17331,81 @@ QuotaClient::PerformIdleMaintenanceOnDat
     ~AutoClose()
     {
       MOZ_ASSERT(mConnection);
 
       MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mConnection->Close()));
     }
   };
 
-  nsCOMPtr<nsIFile> databaseFile = GetFileForPath(aDatabasePath);
+  nsCOMPtr<nsIFile> databaseFile = GetFileForPath(mDatabasePath);
   MOZ_ASSERT(databaseFile);
 
   nsCOMPtr<mozIStorageConnection> connection;
   nsresult rv = GetStorageConnection(databaseFile,
-                                     aPersistenceType,
-                                     aGroup,
-                                     aOrigin,
+                                     mPersistenceType,
+                                     mGroup,
+                                     mOrigin,
                                      TelemetryIdForFile(databaseFile),
                                      getter_AddRefs(connection));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
   AutoClose autoClose(connection);
 
-  if (aMaintenance->IsAbortedOnAnyThread()) {
-    return;
-  }
-
-  AutoProgressHandler progressHandler(aMaintenance);
+  if (mMaintenance->IsAbortedOnAnyThread()) {
+    return;
+  }
+
+  QuotaClient::AutoProgressHandler progressHandler(mMaintenance);
   if (NS_WARN_IF(NS_FAILED(progressHandler.Register(connection)))) {
     return;
   }
 
   bool databaseIsOk;
-  rv = CheckIntegrity(connection, &databaseIsOk);
+  rv = QuotaClient::CheckIntegrity(connection, &databaseIsOk);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
   if (NS_WARN_IF(!databaseIsOk)) {
     // XXX Handle this somehow! Probably need to clear all storage for the
     //     origin. Needs followup.
     MOZ_ASSERT(false, "Database corruption detected!");
     return;
   }
 
-  if (aMaintenance->IsAbortedOnAnyThread()) {
-    return;
-  }
-
-  MaintenanceAction maintenanceAction;
-  rv = DetermineMaintenanceAction(connection,
-                                  databaseFile,
-                                  aMaintenance->StartTime(),
-                                  &maintenanceAction);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return;
-  }
-
-  if (aMaintenance->IsAbortedOnAnyThread()) {
+  if (mMaintenance->IsAbortedOnAnyThread()) {
+    return;
+  }
+
+  QuotaClient::MaintenanceAction maintenanceAction;
+  rv = QuotaClient::DetermineMaintenanceAction(connection,
+                                               databaseFile,
+                                               mMaintenance->StartTime(),
+                                               &maintenanceAction);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  if (mMaintenance->IsAbortedOnAnyThread()) {
     return;
   }
 
   switch (maintenanceAction) {
-    case MaintenanceAction::Nothing:
-      break;
-
-    case MaintenanceAction::IncrementalVacuum:
-      IncrementalVacuum(connection);
-      break;
-
-    case MaintenanceAction::FullVacuum:
-      FullVacuum(connection, databaseFile);
+    case QuotaClient::MaintenanceAction::Nothing:
+      break;
+
+    case QuotaClient::MaintenanceAction::IncrementalVacuum:
+      QuotaClient::IncrementalVacuum(connection);
+      break;
+
+    case QuotaClient::MaintenanceAction::FullVacuum:
+      QuotaClient::FullVacuum(connection, databaseFile);
       break;
 
     default:
       MOZ_CRASH("Unknown MaintenanceAction!");
   }
 }
 
 nsresult
@@ -17819,16 +17900,48 @@ AutoProgressHandler::OnProgress(mozIStor
   MOZ_ASSERT(mConnection == aConnection);
   MOZ_ASSERT(_retval);
 
   *_retval = mMaintenance->IsAbortedOnAnyThread();
 
   return NS_OK;
 }
 
+void
+Maintenance::RegisterDatabaseMaintenance(
+                                      DatabaseMaintenance* aDatabaseMaintenance)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aDatabaseMaintenance);
+  MOZ_ASSERT(mState == State::BeginDatabaseMaintenance);
+  MOZ_ASSERT(!mDatabaseMaintenances.Get(aDatabaseMaintenance->DatabasePath()));
+
+  mDatabaseMaintenances.Put(aDatabaseMaintenance->DatabasePath(),
+                            aDatabaseMaintenance);
+}
+
+void
+Maintenance::UnregisterDatabaseMaintenance(
+                                      DatabaseMaintenance* aDatabaseMaintenance)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aDatabaseMaintenance);
+  MOZ_ASSERT(mState == State::WaitingForDatabaseMaintenancesToComplete);
+  MOZ_ASSERT(mDatabaseMaintenances.Get(aDatabaseMaintenance->DatabasePath()));
+
+  mDatabaseMaintenances.Remove(aDatabaseMaintenance->DatabasePath());
+
+  if (mDatabaseMaintenances.Count()) {
+    return;
+  }
+
+  mState = State::Finishing;
+  Finish();
+}
+
 nsresult
 Maintenance::Start()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::Initial);
 
   if (IsAborted()) {
     return NS_ERROR_ABORT;
@@ -17914,16 +18027,80 @@ Maintenance::DirectoryOpen()
   nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
+nsresult
+Maintenance::BeginDatabaseMaintenance()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mState == State::BeginDatabaseMaintenance);
+
+  class MOZ_STACK_CLASS Helper final
+  {
+  public:
+    static bool
+    IsSafeToRunMaintenance(const nsAString& aDatabasePath)
+    {
+      if (gLiveDatabaseHashtable) {
+        for (auto iter = gLiveDatabaseHashtable->ConstIter();
+             !iter.Done(); iter.Next()) {
+          for (Database* database : iter.Data()->mLiveDatabases) {
+            if (database->FilePath() == aDatabasePath) {
+              return false;
+            }
+          }
+        }
+      }
+
+      return true;
+    }
+  };
+
+  RefPtr<nsThreadPool> threadPool;
+
+  for (DirectoryInfo& directoryInfo : mDirectoryInfos) {
+    for (const nsString& databasePath : directoryInfo.mDatabasePaths) {
+      if (Helper::IsSafeToRunMaintenance(databasePath)) {
+        RefPtr<DatabaseMaintenance> databaseMaintenance =
+          new DatabaseMaintenance(this,
+                                  directoryInfo.mPersistenceType,
+                                  directoryInfo.mGroup,
+                                  directoryInfo.mOrigin,
+                                  databasePath);
+
+        if (!threadPool) {
+          threadPool = mQuotaClient->GetOrCreateThreadPool();
+          MOZ_ASSERT(threadPool);
+        }
+
+        MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+          threadPool->Dispatch(databaseMaintenance, NS_DISPATCH_NORMAL)));
+
+        RegisterDatabaseMaintenance(databaseMaintenance);
+      }
+    }
+  }
+
+  mDirectoryInfos.Clear();
+
+  if (mDatabaseMaintenances.Count()) {
+    mState = State::WaitingForDatabaseMaintenancesToComplete;
+  } else {
+    mState = State::Finishing;
+    Finish();
+  }
+
+  return NS_OK;
+}
+
 void
 Maintenance::Finish()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::Finishing);
 
   mDirectoryLock = nullptr;
 
@@ -17953,16 +18130,20 @@ Maintenance::Run()
     case State::IndexedDatabaseManagerOpen:
       rv = OpenDirectory();
       break;
 
     case State::DirectoryWorkOpen:
       rv = DirectoryWork();
       break;
 
+    case State::BeginDatabaseMaintenance:
+      rv = BeginDatabaseMaintenance();
+      break;
+
     case State::Finishing:
       Finish();
       return NS_OK;
 
     default:
       MOZ_CRASH("Bad state!");
   }
 
@@ -18006,16 +18187,48 @@ Maintenance::DirectoryLockFailed()
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::DirectoryOpenPending);
   MOZ_ASSERT(!mDirectoryLock);
 
   mState = State::Finishing;
   Finish();
 }
 
+void
+DatabaseMaintenance::RunOnOwningThread()
+{
+  AssertIsOnOwningThread();
+
+  mMaintenance->UnregisterDatabaseMaintenance(this);
+}
+
+void
+DatabaseMaintenance::RunOnConnectionThread()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(!IsOnBackgroundThread());
+
+  PerformMaintenanceOnDatabase();
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mOwningThread->Dispatch(this,
+                                                       NS_DISPATCH_NORMAL)));
+}
+
+NS_IMETHODIMP
+DatabaseMaintenance::Run()
+{
+  if (IsOnOwningThread()) {
+    RunOnOwningThread();
+  } else {
+    RunOnConnectionThread();
+  }
+
+  return NS_OK;
+}
+
 /*******************************************************************************
  * Local class implementations
  ******************************************************************************/
 
 NS_IMPL_ISUPPORTS(CompressDataBlobsFunction, mozIStorageFunction)
 NS_IMPL_ISUPPORTS(EncodeKeysFunction, mozIStorageFunction)
 
 #if !defined(MOZ_B2G)