Bug 1422087: Purge startup caches after an incomplete startup. r=Mossop
authorKris Maglione <maglione.k@gmail.com>
Mon, 05 Mar 2018 15:19:07 -0800
changeset 462430 686b970032b4eff9d6513bbe3c2652b75a5378d8
parent 462429 a471a4087690521628d99dd02f402447c67f404a
child 462431 e957efe5d14c0a7a79483b477e9a6c9a8e7ddb3f
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMossop
bugs1422087
milestone60.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 1422087: Purge startup caches after an incomplete startup. r=Mossop Since we know that corrupt startup caches can and do cause startup crashes, we should ideally purge them any time we detect that the last startup resulted in a crash. The alternative is waiting until the users restart enough times to trigger safe mode, and hope that they start in safe mode and then immediately restart in normal mode. This is not a great user experience. Unfortunately, the normal startup crash detection mechanism relies on the preference service, which starts up far too late for us to make this decision effectively. To work around that, this patch adds a new startup crash detection mechanism that creates an incomplete startup canary file which we remove at the end of the startup crash detection window, or on any sort of graceful exit. MozReview-Commit-ID: ALaLsyF4meg
toolkit/components/startup/nsAppStartup.cpp
toolkit/xre/nsAppRunner.cpp
toolkit/xre/nsAppRunner.h
--- a/toolkit/components/startup/nsAppStartup.cpp
+++ b/toolkit/components/startup/nsAppStartup.cpp
@@ -22,16 +22,18 @@
 #include "nsIWindowWatcher.h"
 #include "nsIXULRuntime.h"
 #include "nsIXULWindow.h"
 #include "nsNativeCharsetUtils.h"
 #include "nsThreadUtils.h"
 #include "nsAutoPtr.h"
 #include "nsString.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Unused.h"
 #include "GeckoProfiler.h"
 
 #include "prprf.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsWidgetsCID.h"
 #include "nsAppRunner.h"
 #include "nsAppShellCID.h"
 #include "nsXPCOMCIDInternal.h"
@@ -917,31 +919,45 @@ nsAppStartup::TrackStartupCrashBegin(boo
   nsCOMPtr<nsIPrefService> prefs = Preferences::GetService();
   rv = static_cast<Preferences *>(prefs.get())->SavePrefFileBlocking(); // flush prefs to disk since we are tracking crashes
   NS_ENSURE_SUCCESS(rv, rv);
 
   GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
   return rv;
 }
 
