summaryrefslogtreecommitdiffstats
path: root/xpcom/base/AppShutdown.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/base/AppShutdown.cpp')
-rw-r--r--xpcom/base/AppShutdown.cpp469
1 files changed, 469 insertions, 0 deletions
diff --git a/xpcom/base/AppShutdown.cpp b/xpcom/base/AppShutdown.cpp
new file mode 100644
index 0000000000..c68dedef31
--- /dev/null
+++ b/xpcom/base/AppShutdown.cpp
@@ -0,0 +1,469 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "ShutdownPhase.h"
+#ifdef XP_WIN
+# include <windows.h>
+# include "mozilla/PreXULSkeletonUI.h"
+#else
+# include <unistd.h>
+#endif
+
+#include "ProfilerControl.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/PoisonIOInterposer.h"
+#include "mozilla/Printf.h"
+#include "mozilla/scache/StartupCache.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/StartupTimeline.h"
+#include "mozilla/StaticPrefs_toolkit.h"
+#include "mozilla/LateWriteChecks.h"
+#include "mozilla/Services.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsAppRunner.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsExceptionHandler.h"
+#include "nsICertStorage.h"
+#include "nsThreadUtils.h"
+
+#include "AppShutdown.h"
+
+// TODO: understand why on Android we cannot include this and if we should
+#ifndef ANDROID
+# include "nsTerminator.h"
+#endif
+#include "prenv.h"
+
+#ifdef MOZ_BACKGROUNDTASKS
+# include "mozilla/BackgroundTasks.h"
+#endif
+
+namespace mozilla {
+
+const char* sPhaseObserverKeys[] = {
+ nullptr, // NotInShutdown
+ "quit-application", // AppShutdownConfirmed
+ "profile-change-net-teardown", // AppShutdownNetTeardown
+ "profile-change-teardown", // AppShutdownTeardown
+ "profile-before-change", // AppShutdown
+ "profile-before-change-qm", // AppShutdownQM
+ "profile-before-change-telemetry", // AppShutdownTelemetry
+ "xpcom-will-shutdown", // XPCOMWillShutdown
+ "xpcom-shutdown", // XPCOMShutdown
+ "xpcom-shutdown-threads", // XPCOMShutdownThreads
+ nullptr, // XPCOMShutdownFinal
+ nullptr // CCPostLastCycleCollection
+};
+
+static_assert(sizeof(sPhaseObserverKeys) / sizeof(sPhaseObserverKeys[0]) ==
+ (size_t)ShutdownPhase::ShutdownPhase_Length);
+
+const char* sPhaseReadableNames[] = {"NotInShutdown",
+ "AppShutdownConfirmed",
+ "AppShutdownNetTeardown",
+ "AppShutdownTeardown",
+ "AppShutdown",
+ "AppShutdownQM",
+ "AppShutdownTelemetry",
+ "XPCOMWillShutdown",
+ "XPCOMShutdown",
+ "XPCOMShutdownThreads",
+ "XPCOMShutdownFinal",
+ "CCPostLastCycleCollection"};
+
+static_assert(sizeof(sPhaseReadableNames) / sizeof(sPhaseReadableNames[0]) ==
+ (size_t)ShutdownPhase::ShutdownPhase_Length);
+
+#ifndef ANDROID
+static nsTerminator* sTerminator = nullptr;
+#endif
+
+static ShutdownPhase sFastShutdownPhase = ShutdownPhase::NotInShutdown;
+static ShutdownPhase sLateWriteChecksPhase = ShutdownPhase::NotInShutdown;
+static AppShutdownMode sShutdownMode = AppShutdownMode::Normal;
+static Atomic<ShutdownPhase> sCurrentShutdownPhase(
+ ShutdownPhase::NotInShutdown);
+static int sExitCode = 0;
+
+// These environment variable strings are all deliberately copied and leaked
+// due to requirements of PR_SetEnv and similar.
+static char* sSavedXulAppFile = nullptr;
+#ifdef XP_WIN
+static wchar_t* sSavedProfDEnvVar = nullptr;
+static wchar_t* sSavedProfLDEnvVar = nullptr;
+#else
+static char* sSavedProfDEnvVar = nullptr;
+static char* sSavedProfLDEnvVar = nullptr;
+#endif
+
+ShutdownPhase GetShutdownPhaseFromPrefValue(int32_t aPrefValue) {
+ switch (aPrefValue) {
+ case 1:
+ return ShutdownPhase::CCPostLastCycleCollection;
+ case 2:
+ return ShutdownPhase::XPCOMShutdownThreads;
+ case 3:
+ return ShutdownPhase::XPCOMShutdown;
+ // NOTE: the remaining values from the ShutdownPhase enum will be added
+ // when we're at least reasonably confident that the world won't come
+ // crashing down if we do a fast shutdown at that point.
+ }
+ return ShutdownPhase::NotInShutdown;
+}
+
+ShutdownPhase AppShutdown::GetCurrentShutdownPhase() {
+ return sCurrentShutdownPhase;
+}
+
+bool AppShutdown::IsInOrBeyond(ShutdownPhase aPhase) {
+ return (sCurrentShutdownPhase >= aPhase);
+}
+
+int AppShutdown::GetExitCode() { return sExitCode; }
+
+void AppShutdown::SaveEnvVarsForPotentialRestart() {
+ const char* s = PR_GetEnv("XUL_APP_FILE");
+ if (s) {
+ sSavedXulAppFile = Smprintf("%s=%s", "XUL_APP_FILE", s).release();
+ MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedXulAppFile);
+ }
+}
+
+const char* AppShutdown::GetObserverKey(ShutdownPhase aPhase) {
+ return sPhaseObserverKeys[static_cast<std::underlying_type_t<ShutdownPhase>>(
+ aPhase)];
+}
+
+const char* AppShutdown::GetShutdownPhaseName(ShutdownPhase aPhase) {
+ return sPhaseReadableNames[static_cast<std::underlying_type_t<ShutdownPhase>>(
+ aPhase)];
+}
+
+void AppShutdown::MaybeDoRestart() {
+ if (sShutdownMode == AppShutdownMode::Restart) {
+ StopLateWriteChecks();
+
+ // Since we'll be launching our child while we're still alive, make sure
+ // we've unlocked the profile first, otherwise the child could hit its
+ // profile lock check before we've exited and thus released our lock.
+ UnlockProfile();
+
+ if (sSavedXulAppFile) {
+ PR_SetEnv(sSavedXulAppFile);
+ }
+
+#ifdef XP_WIN
+ if (sSavedProfDEnvVar && !EnvHasValue("XRE_PROFILE_PATH")) {
+ SetEnvironmentVariableW(L"XRE_PROFILE_PATH", sSavedProfDEnvVar);
+ }
+ if (sSavedProfLDEnvVar && !EnvHasValue("XRE_PROFILE_LOCAL_PATH")) {
+ SetEnvironmentVariableW(L"XRE_PROFILE_LOCAL_PATH", sSavedProfLDEnvVar);
+ }
+ Unused << NotePreXULSkeletonUIRestarting();
+#else
+ if (sSavedProfDEnvVar && !EnvHasValue("XRE_PROFILE_PATH")) {
+ PR_SetEnv(sSavedProfDEnvVar);
+ }
+ if (sSavedProfLDEnvVar && !EnvHasValue("XRE_PROFILE_LOCAL_PATH")) {
+ PR_SetEnv(sSavedProfLDEnvVar);
+ }
+#endif
+
+ LaunchChild(true);
+ }
+}
+
+#ifdef XP_WIN
+wchar_t* CopyPathIntoNewWCString(nsIFile* aFile) {
+ wchar_t* result = nullptr;
+ nsAutoString resStr;
+ aFile->GetPath(resStr);
+ if (resStr.Length() > 0) {
+ result = (wchar_t*)malloc((resStr.Length() + 1) * sizeof(wchar_t));
+ if (result) {
+ wcscpy(result, resStr.get());
+ result[resStr.Length()] = 0;
+ }
+ }
+
+ return result;
+}
+#endif
+
+void AppShutdown::Init(AppShutdownMode aMode, int aExitCode,
+ AppShutdownReason aReason) {
+ if (sShutdownMode == AppShutdownMode::Normal) {
+ sShutdownMode = aMode;
+ }
+ AppShutdown::AnnotateShutdownReason(aReason);
+
+ sExitCode = aExitCode;
+
+#ifndef ANDROID
+ sTerminator = new nsTerminator();
+#endif
+
+ // Late-write checks needs to find the profile directory, so it has to
+ // be initialized before services::Shutdown or (because of
+ // xpcshell tests replacing the service) modules being unloaded.
+ InitLateWriteChecks();
+
+ int32_t fastShutdownPref = StaticPrefs::toolkit_shutdown_fastShutdownStage();
+ sFastShutdownPhase = GetShutdownPhaseFromPrefValue(fastShutdownPref);
+ int32_t lateWriteChecksPref =
+ StaticPrefs::toolkit_shutdown_lateWriteChecksStage();
+ sLateWriteChecksPhase = GetShutdownPhaseFromPrefValue(lateWriteChecksPref);
+
+ // Very early shutdowns can happen before the startup cache is even
+ // initialized; don't bother initializing it during shutdown.
+ if (auto* cache = scache::StartupCache::GetSingletonNoInit()) {
+ cache->MaybeInitShutdownWrite();
+ }
+}
+
+void AppShutdown::MaybeFastShutdown(ShutdownPhase aPhase) {
+ // For writes which we want to ensure are recorded, we don't want to trip
+ // the late write checking code. Anything that writes to disk and which
+ // we don't want to skip should be listed out explicitly in this section.
+ if (aPhase == sFastShutdownPhase || aPhase == sLateWriteChecksPhase) {
+ if (auto* cache = scache::StartupCache::GetSingletonNoInit()) {
+ cache->EnsureShutdownWriteComplete();
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsICertStorage> certStorage =
+ do_GetService("@mozilla.org/security/certstorage;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ SpinEventLoopUntil("AppShutdown::MaybeFastShutdown"_ns, [&]() {
+ int32_t remainingOps;
+ nsresult rv = certStorage->GetRemainingOperationCount(&remainingOps);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "nsICertStorage::getRemainingOperationCount failed during "
+ "shutdown");
+ return NS_FAILED(rv) || remainingOps <= 0;
+ });
+ }
+ }
+ if (aPhase == sFastShutdownPhase) {
+ StopLateWriteChecks();
+ RecordShutdownEndTimeStamp();
+ MaybeDoRestart();
+
+ profiler_shutdown(IsFastShutdown::Yes);
+
+ DoImmediateExit(sExitCode);
+ } else if (aPhase == sLateWriteChecksPhase) {
+#ifdef XP_MACOSX
+ OnlyReportDirtyWrites();
+#endif /* XP_MACOSX */
+ BeginLateWriteChecks();
+ }
+}
+
+void AppShutdown::OnShutdownConfirmed() {
+ // If we're restarting, we need to save environment variables correctly
+ // while everything is still alive to do so.
+ if (sShutdownMode == AppShutdownMode::Restart) {
+ nsCOMPtr<nsIFile> profD;
+ nsCOMPtr<nsIFile> profLD;
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profD));
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
+ getter_AddRefs(profLD));
+#ifdef XP_WIN
+ sSavedProfDEnvVar = CopyPathIntoNewWCString(profD);
+ sSavedProfLDEnvVar = CopyPathIntoNewWCString(profLD);
+#else
+ nsAutoCString profDStr;
+ profD->GetNativePath(profDStr);
+ sSavedProfDEnvVar =
+ Smprintf("XRE_PROFILE_PATH=%s", profDStr.get()).release();
+ nsAutoCString profLDStr;
+ profLD->GetNativePath(profLDStr);
+ sSavedProfLDEnvVar =
+ Smprintf("XRE_PROFILE_LOCAL_PATH=%s", profLDStr.get()).release();
+#endif
+ MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedProfDEnvVar);
+ MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedProfLDEnvVar);
+ }
+}
+
+void AppShutdown::DoImmediateExit(int aExitCode) {
+#ifdef XP_WIN
+ HANDLE process = ::GetCurrentProcess();
+ if (::TerminateProcess(process, aExitCode)) {
+ ::WaitForSingleObject(process, INFINITE);
+ }
+ MOZ_CRASH("TerminateProcess failed.");
+#else
+ _exit(aExitCode);
+#endif
+}
+
+bool AppShutdown::IsRestarting() {
+ return sShutdownMode == AppShutdownMode::Restart;
+}
+
+void AppShutdown::AnnotateShutdownReason(AppShutdownReason aReason) {
+ auto key = CrashReporter::Annotation::ShutdownReason;
+ nsCString reasonStr;
+ switch (aReason) {
+ case AppShutdownReason::AppClose:
+ reasonStr = "AppClose"_ns;
+ break;
+ case AppShutdownReason::AppRestart:
+ reasonStr = "AppRestart"_ns;
+ break;
+ case AppShutdownReason::OSForceClose:
+ reasonStr = "OSForceClose"_ns;
+ break;
+ case AppShutdownReason::OSSessionEnd:
+ reasonStr = "OSSessionEnd"_ns;
+ break;
+ case AppShutdownReason::OSShutdown:
+ reasonStr = "OSShutdown"_ns;
+ break;
+ case AppShutdownReason::WinUnexpectedMozQuit:
+ reasonStr = "WinUnexpectedMozQuit"_ns;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("We should know the given reason for shutdown.");
+ reasonStr = "Unknown"_ns;
+ break;
+ }
+ CrashReporter::AnnotateCrashReport(key, reasonStr);
+}
+
+#ifdef DEBUG
+static bool sNotifyingShutdownObservers = false;
+static bool sAdvancingShutdownPhase = false;
+
+bool AppShutdown::IsNoOrLegalShutdownTopic(const char* aTopic) {
+ if (!XRE_IsParentProcess()) {
+ // Until we know what to do with AppShutdown for child processes,
+ // we ignore them for now. See bug 1697745.
+ return true;
+ }
+ ShutdownPhase phase = GetShutdownPhaseFromTopic(aTopic);
+ return phase == ShutdownPhase::NotInShutdown ||
+ (sNotifyingShutdownObservers && phase == sCurrentShutdownPhase);
+}
+#endif
+
+void AppShutdown::AdvanceShutdownPhaseInternal(
+ ShutdownPhase aPhase, bool doNotify, const char16_t* aNotificationData,
+ const nsCOMPtr<nsISupports>& aNotificationSubject) {
+ AssertIsOnMainThread();
+#ifdef DEBUG
+ // Prevent us from re-entrance
+ MOZ_ASSERT(!sAdvancingShutdownPhase);
+ sAdvancingShutdownPhase = true;
+ auto exit = MakeScopeExit([] { sAdvancingShutdownPhase = false; });
+#endif
+
+ // We ensure that we can move only forward. We cannot
+ // MOZ_ASSERT here as there are some tests that fire
+ // notifications out of shutdown order.
+ // See for example test_sss_sanitizeOnShutdown.js
+ if (sCurrentShutdownPhase >= aPhase) {
+ return;
+ }
+
+ nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+
+ // AppShutdownConfirmed is special in some ways as
+ // - we can be called on top of a nested event loop (and it is the phase for
+ // which SpinEventLoopUntilOrQuit breaks, so we want to return soon)
+ // - we can be called from a sync marionette function that wants immediate
+ // feedback, too
+ // - in general, AppShutdownConfirmed will fire the "quit-application"
+ // notification which in turn will cause an event to be dispatched that
+ // runs all the rest of our shutdown sequence which we do not want to be
+ // processed on top of the running event.
+ // Thus we never do any NS_ProcessPendingEvents for it.
+ bool mayProcessPending = (aPhase > ShutdownPhase::AppShutdownConfirmed);
+
+ // Give runnables dispatched between two calls to AdvanceShutdownPhase
+ // a chance to run before actually advancing the phase. As we excluded
+ // AppShutdownConfirmed above we can be sure that the processing is
+ // covered by the terminator's timer of the previous phase during normal
+ // shutdown (except out-of-order calls from some test).
+ // Note that this affects only main thread runnables, such that the correct
+ // way of ensuring shutdown processing remains to have an async shutdown
+ // blocker.
+ if (mayProcessPending && thread) {
+ NS_ProcessPendingEvents(thread);
+ }
+
+ // From now on any IsInOrBeyond checks will find the new phase set.
+ sCurrentShutdownPhase = aPhase;
+
+#ifndef ANDROID
+ if (sTerminator) {
+ sTerminator->AdvancePhase(aPhase);
+ }
+#endif
+
+ AppShutdown::MaybeFastShutdown(aPhase);
+
+ // This will null out the gathered pointers for this phase synchronously.
+ // Note that we keep the old order here to avoid breakage, so be aware that
+ // the notifications fired below will find these already cleared in case
+ // you expected the opposite.
+ mozilla::KillClearOnShutdown(aPhase);
+
+ // Empty our MT event queue to process any side effects thereof.
+ if (mayProcessPending && thread) {
+ NS_ProcessPendingEvents(thread);
+ }
+
+ if (doNotify) {
+ const char* aTopic = AppShutdown::GetObserverKey(aPhase);
+ if (aTopic) {
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ if (obsService) {
+#ifdef DEBUG
+ sNotifyingShutdownObservers = true;
+ auto reset = MakeScopeExit([] { sNotifyingShutdownObservers = false; });
+#endif
+ obsService->NotifyObservers(aNotificationSubject, aTopic,
+ aNotificationData);
+ // Empty our MT event queue again after the notification has finished
+ if (mayProcessPending && thread) {
+ NS_ProcessPendingEvents(thread);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * XXX: Before tackling bug 1697745 we need the
+ * possibility to advance the phase without notification
+ * in the content process.
+ */
+void AppShutdown::AdvanceShutdownPhaseWithoutNotify(ShutdownPhase aPhase) {
+ AdvanceShutdownPhaseInternal(aPhase, /* doNotify */ false, nullptr, nullptr);
+}
+
+void AppShutdown::AdvanceShutdownPhase(
+ ShutdownPhase aPhase, const char16_t* aNotificationData,
+ const nsCOMPtr<nsISupports>& aNotificationSubject) {
+ AdvanceShutdownPhaseInternal(aPhase, /* doNotify */ true, aNotificationData,
+ aNotificationSubject);
+}
+
+ShutdownPhase AppShutdown::GetShutdownPhaseFromTopic(const char* aTopic) {
+ for (size_t i = 0; i < ArrayLength(sPhaseObserverKeys); ++i) {
+ if (sPhaseObserverKeys[i] && !strcmp(sPhaseObserverKeys[i], aTopic)) {
+ return static_cast<ShutdownPhase>(i);
+ }
+ }
+ return ShutdownPhase::NotInShutdown;
+}
+
+} // namespace mozilla