Bug 1276488 - Add diagnostics about Prefs startup failures. r=froydnj.
authorNicholas Nethercote <nnethercote@mozilla.com>
Wed, 06 Sep 2017 15:17:05 +1000
changeset 379418 ed7b5443cf8b
parent 379417 93d2637ff309
child 379419 1e4b5ec2904f
push id32453
push userarchaeopteryx@coole-files.de
push dateThu, 07 Sep 2017 10:39:44 +0000
treeherdermozilla-central@37b95547f0d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1276488
milestone57.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 1276488 - Add diagnostics about Prefs startup failures. r=froydnj.
modules/libpref/Preferences.cpp
modules/libpref/Preferences.h
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -125,17 +125,17 @@ Preferences::DirtyCallback()
                                    &Preferences::SavePrefFileAsynchronous),
         PREF_DELAY_MS);
     }
   }
 }
 
 // Prototypes
 static nsresult openPrefFile(nsIFile* aFile);
-static nsresult pref_InitInitialObjects(void);
+static Result<Ok, const char*> pref_InitInitialObjects();
 static nsresult pref_LoadPrefsInDirList(const char *listId);
 static nsresult ReadExtensionPrefs(nsIFile *aFile);
 
 static const char kTelemetryPref[] = "toolkit.telemetry.enabled";
 static const char kOldTelemetryPref[] = "toolkit.telemetry.enabledPreRelease";
 static const char kChannelPref[] = "app.update.channel";
 
 static const char kPrefFileHeader[] =
@@ -402,16 +402,19 @@ struct CacheData {
   union {
     bool defaultValueBool;
     int32_t defaultValueInt;
     uint32_t defaultValueUint;
     float defaultValueFloat;
   };
 };
 
