Bug 1125102 - Make QuotaManager and FileService to be independent of each other; r=bent
authorJan Varga <jan.varga@gmail.com>
Tue, 14 Apr 2015 10:57:41 +0200
changeset 270349 c2c326d0ba80043d17aa86acd3c6f5d21f5fa79a
parent 270348 d19d1522ce46c7ffaceabe17a047d425554ab324
child 270350 607cfbb06eb83175f7546ff585e191359511b52d
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent
bugs1125102
milestone40.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 1125102 - Make QuotaManager and FileService to be independent of each other; r=bent
dom/asmjscache/AsmJSCache.cpp
dom/cache/QuotaClient.cpp
dom/filehandle/AsyncHelper.cpp
dom/filehandle/FileHandle.cpp
dom/filehandle/FileService.cpp
dom/filehandle/FileService.h
dom/filehandle/moz.build
dom/indexedDB/ActorsParent.cpp
dom/indexedDB/IDBDatabase.cpp
dom/indexedDB/IDBDatabase.h
dom/indexedDB/IDBFileHandle.cpp
dom/indexedDB/PBackgroundIDBDatabase.ipdl
dom/quota/Client.h
dom/quota/QuotaManager.cpp
--- a/dom/asmjscache/AsmJSCache.cpp
+++ b/dom/asmjscache/AsmJSCache.cpp
@@ -1830,37 +1830,25 @@ public:
                          const nsACString& aOrigin)
                          override
   { }
 
   virtual void
   ReleaseIOThreadObjects() override
   { }
 
-  virtual bool
-  IsFileServiceUtilized() override
-  {
-    return false;
-  }
-
-  virtual bool
-  IsTransactionServiceActivated() override
-  {
-    return false;
-  }
-
   virtual void
   WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
                             nsIRunnable* aCallback) override
   {
     MOZ_ASSERT_UNREACHABLE("There are no storages");
   }
 
   virtual void
-  ShutdownTransactionService() override
+  ShutdownWorkThreads() override
   { }
 
 private:
   nsAutoRefCnt mRefCnt;
   NS_DECL_OWNINGTHREAD
 };
 
 NS_IMPL_ADDREF(asmjscache::Client)
--- a/dom/cache/QuotaClient.cpp
+++ b/dom/cache/QuotaClient.cpp
@@ -193,28 +193,16 @@ public:
 
   virtual void
   ReleaseIOThreadObjects() override
   {
     // Nothing to do here as the Context handles cleaning everything up
     // automatically.
   }
 
-  virtual bool
-  IsFileServiceUtilized() override
-  {
-    return false;
-  }
-
-  virtual bool
-  IsTransactionServiceActivated() override
-  {
-    return true;
-  }
-
   virtual void
   WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
                             nsIRunnable* aCallback) override
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(!aStorages.IsEmpty());
 
     nsCOMPtr<nsIRunnable> callback =
@@ -226,17 +214,17 @@ public:
       nsRefPtr<OfflineStorage> storage =
         static_cast<OfflineStorage*>(aStorages[i]);
       storage->AddDestroyCallback(callback);
     }
   }
 
 
   virtual void
-  ShutdownTransactionService() override
+  ShutdownWorkThreads() override
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     // spins the event loop and synchronously shuts down all Managers
     Manager::ShutdownAllOnMainThread();
   }
 
 private:
--- a/dom/filehandle/AsyncHelper.cpp
+++ b/dom/filehandle/AsyncHelper.cpp
@@ -27,17 +27,17 @@ AsyncHelper::AsyncWork(nsIRequestObserve
     // build proxy for observer events
     rv = NS_NewRequestObserverProxy(getter_AddRefs(mObserver), aObserver, aCtxt);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   FileService* service = FileService::GetOrCreate();
   NS_ENSURE_TRUE(service, NS_ERROR_FAILURE);
 
-  nsIEventTarget* target = service->StreamTransportTarget();
+  nsIEventTarget* target = service->ThreadPoolTarget();
 
   rv = target->Dispatch(this, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/dom/filehandle/FileHandle.cpp
+++ b/dom/filehandle/FileHandle.cpp
@@ -587,17 +587,17 @@ FileHandleBase::Finish()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
 
   nsRefPtr<FinishHelper> helper(new FinishHelper(this));
 
   FileService* service = FileService::Get();
   MOZ_ASSERT(service, "This should never be null");
 
-  nsIEventTarget* target = service->StreamTransportTarget();
+  nsIEventTarget* target = service->ThreadPoolTarget();
 
   nsresult rv = target->Dispatch(helper, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 // static
@@ -734,17 +734,17 @@ ReadHelper::DoAsyncRun(nsISupports* aStr
   uint32_t flags = FileStreamWrapper::NOTIFY_PROGRESS;
 
   nsCOMPtr<nsIInputStream> istream =
     new FileInputStreamWrapper(aStream, this, mLocation, mSize, flags);
 
   FileService* service = FileService::Get();
   MOZ_ASSERT(service, "This should never be null");
 
-  nsIEventTarget* target = service->StreamTransportTarget();
+  nsIEventTarget* target = service->ThreadPoolTarget();
 
   nsCOMPtr<nsIAsyncStreamCopier> copier;
   nsresult rv =
     NS_NewAsyncStreamCopier(getter_AddRefs(copier), istream, mStream, target,
                             false, true, STREAM_COPY_BLOCK_SIZE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = copier->AsyncCopy(this, nullptr);
@@ -809,17 +809,17 @@ WriteHelper::DoAsyncRun(nsISupports* aSt
   uint32_t flags = FileStreamWrapper::NOTIFY_PROGRESS;
 
   nsCOMPtr<nsIOutputStream> ostream =
     new FileOutputStreamWrapper(aStream, this, mLocation, mLength, flags);
 
   FileService* service = FileService::Get();
   MOZ_ASSERT(service, "This should never be null");
 
-  nsIEventTarget* target = service->StreamTransportTarget();
+  nsIEventTarget* target = service->ThreadPoolTarget();
 
   nsCOMPtr<nsIAsyncStreamCopier> copier;
   nsresult rv =
     NS_NewAsyncStreamCopier(getter_AddRefs(copier), mStream, ostream, target,
                             true, false, STREAM_COPY_BLOCK_SIZE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = copier->AsyncCopy(this, nullptr);
--- a/dom/filehandle/FileService.cpp
+++ b/dom/filehandle/FileService.cpp
@@ -7,70 +7,90 @@
 #include "FileService.h"
 
 #include "FileHandle.h"
 #include "MainThreadUtils.h"
 #include "mozilla/Assertions.h"
 #include "MutableFile.h"
 #include "nsError.h"
 #include "nsIEventTarget.h"
-#include "nsIObserverService.h"
-#include "nsIOfflineStorage.h"
 #include "nsNetCID.h"
 #include "nsServiceManagerUtils.h"
+#include "nsThreadPool.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
-FileService* gInstance = nullptr;
+const uint32_t kThreadLimit = 5;
+const uint32_t kIdleThreadLimit = 1;
+const uint32_t kIdleThreadTimeoutMs = 30000;
+
+StaticAutoPtr<FileService> gInstance;
 bool gShutdown = false;
 
 } // anonymous namespace
 
 FileService::FileService()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!gInstance, "More than one instance!");
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!gInstance);
 }
 
 FileService::~FileService()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!gInstance, "More than one instance!");
+  MOZ_ASSERT(NS_IsMainThread());
 }
 
 nsresult
 FileService::Init()
 {
-  nsresult rv;
-  mStreamTransportTarget =
-    do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+  mThreadPool = new nsThreadPool();
+
+  nsresult rv = mThreadPool->SetName(NS_LITERAL_CSTRING("FileHandleTrans"));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mThreadPool->SetThreadLimit(kThreadLimit);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
-  return rv;
+  rv = mThreadPool->SetIdleThreadLimit(kIdleThreadLimit);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mThreadPool->SetIdleThreadTimeout(kIdleThreadTimeoutMs);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
 }
 
 nsresult
 FileService::Cleanup()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  MOZ_ASSERT(NS_IsMainThread());
 
   nsIThread* thread = NS_GetCurrentThread();
-  while (mStorageInfos.Count()) {
-    if (!NS_ProcessNextEvent(thread)) {
-      NS_ERROR("Failed to process next event!");
-      break;
-    }
+  MOZ_ASSERT(thread);
+
+  nsresult rv = mThreadPool->Shutdown();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
 
   // Make sure the service is still accessible while any generated callbacks
   // are processed.
-  nsresult rv = NS_ProcessPendingEvents(thread);
+  rv = NS_ProcessPendingEvents(thread);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!mCompleteCallbacks.IsEmpty()) {
     // Run all callbacks manually now.
     for (uint32_t index = 0; index < mCompleteCallbacks.Length(); index++) {
       mCompleteCallbacks[index].mCallback->Run();
     }
     mCompleteCallbacks.Clear();
@@ -90,30 +110,22 @@ FileService::GetOrCreate()
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   if (gShutdown) {
     NS_WARNING("Calling GetOrCreate() after shutdown!");
     return nullptr;
   }
 
   if (!gInstance) {
-    nsRefPtr<FileService> service(new FileService);
+    nsAutoPtr<FileService> service(new FileService());
 
     nsresult rv = service->Init();
     NS_ENSURE_SUCCESS(rv, nullptr);
 
-    nsCOMPtr<nsIObserverService> obs =
-      do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
-    NS_ENSURE_SUCCESS(rv, nullptr);
-
-    rv = obs->AddObserver(service, "profile-before-change", false);
-    NS_ENSURE_SUCCESS(rv, nullptr);
-
-    // The observer service now owns us.
-    gInstance = service;
+    gInstance = service.forget();
   }
 
   return gInstance;
 }
 
 // static
 FileService*
 FileService::Get()
@@ -249,74 +261,48 @@ FileService::NotifyFileHandleCompleted(F
       else {
         index++;
       }
     }
   }
 }
 
 void