+static nsresult
+RemoveIncompleteStartupFile()
+{
+  nsCOMPtr<nsIFile> file;
+  MOZ_TRY(NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, getter_AddRefs(file)));
+
+  MOZ_TRY_VAR(file, mozilla::startup::GetIncompleteStartupFile(file));
+  return file->Remove(false);
+}
+
 NS_IMETHODIMP
 nsAppStartup::TrackStartupCrashEnd()
 {
   bool inSafeMode = false;
   nsCOMPtr<nsIXULRuntime> xr = do_GetService(XULRUNTIME_SERVICE_CONTRACTID);
   if (xr)
     xr->GetInSafeMode(&inSafeMode);
 
   // return if we already ended or we're restarting into safe mode
   if (mStartupCrashTrackingEnded || (mIsSafeModeNecessary && !inSafeMode))
     return NS_OK;
   mStartupCrashTrackingEnded = true;
 
   StartupTimeline::Record(StartupTimeline::STARTUP_CRASH_DETECTION_END);
 
+  // Remove the incomplete startup canary file, so the next startup doesn't
+  // detect a recent startup crash.
+  Unused << NS_WARN_IF(NS_FAILED(RemoveIncompleteStartupFile()));
+
   // Use the timestamp of XRE_main as an approximation for the lock file timestamp.
   // See MAX_STARTUP_BUFFER for the buffer time period.
   TimeStamp mainTime = StartupTimeline::Get(StartupTimeline::MAIN);
   nsresult rv;
 
   if (mainTime.IsNull()) {
     NS_WARNING("Could not get StartupTimeline::MAIN time.");
   } else {
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -11,16 +11,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/ChaosMode.h"
 #include "mozilla/IOInterposer.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryChecking.h"
 #include "mozilla/Poison.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Printf.h"
+#include "mozilla/ResultExtensions.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/intl/LocaleService.h"
 
 #include "nsAppRunner.h"
 #include "mozilla/XREAppData.h"
 #include "mozilla/Bootstrap.h"
@@ -232,16 +233,17 @@
 #include "mozilla/CodeCoverageHandler.h"
 #endif
 
 extern uint32_t gRestartMode;
 extern void InstallSignalHandlers(const char *ProgramName);
 
 #define FILE_COMPATIBILITY_INFO NS_LITERAL_CSTRING("compatibility.ini")
 #define FILE_INVALIDATE_CACHES NS_LITERAL_CSTRING(".purgecaches")
+#define FILE_STARTUP_INCOMPLETE NS_LITERAL_STRING(".startup-incomplete")
 
 int    gArgc;
 char **gArgv;
 
 static const char gToolkitVersion[] = NS_STRINGIFY(GRE_MILESTONE);
 static const char gToolkitBuildID[] = NS_STRINGIFY(MOZ_BUILDID);
 
 static nsIProfileLock* gProfileLock;
@@ -296,16 +298,17 @@ void XRE_LibFuzzerSetDriver(LibFuzzerDri
 #endif
 #endif // FUZZING
 
 namespace mozilla {
 int (*RunGTest)(int*, char**) = 0;
 } // namespace mozilla
 
 using namespace mozilla;
+using namespace mozilla::startup;
 using mozilla::Unused;
 using mozilla::scache::StartupCache;
 using mozilla::dom::ContentParent;
 using mozilla::dom::ContentChild;
 using mozilla::intl::LocaleService;
 
 // Save literal putenv string to environment variable.
 static void
@@ -3120,16 +3123,18 @@ public:
     mAppData = nullptr;
   }
 
   int XRE_main(int argc, char* argv[], const BootstrapConfig& aConfig);
   int XRE_mainInit(bool* aExitFlag);
   int XRE_mainStartup(bool* aExitFlag);
   nsresult XRE_mainRun();
 
+  Result<bool, nsresult> CheckLastStartupWasCrash();
+
   nsCOMPtr<nsINativeAppSupport> mNativeApp;
   nsCOMPtr<nsIToolkitProfileService> mProfileSvc;
   nsCOMPtr<nsIFile> mProfD;
   nsCOMPtr<nsIFile> mProfLD;
   nsCOMPtr<nsIProfileLock> mProfileLock;
 #ifdef MOZ_ENABLE_XREMOTE
   nsCOMPtr<nsIRemoteService> mRemoteService;
   nsProfileLock mRemoteLock;
@@ -3785,16 +3790,61 @@ static void SetShutdownChecks() {
       gShutdownChecks = SCM_RECORD;
     } else if (strcmp(mozShutdownChecksEnv, "nothing") == 0) {
       gShutdownChecks = SCM_NOTHING;
     }
   }
 
 }
 