+// gCacheDataDesc holds information about prefs startup. It's being used for
+// diagnosing prefs startup problems in bug 1276488.
+static const char* gCacheDataDesc = "untouched";
 static nsTArray<nsAutoPtr<CacheData> >* gCacheData = nullptr;
 static nsRefPtrHashtable<ValueObserverHashKey,
                          ValueObserver>* gObserverTable = nullptr;
 
 #ifdef DEBUG
 static bool
 HaveExistingCacheFor(void* aPtr)
 {
@@ -605,33 +608,39 @@ public:
 Preferences*
 Preferences::GetInstanceForService()
 {
   if (sPreferences) {
     NS_ADDREF(sPreferences);
     return sPreferences;
   }
 
-  NS_ENSURE_TRUE(!sShutdown, nullptr);
+  if (sShutdown) {
+    gCacheDataDesc = "shutting down in GetInstanceForService()";
+    return nullptr;
+  }
 
   sRootBranch = new nsPrefBranch("", false);
   NS_ADDREF(sRootBranch);
   sDefaultRootBranch = new nsPrefBranch("", true);
   NS_ADDREF(sDefaultRootBranch);
 
   sPreferences = new Preferences();
   NS_ADDREF(sPreferences);
 
-  if (NS_FAILED(sPreferences->Init())) {
+  Result<Ok, const char*> res = sPreferences->Init();
+  if (res.isErr()) {
     // The singleton instance will delete sRootBranch and sDefaultRootBranch.
+    gCacheDataDesc = res.unwrapErr();
     NS_RELEASE(sPreferences);
     return nullptr;
   }
 
   gCacheData = new nsTArray<nsAutoPtr<CacheData> >();
+  gCacheDataDesc = "set by GetInstanceForService()";
 
   gObserverTable = new nsRefPtrHashtable<ValueObserverHashKey, ValueObserver>();
 
   // Preferences::GetInstanceForService() can be called from GetService(), and
   // RegisterStrongMemoryReporter calls GetService(nsIMemoryReporter).  To
   // avoid a potential recursive GetService() call, we can't register the
   // memory reporter here; instead, do it off a runnable.
   RefPtr<AddPreferencesMemoryReporterRunnable> runnable =
@@ -731,64 +740,67 @@ NS_INTERFACE_MAP_END
 InfallibleTArray<Preferences::PrefSetting>* gInitPrefs;
 
 /*static*/
 void
 Preferences::SetInitPreferences(nsTArray<PrefSetting>* aPrefs) {
   gInitPrefs = new InfallibleTArray<PrefSetting>(mozilla::Move(*aPrefs));
 }
 
-nsresult
+Result<Ok, const char*>
 Preferences::Init()
 {
-  nsresult rv;
-
   PREF_SetDirtyCallback(&DirtyCallback);
   PREF_Init();
 
-  rv = pref_InitInitialObjects();
-  NS_ENSURE_SUCCESS(rv, rv);
+  MOZ_TRY(pref_InitInitialObjects());
 
   if (XRE_IsContentProcess()) {
     MOZ_ASSERT(gInitPrefs);
     for (unsigned int i = 0; i < gInitPrefs->Length(); i++) {
       Preferences::SetPreference(gInitPrefs->ElementAt(i));
     }
     delete gInitPrefs;
     gInitPrefs = nullptr;
-    return NS_OK;
+    return Ok();
   }
 
   nsCString lockFileName;
   /*
    * The following is a small hack which will allow us to only load the library
    * which supports the netscape.cfg file if the preference is defined. We
    * test for the existence of the pref, set in the all.js (mozilla) or
    * all-ns.js (netscape 6), and if it exists we startup the pref config
    * category which will do the rest.
    */
 
-  rv = PREF_CopyCharPref("general.config.filename", getter_Copies(lockFileName), false);
+  nsresult rv = PREF_CopyCharPref("general.config.filename",
+                                  getter_Copies(lockFileName), false);
   if (NS_SUCCEEDED(rv))
     NS_CreateServicesFromCategory("pref-config-startup",
                                   static_cast<nsISupports *>(static_cast<void *>(this)),
                                   "pref-config-startup");
 
   nsCOMPtr<nsIObserverService> observerService =
     mozilla::services::GetObserverService();
-  if (!observerService)
-    return NS_ERROR_FAILURE;
+  if (!observerService) {
+    return Err("GetObserverService() failed (1)");
+  }
 
   observerService->AddObserver(this, "profile-before-change-telemetry", true);
   rv = observerService->AddObserver(this, "profile-before-change", true);
 
   observerService->AddObserver(this, "load-extension-defaults", true);
   observerService->AddObserver(this, "suspend_process_notification", true);
 
-  return(rv);
+  if (NS_FAILED(rv)) {
+    return Err("AddObserver(\"profile-before-change\") failed");
+  }
+
+  return Ok();
 }
 
 // static
 void
 Preferences::InitializeUserPrefs()
 {
   MOZ_ASSERT(!sPreferences->mCurrentFile, "Should only initialize prefs once");
 
@@ -836,17 +848,17 @@ Preferences::Observe(nsISupports *aSubje
     // for any pending saves to complete.
     SavePrefFileBlocking();
     MOZ_ASSERT(!mDirty, "Preferences should not be dirty");
     mProfileShutdown = true;
   } else if (!strcmp(aTopic, "load-extension-defaults")) {
     pref_LoadPrefsInDirList(NS_EXT_PREFS_DEFAULTS_DIR_LIST);
   } else if (!nsCRT::strcmp(aTopic, "reload-default-prefs")) {
     // Reload the default prefs from file.
-    pref_InitInitialObjects();
+    Unused << pref_InitInitialObjects();
   } else if (!nsCRT::strcmp(aTopic, "suspend_process_notification")) {
     // Our process is being suspended. The OS may wake our process later,
     // or it may kill the process. In case our process is going to be killed
     // from the suspended state, we save preferences before suspending.
     rv = SavePrefFileBlocking();
   }
   return rv;
 }
@@ -876,17 +888,17 @@ Preferences::ResetPrefs()
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   NotifyServiceObservers(NS_PREFSERVICE_RESET_TOPIC_ID);
   PREF_CleanupPrefs();
 
   PREF_Init();
 
-  return pref_InitInitialObjects();
+  return pref_InitInitialObjects().isOk() ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 Preferences::ResetUserPrefs()
 {
   if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {
     NS_ERROR("must reset user prefs from parent process");
     return NS_ERROR_NOT_AVAILABLE;
@@ -1448,17 +1460,18 @@ static nsresult pref_ReadPrefFromJar(nsZ
 
   return NS_OK;
 }
 
 //----------------------------------------------------------------------------------------
 // Initialize default preference JavaScript buffers from
 // appropriate TEXT resources
 //----------------------------------------------------------------------------------------
-static nsresult pref_InitInitialObjects()
+static Result<Ok, const char*>
+pref_InitInitialObjects()
 {
   nsresult rv;
 
   // In omni.jar case, we load the following prefs:
   // - jar:$gre/omni.jar!/greprefs.js
   // - jar:$gre/omni.jar!/defaults/pref/*.js
   // In non omni.jar case, we load:
   // - $gre/greprefs.js
@@ -1485,52 +1498,53 @@ static nsresult pref_InitInitialObjects(
   nsTArray<nsCString> prefEntries;
   const char *entryName;
   uint16_t entryNameLen;
 
   RefPtr<nsZipArchive> jarReader = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
   if (jarReader) {
     // Load jar:$gre/omni.jar!/greprefs.js
     rv = pref_ReadPrefFromJar(jarReader, "greprefs.js");
-    NS_ENSURE_SUCCESS(rv, rv);
+    NS_ENSURE_SUCCESS(rv, Err("pref_ReadPrefFromJar() failed"));
 
     // Load jar:$gre/omni.jar!/defaults/pref/*.js
     rv = jarReader->FindInit("defaults/pref/*.js$", &findPtr);
-    NS_ENSURE_SUCCESS(rv, rv);
+    NS_ENSURE_SUCCESS(rv, Err("jarReader->FindInit() failed"));
 
     find = findPtr;
     while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) {
       prefEntries.AppendElement(Substring(entryName, entryNameLen));
     }
 
     prefEntries.Sort();
     for (uint32_t i = prefEntries.Length(); i--; ) {
       rv = pref_ReadPrefFromJar(jarReader, prefEntries[i].get());
       if (NS_FAILED(rv))
         NS_WARNING("Error parsing preferences.");
     }
   } else {
     // Load $gre/greprefs.js
     nsCOMPtr<nsIFile> greprefsFile;
     rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(greprefsFile));
-    NS_ENSURE_SUCCESS(rv, rv);
+    NS_ENSURE_SUCCESS(rv, Err("NS_GetSpecialDirectory(NS_GRE_DIR) failed"));
 
     rv = greprefsFile->AppendNative(NS_LITERAL_CSTRING("greprefs.js"));
-    NS_ENSURE_SUCCESS(rv, rv);
+    NS_ENSURE_SUCCESS(rv, Err("greprefsFile->AppendNative() failed"));
 
     rv = openPrefFile(greprefsFile);
     if (NS_FAILED(rv))
       NS_WARNING("Error parsing GRE default preferences. Is this an old-style embedding app?");
   }
 
   // Load $gre/defaults/pref/*.js
   nsCOMPtr<nsIFile> defaultPrefDir;
 
   rv = NS_GetSpecialDirectory(NS_APP_PREF_DEFAULTS_50_DIR, getter_AddRefs(defaultPrefDir));
-  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_SUCCESS(
+    rv, Err("NS_GetSpecialDirectory(NS_APP_PREF_DEFAULTS_50_DIR) failed"));
 
   /* these pref file names should not be used: we process them after all other application pref files for backwards compatibility */
   static const char* specialFiles[] = {
 #if defined(XP_MACOSX)
     "macprefs.js"
 #elif defined(XP_WIN)
     "winpref.js"
 #elif defined(XP_UNIX)
@@ -1551,32 +1565,33 @@ static nsresult pref_InitInitialObjects(
   // or jar:$gre/omni.jar!/defaults/preferences/*.js.
   RefPtr<nsZipArchive> appJarReader = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP);
   // GetReader(mozilla::Omnijar::APP) returns null when $app == $gre, in which
   // case we look for app-specific default preferences in $gre.
   if (!appJarReader)
     appJarReader = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
   if (appJarReader) {
     rv = appJarReader->FindInit("defaults/preferences/*.js$", &findPtr);
-    NS_ENSURE_SUCCESS(rv, rv);
+    NS_ENSURE_SUCCESS(rv, Err("appJarReader->FindInit() failed"));
     find = findPtr;
     prefEntries.Clear();
     while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) {
       prefEntries.AppendElement(Substring(entryName, entryNameLen));
     }
     prefEntries.Sort();
     for (uint32_t i = prefEntries.Length(); i--; ) {
       rv = pref_ReadPrefFromJar(appJarReader, prefEntries[i].get());
       if (NS_FAILED(rv))
         NS_WARNING("Error parsing preferences.");
     }
   }
 
   rv = pref_LoadPrefsInDirList(NS_APP_PREFS_DEFAULTS_DIR_LIST);
-  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_SUCCESS(
+    rv, Err("pref_LoadPrefsInDirList(NS_APP_PREFS_DEFAULTS_DIR_LIST) failed"));
 
   // Set up the correct default for toolkit.telemetry.enabled.
   // If this build has MOZ_TELEMETRY_ON_BY_DEFAULT *or* we're on the beta
   // channel, telemetry is on by default, otherwise not. This is necessary
   // so that beta users who are testing final release builds don't flipflop
   // defaults.
   if (Preferences::GetDefaultType(kTelemetryPref) == nsIPrefBranch::PREF_INVALID) {
     bool prerelease = false;
@@ -1592,22 +1607,25 @@ static nsresult pref_InitInitialObjects(
     PREF_SetBoolPref(kTelemetryPref, prerelease, true);
   }
 
   NS_CreateServicesFromCategory(NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID,
                                 nullptr, NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID);
 
   nsCOMPtr<nsIObserverService> observerService =
     mozilla::services::GetObserverService();
-  if (!observerService)
-    return NS_ERROR_FAILURE;
+  NS_ENSURE_SUCCESS(rv, Err("GetObserverService() failed (2)"));
 
   observerService->NotifyObservers(nullptr, NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID, nullptr);
 
-  return pref_LoadPrefsInDirList(NS_EXT_PREFS_DEFAULTS_DIR_LIST);
+  rv = pref_LoadPrefsInDirList(NS_EXT_PREFS_DEFAULTS_DIR_LIST);
+  NS_ENSURE_SUCCESS(
+    rv, Err("pref_LoadPrefsInDirList(NS_EXT_PREFS_DEFAULTS_DIR_LIST) failed"));
+
+  return Ok();
 }
 
 
 /******************************************************************************
  *
  * static utilities
  *
  ******************************************************************************/
@@ -1976,32 +1994,41 @@ Preferences::UnregisterCallback(PrefChan
 
 static void BoolVarChanged(const char* aPref, void* aClosure)
 {
   CacheData* cache = static_cast<CacheData*>(aClosure);
   *((bool*)cache->cacheLocation) =
     Preferences::GetBool(aPref, cache->defaultValueBool);
 }
 
+static void
+CacheDataAppendElement(CacheData* aData)
+{
+  if (!gCacheData) {
+    MOZ_CRASH_UNSAFE_PRINTF("!gCacheData: %s", gCacheDataDesc);
+  }
+  gCacheData->AppendElement(aData);
+}
+
 // static
 nsresult
 Preferences::AddBoolVarCache(bool* aCache,
                              const char* aPref,
                              bool aDefault)
 {
   WATCHING_PREF_RAII();
   NS_ASSERTION(aCache, "aCache must not be NULL");
 #ifdef DEBUG
   AssertNotAlreadyCached("bool", aPref, aCache);
 #endif
   *aCache = GetBool(aPref, aDefault);
   CacheData* data = new CacheData();
   data->cacheLocation = aCache;
   data->defaultValueBool = aDefault;
-  gCacheData->AppendElement(data);
+  CacheDataAppendElement(data);
   RegisterPriorityCallback(BoolVarChanged, aPref, data);
   return NS_OK;
 }
 
 static void IntVarChanged(const char* aPref, void* aClosure)
 {
   CacheData* cache = static_cast<CacheData*>(aClosure);
   *((int32_t*)cache->cacheLocation) =
@@ -2018,17 +2045,17 @@ Preferences::AddIntVarCache(int32_t* aCa
   NS_ASSERTION(aCache, "aCache must not be NULL");
 #ifdef DEBUG
   AssertNotAlreadyCached("int", aPref, aCache);
 #endif
   *aCache = Preferences::GetInt(aPref, aDefault);
   CacheData* data = new CacheData();
   data->cacheLocation = aCache;
   data->defaultValueInt = aDefault;
-  gCacheData->AppendElement(data);
+  CacheDataAppendElement(data);
   RegisterPriorityCallback(IntVarChanged, aPref, data);
   return NS_OK;
 }
 
 static void UintVarChanged(const char* aPref, void* aClosure)
 {
   CacheData* cache = static_cast<CacheData*>(aClosure);
   *((uint32_t*)cache->cacheLocation) =
@@ -2045,17 +2072,17 @@ Preferences::AddUintVarCache(uint32_t* a
   NS_ASSERTION(aCache, "aCache must not be NULL");
 #ifdef DEBUG
   AssertNotAlreadyCached("uint", aPref, aCache);
 #endif
   *aCache = Preferences::GetUint(aPref, aDefault);
   CacheData* data = new CacheData();
   data->cacheLocation = aCache;
   data->defaultValueUint = aDefault;
-  gCacheData->AppendElement(data);
+  CacheDataAppendElement(data);
   RegisterPriorityCallback(UintVarChanged, aPref, data);
   return NS_OK;
 }
 
 template <MemoryOrdering Order>
 static void AtomicUintVarChanged(const char* aPref, void* aClosure)
 {
   CacheData* cache = static_cast<CacheData*>(aClosure);
@@ -2074,17 +2101,17 @@ Preferences::AddAtomicUintVarCache(Atomi
   NS_ASSERTION(aCache, "aCache must not be NULL");
 #ifdef DEBUG
   AssertNotAlreadyCached("uint", aPref, aCache);
 #endif
   *aCache = Preferences::GetUint(aPref, aDefault);
   CacheData* data = new CacheData();
   data->cacheLocation = aCache;
   data->defaultValueUint = aDefault;
-  gCacheData->AppendElement(data);
+  CacheDataAppendElement(data);
   RegisterPriorityCallback(AtomicUintVarChanged<Order>, aPref, data);
   return NS_OK;
 }
 
 // Since the definition of this template function is not in a header file,
 // we need to explicitly specify the instantiations that are required.
 // Currently only the order=Relaxed variant is needed.
 template
@@ -2108,17 +2135,17 @@ Preferences::AddFloatVarCache(float* aCa
   NS_ASSERTION(aCache, "aCache must not be NULL");
 #ifdef DEBUG
   AssertNotAlreadyCached("float", aPref, aCache);
 #endif
   *aCache = Preferences::GetFloat(aPref, aDefault);
   CacheData* data = new CacheData();
   data->cacheLocation = aCache;
   data->defaultValueFloat = aDefault;
-  gCacheData->AppendElement(data);
+  CacheDataAppendElement(data);
   RegisterPriorityCallback(FloatVarChanged, aPref, data);
   return NS_OK;
 }
 
 // static
 nsresult
 Preferences::GetDefaultBool(const char* aPref, bool* aResult)
 {
--- a/modules/libpref/Preferences.h
+++ b/modules/libpref/Preferences.h
@@ -36,17 +36,18 @@ enum pref_initPhase {
 };
 
 #define SET_PREF_PHASE(p) Preferences::SetInitPhase(p)
 #else
 #define SET_PREF_PHASE(p) do { } while (0)
 #endif
 
 namespace mozilla {
-
+struct Ok;
+template <typename V, typename E> class Result;
 namespace dom {
 class PrefSetting;
 } // namespace dom
 
 class Preferences final : public nsIPrefService,
                           public nsIObserver,
                           public nsIPrefBranch,
                           public nsSupportsWeakReference
@@ -56,17 +57,17 @@ public:
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIPREFSERVICE
   NS_FORWARD_NSIPREFBRANCH(sRootBranch->)
   NS_DECL_NSIOBSERVER
 
   Preferences();
 
-  nsresult Init();
+  mozilla::Result<Ok, const char*> Init();
 
   /**
    * Returns true if the Preferences service is available, false otherwise.
    */
   static bool IsServiceAvailable();
 
   /**
    * Initialize user prefs from prefs.js/user.js