-FileService::WaitForStoragesToComplete(
-                                 nsTArray<nsCOMPtr<nsIOfflineStorage> >& aStorages,
-                                 nsIRunnable* aCallback)
+FileService::WaitForStoragesToComplete(nsTArray<nsCString>& aStorageIds,
+                                       nsIRunnable* aCallback)
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!aStorages.IsEmpty(), "No databases to wait on!");
-  NS_ASSERTION(aCallback, "Null pointer!");
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!aStorageIds.IsEmpty());
+  MOZ_ASSERT(aCallback);
 
   StoragesCompleteCallback* callback = mCompleteCallbacks.AppendElement();
   callback->mCallback = aCallback;
-  callback->mStorages.SwapElements(aStorages);
+  callback->mStorageIds.SwapElements(aStorageIds);
 
   if (MaybeFireCallback(*callback)) {
     mCompleteCallbacks.RemoveElementAt(mCompleteCallbacks.Length() - 1);
   }
 }
 
-void
-FileService::AbortFileHandlesForStorage(nsIOfflineStorage* aStorage)
+nsIEventTarget*
+FileService::ThreadPoolTarget() const
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
-  MOZ_ASSERT(aStorage, "Null pointer!");
-
-  StorageInfo* storageInfo;
-  if (!mStorageInfos.Get(aStorage->Id(), &storageInfo)) {
-    return;
-  }
-
-  nsAutoTArray<nsRefPtr<FileHandleBase>, 10> fileHandles;
-  storageInfo->CollectRunningAndDelayedFileHandles(aStorage, fileHandles);
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mThreadPool);
 
-  for (uint32_t index = 0; index < fileHandles.Length(); index++) {
-    ErrorResult ignored;
-    fileHandles[index]->Abort(ignored);
-  }
-}
-
-NS_IMPL_ISUPPORTS(FileService, nsIObserver)
-
-NS_IMETHODIMP
-FileService::Observe(nsISupports* aSubject, const char*  aTopic,
-                     const char16_t* aData)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!strcmp(aTopic, "profile-before-change"), "Wrong topic!");
-
-  Shutdown();
-
-  return NS_OK;
+  return mThreadPool;
 }
 
 bool
 FileService::MaybeFireCallback(StoragesCompleteCallback& aCallback)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  for (uint32_t index = 0; index < aCallback.mStorages.Length(); index++) {
-    if (mStorageInfos.Get(aCallback.mStorages[index]->Id(), nullptr)) {
+  for (uint32_t index = 0; index < aCallback.mStorageIds.Length(); index++) {
+    if (mStorageInfos.Get(aCallback.mStorageIds[index], nullptr)) {
       return false;
     }
   }
 
   aCallback.mCallback->Run();
   return true;
 }
 
@@ -484,30 +470,10 @@ FileService::StorageInfo::CreateDelayedE
                                                    FileHelper* aFileHelper)
 {
   DelayedEnqueueInfo* info = mDelayedEnqueueInfos.AppendElement();
   info->mFileHandle = aFileHandle;
   info->mFileHelper = aFileHelper;
   return info;
 }
 
