Bug 789945: Part 1. Extract preference write functionality into a separate method/class and add interrupt callback functionality. Add methods to explicitly ask for main or off main thread save (inactive.) r?bsmedberg draft
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Fri, 01 Jan 2016 12:39:02 +0000
changeset 586059 13c122cc554354bd7e7f185048ae0dfd5b7a3905
parent 585921 cce4d83d2b99ffedbd67a2f40ce26e53e9ae27ab
child 586060 4a267f3429896af560cef413191c52a1577c821d
push id61275
push userbmo:milan@mozilla.com
push dateMon, 29 May 2017 16:09:16 +0000
reviewersbsmedberg
bugs789945
milestone55.0a1
Bug 789945: Part 1. Extract preference write functionality into a separate method/class and add interrupt callback functionality. Add methods to explicitly ask for main or off main thread save (inactive.) r?bsmedberg MozReview-Commit-ID: Llon6B2LjIW
modules/libpref/Preferences.cpp
modules/libpref/Preferences.h
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -51,17 +51,17 @@
 #include "nsTArray.h"
 #include "nsRefPtrHashtable.h"
 #include "nsIMemoryReporter.h"
 #include "nsThreadUtils.h"
 #include "GeckoProfiler.h"
 
 #ifdef DEBUG
 #define ENSURE_MAIN_PROCESS(message, pref) do {                                \
-  if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {        \
+  if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {                                  \
     nsPrintfCString msg("ENSURE_MAIN_PROCESS failed. %s %s", message, pref);   \
     NS_WARNING(msg.get());                                                     \
     return NS_ERROR_NOT_AVAILABLE;                                             \
   }                                                                            \
 } while (0);
 class WatchinPrefRAII {
 public:
   WatchinPrefRAII() {
@@ -69,17 +69,17 @@ public:
   }
   ~WatchinPrefRAII() {
     pref_SetWatchingPref(false);
   }
 };
 #define WATCHING_PREF_RAII() WatchinPrefRAII watchingPrefRAII
 #else
 #define ENSURE_MAIN_PROCESS(message, pref)                                     \