+namespace mozilla {
+namespace startup {
+  Result<nsCOMPtr<nsIFile>, nsresult>
+  GetIncompleteStartupFile(nsIFile* aProfLD)
+  {
+    nsCOMPtr<nsIFile> crashFile;
+    MOZ_TRY(aProfLD->Clone(getter_AddRefs(crashFile)));
+    MOZ_TRY(crashFile->Append(FILE_STARTUP_INCOMPLETE));
+    return Move(crashFile);
+  }
+}
+}
+
+// Check whether the last startup attempt resulted in a crash within the
+// last 6 hours.
+// Note that this duplicates the logic in nsAppStartup::TrackStartupCrashBegin,
+// which runs too late for our purposes.
+Result<bool, nsresult>
+XREMain::CheckLastStartupWasCrash()
+{
+  constexpr int32_t MAX_TIME_SINCE_STARTUP = 6 * 60 * 60 * 1000;
+
+  nsCOMPtr<nsIFile> crashFile;
+  MOZ_TRY_VAR(crashFile, GetIncompleteStartupFile(mProfLD));
+
+  // Attempt to create the incomplete startup canary file. If the file already
+  // exists, this fails, and we know the last startup was a success. If it
+  // doesn't already exist, it is created, and will be removed at the end of
+  // the startup crash detection window.
+  AutoFDClose fd;
+  Unused << crashFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE | PR_EXCL,
+                                        0666, &fd.rwget());
+  if (fd) {
+    return false;
+  }
+
+  PRTime lastModifiedTime;
+  MOZ_TRY(crashFile->GetLastModifiedTime(&lastModifiedTime));
+
+  // If the file exists, and was created within the appropriate time window,
+  // the last startup was recent and resulted in a crash.
+  PRTime now = PR_Now() / PR_USEC_PER_MSEC;
+  return now - lastModifiedTime <= MAX_TIME_SINCE_STARTUP;
+}
+
 /*
  * XRE_mainStartup - Initializes the profile and various other services.
  * Main() will exit early if either return value != 0 or if aExitFlag is
  * true.
  */
 int
 XREMain::XRE_mainStartup(bool* aExitFlag)
 {
@@ -4201,36 +4251,34 @@ XREMain::XRE_mainStartup(bool* aExitFlag
   // profile was started with.  The format of the version stamp is defined
   // by the BuildVersion function.
   // Also check to see if something has happened to invalidate our
   // fastload caches, like an extension upgrade or installation.
 
   // If we see .purgecaches, that means someone did a make.
   // Re-register components to catch potential changes.
   nsCOMPtr<nsIFile> flagFile;
-
   rv = NS_ERROR_FILE_NOT_FOUND;
-  nsCOMPtr<nsIFile> fFlagFile;
   if (mAppData->directory) {
-    rv = mAppData->directory->Clone(getter_AddRefs(fFlagFile));
-  }
-  flagFile = do_QueryInterface(fFlagFile);
+    rv = mAppData->directory->Clone(getter_AddRefs(flagFile));
+  }
   if (flagFile) {
     flagFile->AppendNative(FILE_INVALIDATE_CACHES);
   }
 
   bool cachesOK;
   bool versionOK = CheckCompatibility(mProfD, version, osABI,
                                       mDirProvider.GetGREDir(),
                                       mAppData->directory, flagFile,
                                       &cachesOK);
-  if (CheckArg("purgecaches")) {
-    cachesOK = false;
-  }
-  if (PR_GetEnv("MOZ_PURGE_CACHES")) {
+
+  bool lastStartupWasCrash = CheckLastStartupWasCrash().unwrapOr(false);
+
+  if (CheckArg("purgecaches") || PR_GetEnv("MOZ_PURGE_CACHES") ||
+      lastStartupWasCrash) {
     cachesOK = false;
   }
 
   // Every time a profile is loaded by a build with a different version,
   // it updates the compatibility.ini file saying what version last wrote
   // the fastload caches.  On subsequent launches if the version matches,
   // there is no need for re-registration.  If the user loads the same
   // profile in different builds the component registry must be
@@ -4802,16 +4850,24 @@ XREMain::XRE_main(int argc, char* argv[]
 #endif
 
   // init
   bool exit = false;
   int result = XRE_mainInit(&exit);
   if (result != 0 || exit)
     return result;
 
+  // If we exit gracefully, remove the startup crash canary file.
+  auto cleanup = MakeScopeExit([&] () -> nsresult {
+    nsCOMPtr<nsIFile> crashFile;
+    MOZ_TRY_VAR(crashFile, GetIncompleteStartupFile(mProfLD));
+    crashFile->Remove(false);
+    return NS_OK;
+  });
+
   // startup
   result = XRE_mainStartup(&exit);
   if (result != 0 || exit)
     return result;
 
   bool appInitiatedRestart = false;
 
   // Start the real application
--- a/toolkit/xre/nsAppRunner.h
+++ b/toolkit/xre/nsAppRunner.h
@@ -111,16 +111,18 @@ WinLaunchChild(const wchar_t *exePath, i
                char **argv, HANDLE userToken = nullptr,
                HANDLE *hProcess = nullptr);
 #endif
 
 #define NS_NATIVEAPPSUPPORT_CONTRACTID "@mozilla.org/toolkit/native-app-support;1"
 
 namespace mozilla {
 namespace startup {
+Result<nsCOMPtr<nsIFile>, nsresult> GetIncompleteStartupFile(nsIFile* aProfLD);
+
 extern GeckoProcessType sChildProcessType;
 } // namespace startup
 
 const char* PlatformBuildID();
 
 } // namespace mozilla
 
 /**