-void
-FileService::StorageInfo::CollectRunningAndDelayedFileHandles(
-                               nsIOfflineStorage* aStorage,
-                               nsTArray<nsRefPtr<FileHandleBase>>& aFileHandles)
-{
-  for (uint32_t index = 0; index < mFileHandleQueues.Length(); index++) {
-    FileHandleBase* fileHandle = mFileHandleQueues[index]->mFileHandle;
-    if (fileHandle->MutableFile()->Storage() == aStorage) {
-      aFileHandles.AppendElement(fileHandle);
-    }
-  }
-
-  for (uint32_t index = 0; index < mDelayedEnqueueInfos.Length(); index++) {
-    FileHandleBase* fileHandle = mDelayedEnqueueInfos[index].mFileHandle;
-    if (fileHandle->MutableFile()->Storage() == aStorage) {
-      aFileHandles.AppendElement(fileHandle);
-    }
-  }
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/filehandle/FileService.h
+++ b/dom/filehandle/FileService.h
@@ -4,41 +4,41 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_FileService_h
 #define mozilla_dom_FileService_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/FileHelper.h"
+#include "mozilla/StaticPtr.h"
 #include "nsClassHashtable.h"
-#include "nsIObserver.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsDebug.h"
 #include "nsHashKeys.h"
 #include "nsTArray.h"
 #include "nsTHashtable.h"
 
 class nsAString;
 class nsIEventTarget;
-class nsIOfflineStorage;
 class nsIRunnable;
+class nsThreadPool;
 
 namespace mozilla {
 namespace dom {
 
 class FileHandleBase;
 
-class FileService final : public nsIObserver
+class FileService final
 {
+  friend class nsAutoPtr<FileService>;
+  friend class StaticAutoPtr<FileService>;
+
 public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOBSERVER
-
   // Returns a non-owning reference!
   static FileService*
   GetOrCreate();
 
   // Returns a non-owning reference!
   static FileService*
   Get();
 
@@ -51,28 +51,21 @@ public:
 
   nsresult
   Enqueue(FileHandleBase* aFileHandle, FileHelper* aFileHelper);
 
   void
   NotifyFileHandleCompleted(FileHandleBase* aFileHandle);
 
   void
-  WaitForStoragesToComplete(nsTArray<nsCOMPtr<nsIOfflineStorage> >& aStorages,
+  WaitForStoragesToComplete(nsTArray<nsCString>& aStorageIds,
                             nsIRunnable* aCallback);
 
-  void
-  AbortFileHandlesForStorage(nsIOfflineStorage* aStorage);
-
   nsIEventTarget*
-  StreamTransportTarget()
-  {
-    NS_ASSERTION(mStreamTransportTarget, "This should never be null!");
-    return mStreamTransportTarget;
-  }
+  ThreadPoolTarget() const;
 
 private:
   class FileHandleQueue final : public FileHelperListener
   {
     friend class FileService;
 
   public:
     NS_IMETHOD_(MozExternalRefCountType)
@@ -131,21 +124,16 @@ private:
     {
       return !mFileHandleQueues.IsEmpty();
     }
 
     inline DelayedEnqueueInfo*
     CreateDelayedEnqueueInfo(FileHandleBase* aFileHandle,
                              FileHelper* aFileHelper);
 
-    inline void
-    CollectRunningAndDelayedFileHandles(
-                              nsIOfflineStorage* aStorage,
-                              nsTArray<nsRefPtr<FileHandleBase>>& aFileHandles);
-
     void
     LockFileForReading(const nsAString& aFileName)
     {
       mFilesReading.PutEntry(aFileName);
     }
 
     void
     LockFileForWriting(const nsAString& aFileName)
@@ -173,33 +161,33 @@ private:
     nsTArray<nsRefPtr<FileHandleQueue>> mFileHandleQueues;
     nsTArray<DelayedEnqueueInfo> mDelayedEnqueueInfos;
     nsTHashtable<nsStringHashKey> mFilesReading;
     nsTHashtable<nsStringHashKey> mFilesWriting;
   };
 
   struct StoragesCompleteCallback
   {
-    nsTArray<nsCOMPtr<nsIOfflineStorage> > mStorages;
+    nsTArray<nsCString> mStorageIds;
     nsCOMPtr<nsIRunnable> mCallback;
   };
 
   FileService();
   ~FileService();
 
   nsresult
   Init();
 
   nsresult
   Cleanup();
 
   bool
   MaybeFireCallback(StoragesCompleteCallback& aCallback);
 
-  nsCOMPtr<nsIEventTarget> mStreamTransportTarget;
+  nsRefPtr<nsThreadPool> mThreadPool;
   nsClassHashtable<nsCStringHashKey, StorageInfo> mStorageInfos;
   nsTArray<StoragesCompleteCallback> mCompleteCallbacks;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_FileService_h
--- a/dom/filehandle/moz.build
+++ b/dom/filehandle/moz.build
@@ -30,8 +30,12 @@ FAIL_ON_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 LOCAL_INCLUDES += [
     '../base',
 ]
 
 FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+    '/xpcom/threads',
+]
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -24,16 +24,17 @@
 #include "mozilla/Maybe.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/storage.h"
 #include "mozilla/unused.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/File.h"
+#include "mozilla/dom/FileService.h"
 #include "mozilla/dom/StructuredCloneTags.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBCursorParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryRequestParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBRequestParent.h"
@@ -5447,16 +5448,76 @@ private:
                                    PBackgroundIDBFactoryRequestParent* aRequest)
                                    override;
 
   virtual bool
   DeallocPBackgroundIDBDatabaseParent(PBackgroundIDBDatabaseParent* aActor)
                                       override;
 };
 
+class WaitForTransactionsHelper final
+  : public nsRunnable
+{
+  nsCOMPtr<nsIEventTarget> mOwningThread;
+  const nsCString mDatabaseId;
+  nsCOMPtr<nsIRunnable> mCallback;
+
+  enum
+  {
+    State_Initial = 0,
+    State_WaitingForTransactions,
+    State_DispatchToMainThread,
+    State_WaitingForFileHandles,
+    State_DispatchToOwningThread,
+    State_Complete
+  } mState;
+
+public:
+  WaitForTransactionsHelper(const nsCString& aDatabaseId,
+                            nsIRunnable* aCallback)
+    : mOwningThread(NS_GetCurrentThread())
+    , mDatabaseId(aDatabaseId)
+    , mCallback(aCallback)
+    , mState(State_Initial)
+  {
+    AssertIsOnBackgroundThread();
+    MOZ_ASSERT(!aDatabaseId.IsEmpty());
+    MOZ_ASSERT(aCallback);
+  }
+
+  void
+  WaitForTransactions();
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+private:
+  ~WaitForTransactionsHelper()
+  {
+    MOZ_ASSERT(!mCallback);
+    MOZ_ASSERT(mState = State_Complete);
+  }
+
+  void
+  MaybeWaitForTransactions();
+
+  void
+  DispatchToMainThread();
+
+  void
+  MaybeWaitForFileHandles();
+
+  void
+  DispatchToOwningThread();
+
+  void
+  CallCallback();
+
+  NS_DECL_NSIRUNNABLE
+};
+
 class Database final
   : public PBackgroundIDBDatabaseParent
 {
   friend class VersionChangeTransaction;
 
   class StartTransactionOp;
 
 private:
@@ -5466,16 +5527,17 @@ private:
   nsRefPtr<DatabaseOfflineStorage> mOfflineStorage;
   nsTHashtable<nsPtrHashKey<TransactionBase>> mTransactions;
   nsRefPtr<DatabaseConnection> mConnection;
   const PrincipalInfo mPrincipalInfo;
   const nsCString mGroup;
   const nsCString mOrigin;
   const nsCString mId;
   const nsString mFilePath;
+  uint32_t mFileHandleCount;
   const PersistenceType mPersistenceType;
   const bool mChromeWriteAccessAllowed;
   bool mClosed;
   bool mInvalidated;
   bool mActorWasAlive;
   bool mActorDestroyed;
   bool mMetadataCleanedUp;
 
@@ -5646,17 +5708,17 @@ private:
     MOZ_ASSERT(mClosed);
     MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed);
   }
 
   bool
   CloseInternal();
 
   void
-  CloseConnection();
+  MaybeCloseConnection();
 
   void
   ConnectionClosedCallback();
 
   void
   CleanupMetadata();
 
   // IPDL methods are only called by IPDL.
@@ -5706,16 +5768,22 @@ private:
   virtual bool
   RecvDeleteMe() override;
 
   virtual bool
   RecvBlocked() override;
 
   virtual bool
   RecvClose() override;
+
+  virtual bool
+  RecvNewFileHandle() override;
+
+  virtual bool
+  RecvFileHandleFinished() override;
 };
 
 class Database::StartTransactionOp final
   : public TransactionDatabaseOperationBase
 {
   friend class Database;
 
 private:
@@ -6329,21 +6397,21 @@ protected:
 
     // Waiting to do/doing work on the "work thread". This involves waiting for
     // the VersionChangeOp (OpenDatabaseOp and DeleteDatabaseOp each have a
     // different implementation) to do its work. Eventually the state will
     // transition to State_SendingResults.
     State_DatabaseWorkVersionChange,
 
     // Waiting to send/sending results on the PBackground thread. Next step is
-    // UnblockingQuotaManager.
+    // State_UnblockingQuotaManager.
     State_SendingResults,
 
     // Notifying the QuotaManager that it can proceed to the next operation on
-    // the main thread. Next step is Completed.
+    // the main thread. Next step is State_Completed.
     State_UnblockingQuotaManager,
 
     // All done.
     State_Completed
   };
 
   // Must be released on the background thread!
   nsRefPtr<Factory> mFactory;