-  if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {        \
+  if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {                                  \
     return NS_ERROR_NOT_AVAILABLE;                                             \
   }
 #define WATCHING_PREF_RAII()
 #endif
 
 class PrefCallback;
 
 namespace mozilla {
@@ -220,16 +220,130 @@ ValueObserver::Observe(nsISupports     *
   }
   for (uint32_t i = 0; i < mClosures.Length(); i++) {
     mCallback(data.get(), mClosures.ElementAt(i));
   }
 
   return NS_OK;
 }
 
+// Write the preference data to a file.  The data is extracted
+// during the construction and owned by an instance of this class, and
+// we assume that the file remains valid throughout its lifetime.
+//
+class PreferencesWriter
+{
+public:
+  PreferencesWriter(nsIFile* aFile, PLDHashTable* aTable, int32_t aId)
+    : mFile(aFile)
+    , mPrefs(pref_savePrefs(aTable, &mPrefCount))
+    , mId(aId)
+  {
+  }
+
+  // If the interrupt function in the Write call is not null, we will
+  // periodically check and stop the work if it returns true.  The file will
+  // either be written completely, or not at all.
+  typedef bool (*InterruptRequestedFunc)(int32_t);
+  NS_IMETHOD Write(InterruptRequestedFunc aInterrupt = nullptr)
+  {
+    nsCOMPtr<nsIOutputStream> outStreamSink;
+    nsCOMPtr<nsIOutputStream> outStream;
+    uint32_t                  writeAmount;
+    nsresult                  rv;
+
+    if (aInterrupt && aInterrupt(mId)) {
+      NS_WARNING("OMT pref save interrupted 1");
+      return NS_OK;
+    }
+
+    // execute a "safe" save by saving through a tempfile
+    rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStreamSink),
+                                         mFile,
+                                         -1,
+                                         0600);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+    if (aInterrupt && aInterrupt(mId)) {
+      NS_WARNING("OMT pref save interrupted 2");
+      return NS_OK;
+    }
+
+    rv = NS_NewBufferedOutputStream(getter_AddRefs(outStream), outStreamSink, 4096);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    if (aInterrupt && aInterrupt(mId)) {
+      NS_WARNING("OMT pref save interrupted 3");
+      return NS_OK;
+    }
+
+    /* Sort the preferences to make a readable file on disk */
+    NS_QuickSort(mPrefs.get(), mPrefCount, sizeof(char *),
+                 pref_CompareStrings, nullptr);
+
+    if (aInterrupt && aInterrupt(mId)) {
+      NS_WARNING("OMT pref save interrupted 4");
+      return NS_OK;
+    }
+
+    // write out the file header
+    outStream->Write(kPrefFileHeader, sizeof(kPrefFileHeader) - 1, &writeAmount);
+
+    if (aInterrupt && aInterrupt(mId)) {
+      NS_WARNING("OMT pref save interrupted 5");
+      return NS_OK;
+    }
+
+    for (uint32_t valueIdx = 0; valueIdx < mPrefCount; valueIdx++) {
+      // This should not have a measurable performance impact
+      if (aInterrupt && aInterrupt(mId)) {
+        NS_WARNING("OMT pref save interrupted 6");
+        return NS_OK;
+      }
+
+      char*& pref = mPrefs[valueIdx];
+      MOZ_ASSERT(pref);
+      outStream->Write(pref, strlen(pref), &writeAmount);
+      outStream->Write(NS_LINEBREAK, NS_LINEBREAK_LEN, &writeAmount);
+      free(pref);
+      pref = nullptr;
+    }
+
+    // Last chance to abort...
+    if (aInterrupt && aInterrupt(mId)) {
+      NS_WARNING("OMT pref save interrupted 7");
+      return NS_OK;
+    }
+
+    // tell the safe output stream to overwrite the real prefs file
+    // (it'll abort if there were any errors during writing)
+    nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outStream);
+    NS_ASSERTION(safeStream, "expected a safe output stream!");
+    if (safeStream) {
+      rv = safeStream->Finish();
+    }
+
+#ifdef DEBUG
+    if (NS_FAILED(rv)) {
+      NS_WARNING("failed to save prefs file! possible data loss");
+    }
+#endif
+    return rv;
+  }
+
+protected:
+  nsCOMPtr<nsIFile> mFile;
+  UniquePtr<char*[]> mPrefs;
+  uint32_t mPrefCount;
+  int32_t mId;
+};
+
 struct CacheData {
   void* cacheLocation;
   union {
     bool defaultValueBool;
     int32_t defaultValueInt;
     uint32_t defaultValueUint;
     float defaultValueFloat;
   };
@@ -550,17 +664,16 @@ NS_INTERFACE_MAP_BEGIN(Preferences)
     NS_INTERFACE_MAP_ENTRY(nsIPrefService)
     NS_INTERFACE_MAP_ENTRY(nsIObserver)
     NS_INTERFACE_MAP_ENTRY(nsIPrefBranch)
     NS_INTERFACE_MAP_ENTRY(nsIPrefBranch2)
     NS_INTERFACE_MAP_ENTRY(nsIPrefBranchInternal)
     NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END
 
-
 /*
  * nsIPrefService Implementation
  */
 
 InfallibleTArray<Preferences::PrefSetting>* gInitPrefs;
 
 /*static*/
 void
@@ -624,18 +737,19 @@ Preferences::ResetAndReadUserPrefs()
   sPreferences->ResetUserPrefs();
   return sPreferences->ReadUserPrefs(nullptr);
 }
 
 NS_IMETHODIMP
 Preferences::Observe(nsISupports *aSubject, const char *aTopic,
                      const char16_t *someData)
 {
-  if (XRE_IsContentProcess())
+  if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {
     return NS_ERROR_NOT_AVAILABLE;
+  }
 
   nsresult rv = NS_OK;
 
   if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
     rv = SavePrefFile(nullptr);
   } else if (!strcmp(aTopic, "load-extension-defaults")) {
     pref_LoadPrefsInDirList(NS_EXT_PREFS_DEFAULTS_DIR_LIST);
   } else if (!nsCRT::strcmp(aTopic, "reload-default-prefs")) {
@@ -649,18 +763,18 @@ Preferences::Observe(nsISupports *aSubje
   }
   return rv;
 }
 
 
 NS_IMETHODIMP
 Preferences::ReadUserPrefs(nsIFile *aFile)
 {
-  if (XRE_IsContentProcess()) {
-    NS_ERROR("cannot load prefs from content process");
+  if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {
+    NS_ERROR("must load prefs from parent process");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   nsresult rv;
 
   if (nullptr == aFile) {
     rv = UseDefaultPrefFile();
     // A user pref file is optional.
@@ -679,50 +793,65 @@ Preferences::ReadUserPrefs(nsIFile *aFil
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
 Preferences::ResetPrefs()
 {
-  if (XRE_IsContentProcess()) {
-    NS_ERROR("cannot reset prefs from content process");
+  if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {
+    NS_ERROR("must reset prefs from parent process");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   NotifyServiceObservers(NS_PREFSERVICE_RESET_TOPIC_ID);
   PREF_CleanupPrefs();
 
   PREF_Init();
 
   return pref_InitInitialObjects();
 }
 
 NS_IMETHODIMP
 Preferences::ResetUserPrefs()
 {
-  if (XRE_IsContentProcess()) {
-    NS_ERROR("cannot reset user prefs from content process");
+  if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {
+    NS_ERROR("must reset user prefs from parent process");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   PREF_ClearAllUserPrefs();
-  return NS_OK;    
+  return NS_OK;
+}
+
+bool
+Preferences::AllowOffMainThreadSave()
+{
+  return false;
+}
+
+nsresult
+Preferences::SavePrefFileOnMainThread()
+{
+    return SavePrefFileInternal(nullptr, SaveMethod::OnMainThread);
+}
+
+nsresult
+Preferences::SavePrefFileOffMainThread()
+{
+  return SavePrefFileInternal(nullptr, SaveMethod::OffMainThread);
 }
 
 NS_IMETHODIMP
 Preferences::SavePrefFile(nsIFile *aFile)
 {
-  if (XRE_IsContentProcess()) {
-    NS_ERROR("cannot save pref file from content process");
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  return SavePrefFileInternal(aFile);
+  // This is the method accessible from service API.  Make it off
+  // main thread.
+  return SavePrefFileInternal(aFile, SaveMethod::OffMainThread);
 }
 
 static nsresult
 ReadExtensionPrefs(nsIFile *aFile)
 {
   nsresult rv;
   nsCOMPtr<nsIZipReader> reader = do_CreateInstance(kZipReaderCID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -878,23 +1007,23 @@ Preferences::UseDefaultPrefFile()
   nsresult rv = NS_GetSpecialDirectory(NS_APP_PREFS_50_FILE, getter_AddRefs(aFile));
 
   if (NS_SUCCEEDED(rv)) {
     rv = ReadAndOwnUserPrefFile(aFile);
     // Most likely cause of failure here is that the file didn't
     // exist, so save a new one. mUserPrefReadFailed will be
     // used to catch an error in actually reading the file.
     if (NS_FAILED(rv)) {
-      if (NS_FAILED(SavePrefFileInternal(aFile)))
+      if (NS_FAILED(SavePrefFileInternal(aFile, SaveMethod::OnMainThread)))
         NS_ERROR("Failed to save new shared pref file");
       else
         rv = NS_OK;
     }
   }
-  
+
   return rv;
 }
 
 nsresult
 Preferences::UseUserPrefFile()
 {
   nsresult rv = NS_OK;
   nsCOMPtr<nsIFile> aFile;
@@ -964,111 +1093,77 @@ Preferences::ReadAndOwnUserPrefFile(nsIF
   } else {
     rv = NS_ERROR_FILE_NOT_FOUND;
   }
 
   return rv;
 }
 
 nsresult
-Preferences::SavePrefFileInternal(nsIFile *aFile)
+Preferences::SavePrefFileInternal(nsIFile *aFile, SaveMethod aOnWhichThread)
 {
+  if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {
+    NS_ERROR("must save pref file from parent process");
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
   // We allow different behavior here when aFile argument is not null,
   // but it happens to be the same as the current file.  It is not
   // clear that we should, but it does give us a "force" save on the
   // unmodified pref file (see the original bug 160377 when we added this.)
 
   if (nullptr == aFile) {
+    // Off main thread writing only if allowed
+    if (!AllowOffMainThreadSave()) {
+      aOnWhichThread = SaveMethod::OnMainThread;
+    }
+
     // the mDirty flag tells us if we should write to mCurrentFile
     // we only check this flag when the caller wants to write to the default
     if (!mDirty) {
       return NS_OK;
     }
 
     // It's possible that we never got a prefs file.
     nsresult rv = NS_OK;
     if (mCurrentFile) {
-      rv = WritePrefFile(mCurrentFile);
+      rv = WritePrefFile(mCurrentFile, aOnWhichThread);
     }
 
     // If we succeeded writing to mCurrentFile, reset the dirty flag
     if (NS_SUCCEEDED(rv)) {
       mDirty = false;
     }
     return rv;
   } else {
-    return WritePrefFile(aFile);
+    // We only allow off main thread writes on mCurrentFile
+    return WritePrefFile(aFile, SaveMethod::OnMainThread);
   }
 }
 
 nsresult
-Preferences::WritePrefFile(nsIFile* aFile)
+Preferences::WritePrefFile(nsIFile* aFile, SaveMethod /*aOnWhichThread*/)
 {
-  nsCOMPtr<nsIOutputStream> outStreamSink;
-  nsCOMPtr<nsIOutputStream> outStream;
-  uint32_t                  writeAmount;
-  nsresult                  rv;
-
-  if (!gHashTable)
+  if (!gHashTable) {
     return NS_ERROR_NOT_INITIALIZED;
+  }
 
   PROFILER_LABEL("Preferences", "WritePrefFile",
                  js::ProfileEntry::Category::OTHER);
 
-  // execute a "safe" save by saving through a tempfile
-  rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStreamSink),
-                                       aFile,
-                                       -1,
-                                       0600);
-  if (NS_FAILED(rv)) 
-      return rv;
-  rv = NS_NewBufferedOutputStream(getter_AddRefs(outStream), outStreamSink, 4096);
-  if (NS_FAILED(rv)) 
-      return rv;  
-
-  // get the lines that we're supposed to be writing to the file
-  uint32_t prefCount;
-  UniquePtr<char*[]> valueArray = pref_savePrefs(gHashTable, &prefCount);
-
-  /* Sort the preferences to make a readable file on disk */
-  NS_QuickSort(valueArray.get(), prefCount, sizeof(char *),
-               pref_CompareStrings, nullptr);
-
-  // write out the file header
-  outStream->Write(kPrefFileHeader, sizeof(kPrefFileHeader) - 1, &writeAmount);
-
-  for (uint32_t valueIdx = 0; valueIdx < prefCount; valueIdx++) {
-    char*& pref = valueArray[valueIdx];
-    MOZ_ASSERT(pref);
-    outStream->Write(pref, strlen(pref), &writeAmount);
-    outStream->Write(NS_LINEBREAK, NS_LINEBREAK_LEN, &writeAmount);
-    free(pref);
-    pref = nullptr;
-  }
-
-  // tell the safe output stream to overwrite the real prefs file
-  // (it'll abort if there were any errors during writing)
-  nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outStream);
-  NS_ASSERTION(safeStream, "expected a safe output stream!");
-  if (safeStream) {
-    rv = safeStream->Finish();
-    if (NS_FAILED(rv)) {
-      NS_WARNING("failed to save prefs file! possible data loss");
-      return rv;
-    }
-  }
-  return NS_OK;
+  PreferencesWriter writer(aFile, gHashTable, 0);
+  return writer.Write();
 }
 
 static nsresult openPrefFile(nsIFile* aFile)
 {
   nsCOMPtr<nsIInputStream> inStr;
 
   nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), aFile);
-  if (NS_FAILED(rv)) 
+  if (NS_FAILED(rv))
     return rv;        
 
   int64_t fileSize64;
   rv = aFile->GetFileSize(&fileSize64);
   if (NS_FAILED(rv))
     return rv;
   NS_ENSURE_TRUE(fileSize64 <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
 
--- a/modules/libpref/Preferences.h
+++ b/modules/libpref/Preferences.h
@@ -419,34 +419,51 @@ public:
   static void SetInitPhase(pref_initPhase phase);
   static pref_initPhase InitPhase();
 #endif
 
   static int64_t SizeOfIncludingThisAndOtherStuff(mozilla::MallocSizeOf aMallocSizeOf);
 
   static void DirtyCallback();
 
+  // Explicitly choosing a main thread or off main thread (if allowed)
+  // preferences file write.  Only for the default file.  The guarantee
+  // for the "on main thread" is that when it returns, the file on disk
+  // reflect the current state of preferences.
+  nsresult SavePrefFileOnMainThread();
+  nsresult SavePrefFileOffMainThread();
+
 protected:
   virtual ~Preferences();
 
   nsresult NotifyServiceObservers(const char *aSubject);
   /**
    * Reads the default pref file or, if that failed, try to save a new one.
    *
    * @return NS_OK if either action succeeded,
    *         or the error code related to the read attempt.
    */
   nsresult UseDefaultPrefFile();
   nsresult UseUserPrefFile();
   nsresult ReadAndOwnUserPrefFile(nsIFile *aFile);
   nsresult ReadAndOwnSharedUserPrefFile(nsIFile *aFile);
-  nsresult SavePrefFileInternal(nsIFile* aFile);
-  nsresult WritePrefFile(nsIFile* aFile);
   nsresult MakeBackupPrefFile(nsIFile *aFile);
 
+  // Default pref file save can be off or on main thread.
+  enum class SaveMethod {
+    OnMainThread,
+    OffMainThread
+  };
+
+  // Off main thread is only respected for the default aFile value (nullptr)
+  nsresult SavePrefFileInternal(nsIFile* aFile, SaveMethod aOnWhichThread);
+  nsresult WritePrefFile(nsIFile* aFile, SaveMethod aOnWhichThread);
+
+  bool AllowOffMainThreadSave();
+
   /**
    * Helpers for implementing
    * Register(Prefix)Callback/Unregister(Prefix)Callback.
    */
 public:
   // Public so the ValueObserver classes can use it.
   enum MatchKind {
     PrefixMatch,