@@ -7625,26 +7693,26 @@ public:
 private:
   ~NonMainThreadHackBlobImpl()
   { }
 };
 
 class QuotaClient final
   : public mozilla::dom::quota::Client
 {
-  class ShutdownTransactionThreadPoolRunnable;
-  friend class ShutdownTransactionThreadPoolRunnable;
+  class ShutdownWorkThreadsRunnable;
+  friend class ShutdownWorkThreadsRunnable;
 
   class WaitForTransactionsRunnable;
   friend class WaitForTransactionsRunnable;
 
   static QuotaClient* sInstance;
 
   nsCOMPtr<nsIEventTarget> mBackgroundThread;
-  nsRefPtr<ShutdownTransactionThreadPoolRunnable> mShutdownRunnable;
+  nsRefPtr<ShutdownWorkThreadsRunnable> mShutdownRunnable;
 
   bool mShutdownRequested;
 
 public:
   QuotaClient();
 
   static QuotaClient*
   GetInstance()
@@ -7705,65 +7773,59 @@ public:
   virtual void
   OnOriginClearCompleted(PersistenceType aPersistenceType,
                          const nsACString& aOrigin)
                          override;
 
   virtual void
   ReleaseIOThreadObjects() override;
 
-  virtual bool
-  IsFileServiceUtilized() override;
-
-  virtual bool
-  IsTransactionServiceActivated() override;
-
   virtual void
   WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
                             nsIRunnable* aCallback) override;
 
   virtual void
-  ShutdownTransactionService() override;
+  ShutdownWorkThreads() override;
 
 private:
   ~QuotaClient();
 
   nsresult
   GetDirectory(PersistenceType aPersistenceType,
                const nsACString& aOrigin,
                nsIFile** aDirectory);
 
   nsresult
   GetUsageForDirectoryInternal(nsIFile* aDirectory,
                                UsageInfo* aUsageInfo,
                                bool aDatabaseFiles);
 };
 
-class QuotaClient::ShutdownTransactionThreadPoolRunnable final
+class QuotaClient::ShutdownWorkThreadsRunnable final
   : public nsRunnable
 {
   nsRefPtr<QuotaClient> mQuotaClient;
   bool mHasRequestedShutDown;
 
 public:
 
-  explicit ShutdownTransactionThreadPoolRunnable(QuotaClient* aQuotaClient)
+  explicit ShutdownWorkThreadsRunnable(QuotaClient* aQuotaClient)
     : mQuotaClient(aQuotaClient)
     , mHasRequestedShutDown(false)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(aQuotaClient);
     MOZ_ASSERT(QuotaClient::GetInstance() == aQuotaClient);
     MOZ_ASSERT(aQuotaClient->mShutdownRequested);
   }
 
   NS_DECL_ISUPPORTS_INHERITED
 
 private:
-  ~ShutdownTransactionThreadPoolRunnable()
+  ~ShutdownWorkThreadsRunnable()
   {
     MOZ_ASSERT(!mQuotaClient);
   }
 
   NS_DECL_NSIRUNNABLE
 };
 
 class QuotaClient::WaitForTransactionsRunnable final
@@ -7772,17 +7834,18 @@ class QuotaClient::WaitForTransactionsRu
   nsRefPtr<QuotaClient> mQuotaClient;
   nsTArray<nsCString> mDatabaseIds;
   nsCOMPtr<nsIRunnable> mCallback;
 
   enum
   {
     State_Initial = 0,
     State_WaitingForTransactions,
-    State_CallingCallback,
+    State_DispatchToMainThread,
+    State_WaitingForFileHandles,
     State_Complete
   } mState;
 
 public:
   WaitForTransactionsRunnable(QuotaClient* aQuotaClient,
                               nsTArray<nsCString>& aDatabaseIds,
                               nsIRunnable* aCallback)
     : mQuotaClient(aQuotaClient)
@@ -7804,20 +7867,23 @@ private:
   ~WaitForTransactionsRunnable()
   {
     MOZ_ASSERT(!mQuotaClient);
     MOZ_ASSERT(!mCallback);
     MOZ_ASSERT(mState = State_Complete);
   }
 
   void
-  MaybeWait();
-
-  void
-  SendToMainThread();
+  MaybeWaitForTransactions();
+
+  void
+  DispatchToMainThread();
+
+  void
+  MaybeWaitForFileHandles();
 
   void
   CallCallback();
 
   NS_DECL_NSIRUNNABLE
 };
 
 class DatabaseOfflineStorage final
@@ -11234,16 +11300,145 @@ Factory::DeallocPBackgroundIDBDatabasePa
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
 
   nsRefPtr<Database> database = dont_AddRef(static_cast<Database*>(aActor));
   return true;
 }
 
 /*******************************************************************************
+ * WaitForTransactionsHelper
+ ******************************************************************************/
+
+void
+WaitForTransactionsHelper::WaitForTransactions()
+{
+  MOZ_ASSERT(mState == State_Initial);
+
+  unused << this->Run();
+}
+
+void
+WaitForTransactionsHelper::MaybeWaitForTransactions()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mState == State_Initial);
+
+  nsRefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
+  if (connectionPool) {
+    nsTArray<nsCString> ids(1);
+    ids.AppendElement(mDatabaseId);
+
+    mState = State_WaitingForTransactions;
+
+    connectionPool->WaitForDatabasesToComplete(Move(ids), this);
+    return;
+  }
+
+  DispatchToMainThread();
+}
+
+void
+WaitForTransactionsHelper::DispatchToMainThread()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mState == State_Initial || mState == State_WaitingForTransactions);
+
+  mState = State_DispatchToMainThread;
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
+}
+
+void
+WaitForTransactionsHelper::MaybeWaitForFileHandles()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_DispatchToMainThread);
+
+  FileService* service = FileService::Get();
+  if (service) {
+    nsTArray<nsCString> ids(1);
+    ids.AppendElement(mDatabaseId);
+
+    mState = State_WaitingForFileHandles;
+
+    service->WaitForStoragesToComplete(ids, this);
+
+    MOZ_ASSERT(ids.IsEmpty());
+    return;
+  }
+
+  DispatchToOwningThread();
+}
+
+void
+WaitForTransactionsHelper::DispatchToOwningThread()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_DispatchToMainThread ||
+             mState == State_WaitingForFileHandles);
+
+  mState = State_DispatchToOwningThread;
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mOwningThread->Dispatch(this,
+                                                       NS_DISPATCH_NORMAL)));
+}
+
+void
+WaitForTransactionsHelper::CallCallback()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mState == State_DispatchToOwningThread);
+
+  nsCOMPtr<nsIRunnable> callback;
+  mCallback.swap(callback);
+
+  callback->Run();
+
+  mState = State_Complete;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(WaitForTransactionsHelper,
+                             nsRunnable)
+
+NS_IMETHODIMP
+WaitForTransactionsHelper::Run()
+{
+  MOZ_ASSERT(mState != State_Complete);
+  MOZ_ASSERT(mCallback);
+
+  switch (mState) {
+    case State_Initial:
+      MaybeWaitForTransactions();
+      break;
+
+    case State_WaitingForTransactions:
+      DispatchToMainThread();
+      break;
+
+    case State_DispatchToMainThread:
+      MaybeWaitForFileHandles();
+      break;
+
+    case State_WaitingForFileHandles:
+      DispatchToOwningThread();
+      break;
+
+    case State_DispatchToOwningThread:
+      CallCallback();
+      break;
+
+    default:
+      MOZ_CRASH("Should never get here!");
+  }
+
+  return NS_OK;
+}
+
+/*******************************************************************************
  * Database
  ******************************************************************************/
 
 Database::Database(Factory* aFactory,
                    const PrincipalInfo& aPrincipalInfo,
                    const nsACString& aGroup,
                    const nsACString& aOrigin,
                    FullDatabaseMetadata* aMetadata,
@@ -11254,16 +11449,17 @@ Database::Database(Factory* aFactory,
   , mMetadata(aMetadata)
   , mFileManager(aFileManager)
   , mOfflineStorage(Move(aOfflineStorage))
   , mPrincipalInfo(aPrincipalInfo)
   , mGroup(aGroup)
   , mOrigin(aOrigin)
   , mId(aMetadata->mDatabaseId)
   , mFilePath(aMetadata->mFilePath)
+  , mFileHandleCount(0)
   , mPersistenceType(aMetadata->mCommonMetadata.persistenceType())
   , mChromeWriteAccessAllowed(aChromeWriteAccessAllowed)
   , mClosed(false)
   , mInvalidated(false)
   , mActorWasAlive(false)
   , mActorDestroyed(false)
   , mMetadataCleanedUp(false)
 {
@@ -11399,19 +11595,17 @@ void
 Database::UnregisterTransaction(TransactionBase* aTransaction)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aTransaction);
   MOZ_ASSERT(mTransactions.GetEntry(aTransaction));
 
   mTransactions.RemoveEntry(aTransaction);
 
-  if (!mTransactions.Count() && IsClosed() && mOfflineStorage) {
-    CloseConnection();
-  }
+  MaybeCloseConnection();
 }
 
 void
 Database::SetActorAlive()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mActorWasAlive);
   MOZ_ASSERT(!mActorDestroyed);
@@ -11450,50 +11644,48 @@ Database::CloseInternal()
   MOZ_ASSERT(info->mLiveDatabases.Contains(this));
 
   if (info->mWaitingFactoryOp) {
     info->mWaitingFactoryOp->NoteDatabaseClosed(this);
   }
 
   if (mOfflineStorage) {
     mOfflineStorage->CloseOnOwningThread();
-
-    if (!mTransactions.Count()) {
-      CloseConnection();
-    }
-  }
-
-  return true;
-}
-
-void
-Database::CloseConnection()
-{
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(mClosed);
-  MOZ_ASSERT(!mTransactions.Count());
-
-  if (gConnectionPool) {
-    nsTArray<nsCString> ids(1);
-    ids.AppendElement(Id());
-
+  }
+
+  MaybeCloseConnection();
+
+  return true;
+}
+
+void
+Database::MaybeCloseConnection()
+{
+  AssertIsOnBackgroundThread();
+
+  if (!mTransactions.Count() &&
+      !mFileHandleCount &&
+      IsClosed() &&
+      mOfflineStorage) {
     nsCOMPtr<nsIRunnable> callback =
       NS_NewRunnableMethod(this, &Database::ConnectionClosedCallback);
-    gConnectionPool->WaitForDatabasesToComplete(Move(ids), callback);
-  } else {
-    ConnectionClosedCallback();
+
+    nsRefPtr<WaitForTransactionsHelper> helper =
+      new WaitForTransactionsHelper(Id(), callback);
+    helper->WaitForTransactions();
   }
 }
 
 void
 Database::ConnectionClosedCallback()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mClosed);
   MOZ_ASSERT(!mTransactions.Count());
+  MOZ_ASSERT(!mFileHandleCount);
 
   if (mOfflineStorage) {
     DatabaseOfflineStorage::UnregisterOnOwningThread(mOfflineStorage.forget());
   }
 
   CleanupMetadata();
 }
 
@@ -11799,16 +11991,53 @@ Database::RecvClose()
   if (NS_WARN_IF(!CloseInternal())) {
     ASSERT_UNLESS_FUZZING();
     return false;
   }
 
   return true;
 }
 
+bool
+Database::RecvNewFileHandle()
+{
+  AssertIsOnBackgroundThread();
+
+  if (!mOfflineStorage) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  if (mFileHandleCount == UINT32_MAX) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  ++mFileHandleCount;
+
+  return true;
+}
+
+bool
+Database::RecvFileHandleFinished()
+{
+  AssertIsOnBackgroundThread();
+
+  if (mFileHandleCount == 0) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  --mFileHandleCount;
+
+  MaybeCloseConnection();
+
+  return true;
+}
+
 void
 Database::
 StartTransactionOp::RunOnConnectionThread()
 {
   MOZ_ASSERT(!IsOnBackgroundThread());
   MOZ_ASSERT(Transaction());
   MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
 
@@ -14756,32 +14985,16 @@ QuotaClient::ReleaseIOThreadObjects()
 {
   AssertIsOnIOThread();
 
   if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
     mgr->InvalidateAllFileManagers();
   }
 }
 
-bool
-QuotaClient::IsFileServiceUtilized()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  return true;
-}
-
-bool
-QuotaClient::IsTransactionServiceActivated()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  return true;
-}
-
 void
 QuotaClient::WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
                                        nsIRunnable* aCallback)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!aStorages.IsEmpty());
   MOZ_ASSERT(aCallback);
 
@@ -14822,42 +15035,44 @@ QuotaClient::WaitForStoragesToComplete(n
 
   nsCOMPtr<nsIRunnable> runnable =
     new WaitForTransactionsRunnable(this, databaseIds, aCallback);
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
     backgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL)));
 }
 
 void
-QuotaClient::ShutdownTransactionService()
+QuotaClient::ShutdownWorkThreads()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mShutdownRunnable);
   MOZ_ASSERT(!mShutdownRequested);
 
   mShutdownRequested = true;
 
   if (mBackgroundThread) {
-    nsRefPtr<ShutdownTransactionThreadPoolRunnable> runnable =
-      new ShutdownTransactionThreadPoolRunnable(this);
+    nsRefPtr<ShutdownWorkThreadsRunnable> runnable =
+      new ShutdownWorkThreadsRunnable(this);
 
     if (NS_FAILED(mBackgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
       // This can happen if the thread has shut down already.
       return;
     }
 
     nsIThread* currentThread = NS_GetCurrentThread();
     MOZ_ASSERT(currentThread);
 
     mShutdownRunnable.swap(runnable);
 
     while (mShutdownRunnable) {
       MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
     }
   }
+
+  FileService::Shutdown();
 }
 
 nsresult
 QuotaClient::GetDirectory(PersistenceType aPersistenceType,
                           const nsACString& aOrigin, nsIFile** aDirectory)
 {
   QuotaManager* quotaManager = QuotaManager::Get();
   NS_ASSERTION(quotaManager, "This should never fail!");
@@ -14972,53 +15187,78 @@ QuotaClient::GetUsageForDirectoryInterna
   }
 
   return NS_OK;
 }
 
 
 void
 QuotaClient::
-WaitForTransactionsRunnable::MaybeWait()
+WaitForTransactionsRunnable::MaybeWaitForTransactions()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mState == State_Initial);
   MOZ_ASSERT(mQuotaClient);
 
   nsRefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
   if (connectionPool) {
+    // Have to copy here in case the file service needs a list too.
+    nsTArray<nsCString> databaseIds(mDatabaseIds);
+
     mState = State_WaitingForTransactions;
 
-    connectionPool->WaitForDatabasesToComplete(Move(mDatabaseIds), this);
+    connectionPool->WaitForDatabasesToComplete(Move(databaseIds), this);
+    return;
+  }
+
+  DispatchToMainThread();
+}
+
+void
+QuotaClient::
+WaitForTransactionsRunnable::DispatchToMainThread()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mState == State_Initial || mState == State_WaitingForTransactions);
+
+  mState = State_DispatchToMainThread;
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
+}
+
+void
+QuotaClient::
+WaitForTransactionsRunnable::MaybeWaitForFileHandles()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_DispatchToMainThread);
+
+  FileService* service = FileService::Get();
+  if (service) {
+    mState = State_WaitingForFileHandles;
+
+    service->WaitForStoragesToComplete(mDatabaseIds, this);
+
+    MOZ_ASSERT(mDatabaseIds.IsEmpty());
     return;
   }
 
   mDatabaseIds.Clear();
 
-  SendToMainThread();
-}
-
-void
-QuotaClient::
-WaitForTransactionsRunnable::SendToMainThread()
-{
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(mState == State_Initial || mState == State_WaitingForTransactions);
-
-  mState = State_CallingCallback;
-
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
+  mState = State_WaitingForFileHandles;
+
+  CallCallback();
 }
 
 void
 QuotaClient::
 WaitForTransactionsRunnable::CallCallback()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == State_CallingCallback);
+  MOZ_ASSERT(mState == State_WaitingForFileHandles);
 
   nsRefPtr<QuotaClient> quotaClient;
   mQuotaClient.swap(quotaClient);
 
   nsCOMPtr<nsIRunnable> callback;
   mCallback.swap(callback);
 
   callback->Run();
@@ -15033,40 +15273,44 @@ NS_IMETHODIMP
 QuotaClient::
 WaitForTransactionsRunnable::Run()
 {
   MOZ_ASSERT(mState != State_Complete);
   MOZ_ASSERT(mCallback);
 
   switch (mState) {
     case State_Initial:
-      MaybeWait();
+      MaybeWaitForTransactions();
       break;
 
     case State_WaitingForTransactions:
-      SendToMainThread();
-      break;
-
-    case State_CallingCallback:
+      DispatchToMainThread();
+      break;
+
+    case State_DispatchToMainThread:
+      MaybeWaitForFileHandles();
+      break;
+
+    case State_WaitingForFileHandles:
       CallCallback();
       break;
 
     default:
       MOZ_CRASH("Should never get here!");
   }
 
   return NS_OK;
 }
 
-NS_IMPL_ISUPPORTS_INHERITED0(QuotaClient::ShutdownTransactionThreadPoolRunnable,
+NS_IMPL_ISUPPORTS_INHERITED0(QuotaClient::ShutdownWorkThreadsRunnable,
                              nsRunnable)
 
 NS_IMETHODIMP
 QuotaClient::
-ShutdownTransactionThreadPoolRunnable::Run()
+ShutdownWorkThreadsRunnable::Run()
 {
   if (NS_IsMainThread()) {
     MOZ_ASSERT(mHasRequestedShutDown);
     MOZ_ASSERT(QuotaClient::GetInstance() == mQuotaClient);
     MOZ_ASSERT(mQuotaClient->mShutdownRunnable == this);
 
     mQuotaClient->mShutdownRunnable = nullptr;
     mQuotaClient = nullptr;
@@ -16341,26 +16585,21 @@ FactoryOp::WaitForTransactions()
   MOZ_ASSERT(mState == State_BeginVersionChange ||
              mState == State_WaitingForOtherDatabasesToClose);
   MOZ_ASSERT(!mDatabaseId.IsEmpty());
   MOZ_ASSERT(!IsActorDestroyed());
 
   nsTArray<nsCString> databaseIds;
   databaseIds.AppendElement(mDatabaseId);
 
-  nsRefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
-  MOZ_ASSERT(connectionPool);
-
-  // WaitForDatabasesToComplete() will run this op immediately if there are no
-  // transactions blocking it, so be sure to set the next state here before
-  // calling it.
   mState = State_WaitingForTransactionsToComplete;
 
-  connectionPool->WaitForDatabasesToComplete(Move(databaseIds), this);
-  return;
+  nsRefPtr<WaitForTransactionsHelper> helper =
+    new WaitForTransactionsHelper(mDatabaseId, this);
+  helper->WaitForTransactions();
 }
 
 void
 FactoryOp::FinishSendResults()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State_SendingResults);
   MOZ_ASSERT(mFactory);
@@ -17638,27 +17877,22 @@ OpenDatabaseOp::SendResults()
 
     unused <<
       PBackgroundIDBFactoryRequestParent::Send__delete__(this, response);
   }
 
   if (NS_FAILED(mResultCode) && mOfflineStorage) {
     mOfflineStorage->CloseOnOwningThread();
 
-    if (gConnectionPool) {
-      nsTArray<nsCString> ids(1);
-      ids.AppendElement(mDatabaseId);
-
-      nsCOMPtr<nsIRunnable> callback =
-        NS_NewRunnableMethod(this, &OpenDatabaseOp::ConnectionClosedCallback);
-
-      gConnectionPool->WaitForDatabasesToComplete(Move(ids), callback);
-    } else {
-      ConnectionClosedCallback();
-    }
+    nsCOMPtr<nsIRunnable> callback =
+      NS_NewRunnableMethod(this, &OpenDatabaseOp::ConnectionClosedCallback);
+
+    nsRefPtr<WaitForTransactionsHelper> helper =
+      new WaitForTransactionsHelper(mDatabaseId, callback);
+    helper->WaitForTransactions();
   }
 
   // Make sure to release the database on this thread.
   nsRefPtr<Database> database;
   mDatabase.swap(database);
 
   FinishSendResults();
 }
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -1279,16 +1279,36 @@ IDBDatabase::NoteFinishedMutableFile(IDB
 
   // It's ok if this is called more than once, so don't assert that aMutableFile
   // is in the list already.
 
   mLiveMutableFiles.RemoveElement(aMutableFile);
 }
 
 void
+IDBDatabase::OnNewFileHandle()
+{
+  MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mBackgroundActor);
+
+  mBackgroundActor->SendNewFileHandle();
+}
+
+void
+IDBDatabase::OnFileHandleFinished()
+{
+  MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mBackgroundActor);
+
+  mBackgroundActor->SendFileHandleFinished();
+}
+
+void
 IDBDatabase::InvalidateMutableFiles()
 {
   if (!mLiveMutableFiles.IsEmpty()) {
     MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess());
     MOZ_ASSERT(NS_IsMainThread());
 
     for (uint32_t count = mLiveMutableFiles.Length(), index = 0;
          index < count;
--- a/dom/indexedDB/IDBDatabase.h
+++ b/dom/indexedDB/IDBDatabase.h
@@ -195,16 +195,22 @@ public:
   GetQuotaInfo(nsACString& aOrigin, PersistenceType* aPersistenceType);
 
   void
   NoteLiveMutableFile(IDBMutableFile* aMutableFile);
 
   void
   NoteFinishedMutableFile(IDBMutableFile* aMutableFile);
 
+  void
+  OnNewFileHandle();
+
+  void
+  OnFileHandleFinished();
+
   nsPIDOMWindow*
   GetParentObject() const;
 
   already_AddRefed<DOMStringList>
   ObjectStoreNames() const;
 
   already_AddRefed<IDBObjectStore>
   CreateObjectStore(const nsAString& aName,
--- a/dom/indexedDB/IDBFileHandle.cpp
+++ b/dom/indexedDB/IDBFileHandle.cpp
@@ -68,16 +68,18 @@ IDBFileHandle::Create(FileMode aMode,
     return nullptr;
   }
 
   rv = service->Enqueue(fileHandle, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return nullptr;
   }
 
+  aMutableFile->Database()->OnNewFileHandle();
+
   return fileHandle.forget();
 }
 
 mozilla::dom::MutableFileBase*
 IDBFileHandle::MutableFile() const
 {
   return mMutableFile;
 }
@@ -168,16 +170,18 @@ IDBFileHandle::OnCompleteOrAbort(bool aA
     return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
   }
 
   bool dummy;
   if (NS_FAILED(DispatchEvent(event, &dummy))) {
     NS_WARNING("Dispatch failed!");
   }
 
+  mMutableFile->Database()->OnFileHandleFinished();
+
   return NS_OK;
 }
 
 bool
 IDBFileHandle::CheckWindow()
 {
   return GetOwner();
 }
--- a/dom/indexedDB/PBackgroundIDBDatabase.ipdl
+++ b/dom/indexedDB/PBackgroundIDBDatabase.ipdl
@@ -39,16 +39,20 @@ protocol PBackgroundIDBDatabase
 
 parent:
   DeleteMe();
 
   Blocked();
 
   Close();
 
+  NewFileHandle();
+
+  FileHandleFinished();
+
   PBackgroundIDBDatabaseFile(PBlob blob);
 
   PBackgroundIDBTransaction(nsString[] objectStoreNames, Mode mode);
 
 child:
   __delete__();
 
   VersionChange(uint64_t oldVersion, NullableVersion newVersion);
--- a/dom/quota/Client.h
+++ b/dom/quota/Client.h
@@ -107,28 +107,22 @@ public:
   virtual void
   OnOriginClearCompleted(PersistenceType aPersistenceType,
                          const nsACString& aOrigin) = 0;
 
   virtual void
   ReleaseIOThreadObjects() = 0;
 
   // Methods which are called on the main thred.
-  virtual bool
-  IsFileServiceUtilized() = 0;
-
-  virtual bool
-  IsTransactionServiceActivated() = 0;
-
   virtual void
   WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
                             nsIRunnable* aCallback) = 0;
 
   virtual void
-  ShutdownTransactionService() = 0;
+  ShutdownWorkThreads() = 0;
 
 protected:
   virtual ~Client()
   { }
 };
 
 END_QUOTA_NAMESPACE
 
--- a/dom/quota/QuotaManager.cpp
+++ b/dom/quota/QuotaManager.cpp
@@ -24,17 +24,16 @@
 #include "nsIUsageCallback.h"
 #include "nsPIDOMWindow.h"
 
 #include <algorithm>
 #include "GeckoProfiler.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/CondVar.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
-#include "mozilla/dom/FileService.h"
 #include "mozilla/dom/cache/QuotaClient.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/LazyIdleThread.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsComponentManagerUtils.h"
@@ -85,17 +84,16 @@
 
 #define KB * 1024ULL
 #define MB * 1024ULL KB
 #define GB * 1024ULL MB
 
 USING_QUOTA_NAMESPACE
 using namespace mozilla;
 using namespace mozilla::dom;
-using mozilla::dom::FileService;
 
 static_assert(
   static_cast<uint32_t>(StorageType::Persistent) ==
   static_cast<uint32_t>(PERSISTENCE_TYPE_PERSISTENT),
   "Enum values should match.");
 
 static_assert(
   static_cast<uint32_t>(StorageType::Temporary) ==
@@ -541,36 +539,16 @@ public:
   }
 
 private:
   // The QuotaManager holds this alive.
   SynchronizedOp* mOp;
   uint32_t mCountdown;
 };
 
-class WaitForFileHandlesToFinishRunnable final : public nsRunnable
-{
-public:
-  WaitForFileHandlesToFinishRunnable()
-  : mBusy(true)
-  { }
-
-  NS_IMETHOD
-  Run();
-
-  bool
-  IsBusy() const
-  {
-    return mBusy;
-  }
-
-private:
-  bool mBusy;
-};
-
 class SaveOriginAccessTimeRunnable final : public nsRunnable
 {
 public:
   SaveOriginAccessTimeRunnable(PersistenceType aPersistenceType,
                                const nsACString& aOrigin,
                                int64_t aTimestamp)
   : mPersistenceType(aPersistenceType), mOrigin(aOrigin), mTimestamp(aTimestamp)
   { }
@@ -1705,37 +1683,31 @@ QuotaManager::UnregisterStorage(nsIOffli
 }
 
 void
 QuotaManager::AbortCloseStoragesForProcess(ContentParent* aContentParent)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aContentParent);
 
-  FileService* service = FileService::Get();
+  // FileHandle API is not yet supported in child processes, so we don't have
+  // to worry about aborting file handles for given child process.
 
   StorageMatcher<ArrayCluster<nsIOfflineStorage*>> liveStorages;
   liveStorages.Find(mLiveStorages);
 
   for (uint32_t i = 0; i < Client::TYPE_MAX; i++) {
-    nsRefPtr<Client>& client = mClients[i];
-    bool utilized = service && client->IsFileServiceUtilized();
-
     nsTArray<nsIOfflineStorage*>& array = liveStorages[i];
     for (uint32_t j = 0; j < array.Length(); j++) {
       nsCOMPtr<nsIOfflineStorage> storage = array[j];
 
       if (storage->IsOwnedByProcess(aContentParent)) {
         if (NS_FAILED(storage->Close())) {
           NS_WARNING("Failed to close storage for dying process!");
         }
-
-        if (utilized) {
-          service->AbortFileHandlesForStorage(storage);
-        }
       }
     }
   }
 }
 
 nsresult
 QuotaManager::WaitForOpenAllowed(const OriginOrPatternString& aOriginOrPattern,
                                  Nullable<PersistenceType> aPersistenceType,
@@ -2870,61 +2842,26 @@ QuotaManager::Observe(nsISupports* aSubj
   if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_OBSERVER_ID)) {
     // Setting this flag prevents the service from being recreated and prevents
     // further storagess from being created.
     if (gShutdown.exchange(true)) {
       NS_ERROR("Shutdown more than once?!");
     }
 
     if (IsMainProcess()) {
-      FileService* service = FileService::Get();
-      if (service) {
-        // This should only wait for storages registered in this manager
-        // to complete. Other storages may still have running file handles.
-        // If the necko service (thread pool) gets the shutdown notification
-        // first then the sync loop won't be processed at all, otherwise it will
-        // lock the main thread until all storages registered in this manager
-        // are finished.
-
-        nsTArray<uint32_t> indexes;
-        for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
-          if (mClients[index]->IsFileServiceUtilized()) {
-            indexes.AppendElement(index);
-          }
-        }
-
-        StorageMatcher<nsTArray<nsCOMPtr<nsIOfflineStorage>>> liveStorages;
-        liveStorages.Find(mLiveStorages, &indexes);
-
-        if (!liveStorages.IsEmpty()) {
-          nsRefPtr<WaitForFileHandlesToFinishRunnable> runnable =
-            new WaitForFileHandlesToFinishRunnable();
-
-          service->WaitForStoragesToComplete(liveStorages, runnable);
-
-          nsIThread* thread = NS_GetCurrentThread();
-          while (runnable->IsBusy()) {
-            if (!NS_ProcessNextEvent(thread)) {
-              NS_ERROR("Failed to process next event!");
-              break;
-            }
-          }
-        }
-      }
-
       // Kick off the shutdown timer.
       if (NS_FAILED(mShutdownTimer->Init(this, DEFAULT_SHUTDOWN_TIMER_MS,
                                          nsITimer::TYPE_ONE_SHOT))) {
         NS_WARNING("Failed to initialize shutdown timer!");
       }
 
       // Each client will spin the event loop while we wait on all the threads
       // to close. Our timer may fire during that loop.
       for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
-        mClients[index]->ShutdownTransactionService();
+        mClients[index]->ShutdownWorkThreads();
       }
 
       // Cancel the timer regardless of whether it actually fired.
       if (NS_FAILED(mShutdownTimer->Cancel())) {
         NS_WARNING("Failed to cancel shutdown timer!");
       }
 
       // Give clients a chance to cleanup IO thread only objects.
@@ -3087,43 +3024,21 @@ QuotaManager::AcquireExclusiveAccess(con
   }
 
   op->mRunnable = aRunnable;
 
   nsRefPtr<WaitForTransactionsToFinishRunnable> runnable =
     new WaitForTransactionsToFinishRunnable(op);
 
   if (!liveStorages.IsEmpty()) {
-    // Ask the file service to call us back when it's done with this storage.
-    FileService* service = FileService::Get();
-
-    if (service) {
-      // Have to copy here in case a transaction service needs a list too.
-      nsTArray<nsCOMPtr<nsIOfflineStorage>> array;
-
-      for (uint32_t index = 0; index < Client::TYPE_MAX; index++)  {
-        if (!liveStorages[index].IsEmpty() &&
-            mClients[index]->IsFileServiceUtilized()) {
-          array.AppendElements(liveStorages[index]);
-        }
-      }
-
-      if (!array.IsEmpty()) {
-        runnable->AddRun();
-
-        service->WaitForStoragesToComplete(array, runnable);
-      }
-    }
-
     // Ask each transaction service to call us back when they're done with this
     // storage.
     for (uint32_t index = 0; index < Client::TYPE_MAX; index++)  {
       nsRefPtr<Client>& client = mClients[index];
-      if (!liveStorages[index].IsEmpty() &&
-          client->IsTransactionServiceActivated()) {
+      if (!liveStorages[index].IsEmpty()) {
         runnable->AddRun();
 
         client->WaitForStoragesToComplete(liveStorages[index], runnable);
       }
     }
   }
 
   nsresult rv = runnable->Run();
@@ -4421,26 +4336,16 @@ WaitForTransactionsToFinishRunnable::Run
   NS_ENSURE_SUCCESS(rv, rv);
 
   // The listener is responsible for calling
   // QuotaManager::AllowNextSynchronizedOp.
   return NS_OK;
 }
 
 NS_IMETHODIMP
-WaitForFileHandlesToFinishRunnable::Run()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  mBusy = false;
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 SaveOriginAccessTimeRunnable::Run()
 {
   AssertIsOnIOThread();
 
   QuotaManager* quotaManager = QuotaManager::Get();
   NS_ASSERTION(quotaManager, "This should never fail!");
 
   nsCOMPtr<nsIFile> directory;