Bug 1910362 - Create new Nimbus helper r=aaronmt,ohorvath
[gecko.git] / xpcom / base / AppShutdown.cpp
blob9ed3ce0ba36e481e0a98ee81d1ec339f14aac264
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ShutdownPhase.h"
8 #ifdef XP_WIN
9 # include <windows.h>
10 # include "mozilla/PreXULSkeletonUI.h"
11 #else
12 # include <unistd.h>
13 #endif
15 #include "ProfilerControl.h"
16 #include "mozilla/ClearOnShutdown.h"
17 #include "mozilla/CmdLineAndEnvUtils.h"
18 #include "mozilla/PoisonIOInterposer.h"
19 #include "mozilla/Printf.h"
20 #include "mozilla/scache/StartupCache.h"
21 #include "mozilla/SpinEventLoopUntil.h"
22 #include "mozilla/StartupTimeline.h"
23 #include "mozilla/StaticPrefs_toolkit.h"
24 #include "mozilla/LateWriteChecks.h"
25 #include "mozilla/Services.h"
26 #include "nsAppDirectoryServiceDefs.h"
27 #include "nsAppRunner.h"
28 #include "nsDirectoryServiceUtils.h"
29 #include "nsExceptionHandler.h"
30 #include "nsICertStorage.h"
31 #include "nsThreadUtils.h"
33 #include "AppShutdown.h"
35 // TODO: understand why on Android we cannot include this and if we should
36 #ifndef ANDROID
37 # include "nsTerminator.h"
38 #endif
39 #include "prenv.h"
41 #ifdef MOZ_BACKGROUNDTASKS
42 # include "mozilla/BackgroundTasks.h"
43 #endif
45 namespace mozilla {
47 const char* sPhaseObserverKeys[] = {
48 nullptr, // NotInShutdown
49 "quit-application", // AppShutdownConfirmed
50 "profile-change-net-teardown", // AppShutdownNetTeardown
51 "profile-change-teardown", // AppShutdownTeardown
52 "profile-before-change", // AppShutdown
53 "profile-before-change-qm", // AppShutdownQM
54 "profile-before-change-telemetry", // AppShutdownTelemetry
55 "xpcom-will-shutdown", // XPCOMWillShutdown
56 "xpcom-shutdown", // XPCOMShutdown
57 "xpcom-shutdown-threads", // XPCOMShutdownThreads
58 nullptr, // XPCOMShutdownFinal
59 nullptr // CCPostLastCycleCollection
62 static_assert(sizeof(sPhaseObserverKeys) / sizeof(sPhaseObserverKeys[0]) ==
63 (size_t)ShutdownPhase::ShutdownPhase_Length);
65 const char* sPhaseReadableNames[] = {"NotInShutdown",
66 "AppShutdownConfirmed",
67 "AppShutdownNetTeardown",
68 "AppShutdownTeardown",
69 "AppShutdown",
70 "AppShutdownQM",
71 "AppShutdownTelemetry",
72 "XPCOMWillShutdown",
73 "XPCOMShutdown",
74 "XPCOMShutdownThreads",
75 "XPCOMShutdownFinal",
76 "CCPostLastCycleCollection"};
78 static_assert(sizeof(sPhaseReadableNames) / sizeof(sPhaseReadableNames[0]) ==
79 (size_t)ShutdownPhase::ShutdownPhase_Length);
81 #ifndef ANDROID
82 static nsTerminator* sTerminator = nullptr;
83 #endif
85 static ShutdownPhase sFastShutdownPhase = ShutdownPhase::NotInShutdown;
86 static ShutdownPhase sLateWriteChecksPhase = ShutdownPhase::NotInShutdown;
87 static AppShutdownMode sShutdownMode = AppShutdownMode::Normal;
88 static Atomic<ShutdownPhase> sCurrentShutdownPhase(
89 ShutdownPhase::NotInShutdown);
90 static int sExitCode = 0;
92 // These environment variable strings are all deliberately copied and leaked
93 // due to requirements of PR_SetEnv and similar.
94 static char* sSavedXulAppFile = nullptr;
95 #ifdef XP_WIN
96 static wchar_t* sSavedProfDEnvVar = nullptr;
97 static wchar_t* sSavedProfLDEnvVar = nullptr;
98 #else
99 static char* sSavedProfDEnvVar = nullptr;
100 static char* sSavedProfLDEnvVar = nullptr;
101 #endif
103 ShutdownPhase GetShutdownPhaseFromPrefValue(int32_t aPrefValue) {
104 switch (aPrefValue) {
105 case 1:
106 return ShutdownPhase::CCPostLastCycleCollection;
107 case 2:
108 return ShutdownPhase::XPCOMShutdownThreads;
109 case 3:
110 return ShutdownPhase::XPCOMShutdown;
111 // NOTE: the remaining values from the ShutdownPhase enum will be added
112 // when we're at least reasonably confident that the world won't come
113 // crashing down if we do a fast shutdown at that point.
115 return ShutdownPhase::NotInShutdown;
118 ShutdownPhase AppShutdown::GetCurrentShutdownPhase() {
119 return sCurrentShutdownPhase;
122 bool AppShutdown::IsInOrBeyond(ShutdownPhase aPhase) {
123 return (sCurrentShutdownPhase >= aPhase);
126 int AppShutdown::GetExitCode() { return sExitCode; }
128 void AppShutdown::SaveEnvVarsForPotentialRestart() {
129 const char* s = PR_GetEnv("XUL_APP_FILE");
130 if (s) {
131 sSavedXulAppFile = Smprintf("%s=%s", "XUL_APP_FILE", s).release();
132 MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedXulAppFile);
136 const char* AppShutdown::GetObserverKey(ShutdownPhase aPhase) {
137 return sPhaseObserverKeys[static_cast<std::underlying_type_t<ShutdownPhase>>(
138 aPhase)];
141 const char* AppShutdown::GetShutdownPhaseName(ShutdownPhase aPhase) {
142 return sPhaseReadableNames[static_cast<std::underlying_type_t<ShutdownPhase>>(
143 aPhase)];
146 void AppShutdown::MaybeDoRestart() {
147 if (sShutdownMode == AppShutdownMode::Restart) {
148 StopLateWriteChecks();
150 // Since we'll be launching our child while we're still alive, make sure
151 // we've unlocked the profile first, otherwise the child could hit its
152 // profile lock check before we've exited and thus released our lock.
153 UnlockProfile();
155 if (sSavedXulAppFile) {
156 PR_SetEnv(sSavedXulAppFile);
159 #ifdef XP_WIN
160 if (sSavedProfDEnvVar && !EnvHasValue("XRE_PROFILE_PATH")) {
161 SetEnvironmentVariableW(L"XRE_PROFILE_PATH", sSavedProfDEnvVar);
163 if (sSavedProfLDEnvVar && !EnvHasValue("XRE_PROFILE_LOCAL_PATH")) {
164 SetEnvironmentVariableW(L"XRE_PROFILE_LOCAL_PATH", sSavedProfLDEnvVar);
166 Unused << NotePreXULSkeletonUIRestarting();
167 #else
168 if (sSavedProfDEnvVar && !EnvHasValue("XRE_PROFILE_PATH")) {
169 PR_SetEnv(sSavedProfDEnvVar);
171 if (sSavedProfLDEnvVar && !EnvHasValue("XRE_PROFILE_LOCAL_PATH")) {
172 PR_SetEnv(sSavedProfLDEnvVar);
174 #endif
176 LaunchChild(true);
180 #ifdef XP_WIN
181 wchar_t* CopyPathIntoNewWCString(nsIFile* aFile) {
182 wchar_t* result = nullptr;
183 nsAutoString resStr;
184 aFile->GetPath(resStr);
185 if (resStr.Length() > 0) {
186 result = (wchar_t*)malloc((resStr.Length() + 1) * sizeof(wchar_t));
187 if (result) {
188 wcscpy(result, resStr.get());
189 result[resStr.Length()] = 0;
193 return result;
195 #endif
197 void AppShutdown::Init(AppShutdownMode aMode, int aExitCode,
198 AppShutdownReason aReason) {
199 if (sShutdownMode == AppShutdownMode::Normal) {
200 sShutdownMode = aMode;
202 AppShutdown::AnnotateShutdownReason(aReason);
204 sExitCode = aExitCode;
206 #ifndef ANDROID
207 sTerminator = new nsTerminator();
208 #endif
210 // Late-write checks needs to find the profile directory, so it has to
211 // be initialized before services::Shutdown or (because of
212 // xpcshell tests replacing the service) modules being unloaded.
213 InitLateWriteChecks();
215 int32_t fastShutdownPref = StaticPrefs::toolkit_shutdown_fastShutdownStage();
216 sFastShutdownPhase = GetShutdownPhaseFromPrefValue(fastShutdownPref);
217 int32_t lateWriteChecksPref =
218 StaticPrefs::toolkit_shutdown_lateWriteChecksStage();
219 sLateWriteChecksPhase = GetShutdownPhaseFromPrefValue(lateWriteChecksPref);
221 // Very early shutdowns can happen before the startup cache is even
222 // initialized; don't bother initializing it during shutdown.
223 if (auto* cache = scache::StartupCache::GetSingletonNoInit()) {
224 cache->MaybeInitShutdownWrite();
228 void AppShutdown::MaybeFastShutdown(ShutdownPhase aPhase) {
229 // For writes which we want to ensure are recorded, we don't want to trip
230 // the late write checking code. Anything that writes to disk and which
231 // we don't want to skip should be listed out explicitly in this section.
232 if (aPhase == sFastShutdownPhase || aPhase == sLateWriteChecksPhase) {
233 if (auto* cache = scache::StartupCache::GetSingletonNoInit()) {
234 cache->EnsureShutdownWriteComplete();
237 nsresult rv;
238 nsCOMPtr<nsICertStorage> certStorage =
239 do_GetService("@mozilla.org/security/certstorage;1", &rv);
240 if (NS_SUCCEEDED(rv)) {
241 SpinEventLoopUntil("AppShutdown::MaybeFastShutdown"_ns, [&]() {
242 int32_t remainingOps;
243 nsresult rv = certStorage->GetRemainingOperationCount(&remainingOps);
244 NS_ASSERTION(NS_SUCCEEDED(rv),
245 "nsICertStorage::getRemainingOperationCount failed during "
246 "shutdown");
247 return NS_FAILED(rv) || remainingOps <= 0;
251 if (aPhase == sFastShutdownPhase) {
252 StopLateWriteChecks();
253 RecordShutdownEndTimeStamp();
254 MaybeDoRestart();
256 profiler_shutdown(IsFastShutdown::Yes);
258 DoImmediateExit(sExitCode);
259 } else if (aPhase == sLateWriteChecksPhase) {
260 #ifdef XP_MACOSX
261 OnlyReportDirtyWrites();
262 #endif /* XP_MACOSX */
263 BeginLateWriteChecks();
267 void AppShutdown::OnShutdownConfirmed() {
268 // If we're restarting, we need to save environment variables correctly
269 // while everything is still alive to do so.
270 if (sShutdownMode == AppShutdownMode::Restart) {
271 nsCOMPtr<nsIFile> profD;
272 nsCOMPtr<nsIFile> profLD;
273 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profD));
274 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
275 getter_AddRefs(profLD));
276 #ifdef XP_WIN
277 sSavedProfDEnvVar = CopyPathIntoNewWCString(profD);
278 sSavedProfLDEnvVar = CopyPathIntoNewWCString(profLD);
279 #else
280 nsAutoCString profDStr;
281 profD->GetNativePath(profDStr);
282 sSavedProfDEnvVar =
283 Smprintf("XRE_PROFILE_PATH=%s", profDStr.get()).release();
284 nsAutoCString profLDStr;
285 profLD->GetNativePath(profLDStr);
286 sSavedProfLDEnvVar =
287 Smprintf("XRE_PROFILE_LOCAL_PATH=%s", profLDStr.get()).release();
288 #endif
289 MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedProfDEnvVar);
290 MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedProfLDEnvVar);
294 void AppShutdown::DoImmediateExit(int aExitCode) {
295 #ifdef XP_WIN
296 HANDLE process = ::GetCurrentProcess();
297 if (::TerminateProcess(process, aExitCode)) {
298 ::WaitForSingleObject(process, INFINITE);
300 MOZ_CRASH("TerminateProcess failed.");
301 #else
302 _exit(aExitCode);
303 #endif
306 bool AppShutdown::IsRestarting() {
307 return sShutdownMode == AppShutdownMode::Restart;
310 void AppShutdown::AnnotateShutdownReason(AppShutdownReason aReason) {
311 auto key = CrashReporter::Annotation::ShutdownReason;
312 const char* reasonStr;
313 switch (aReason) {
314 case AppShutdownReason::AppClose:
315 reasonStr = "AppClose";
316 break;
317 case AppShutdownReason::AppRestart:
318 reasonStr = "AppRestart";
319 break;
320 case AppShutdownReason::OSForceClose:
321 reasonStr = "OSForceClose";
322 break;
323 case AppShutdownReason::OSSessionEnd:
324 reasonStr = "OSSessionEnd";
325 break;
326 case AppShutdownReason::OSShutdown:
327 reasonStr = "OSShutdown";
328 break;
329 default:
330 MOZ_ASSERT_UNREACHABLE("We should know the given reason for shutdown.");
331 reasonStr = "Unknown";
332 break;
334 CrashReporter::RecordAnnotationCString(key, reasonStr);
337 #ifdef DEBUG
338 static bool sNotifyingShutdownObservers = false;
339 static bool sAdvancingShutdownPhase = false;
341 bool AppShutdown::IsNoOrLegalShutdownTopic(const char* aTopic) {
342 if (!XRE_IsParentProcess()) {
343 // Until we know what to do with AppShutdown for child processes,
344 // we ignore them for now. See bug 1697745.
345 return true;
347 ShutdownPhase phase = GetShutdownPhaseFromTopic(aTopic);
348 return phase == ShutdownPhase::NotInShutdown ||
349 (sNotifyingShutdownObservers && phase == sCurrentShutdownPhase);
351 #endif
353 void AppShutdown::AdvanceShutdownPhaseInternal(
354 ShutdownPhase aPhase, bool doNotify, const char16_t* aNotificationData,
355 const nsCOMPtr<nsISupports>& aNotificationSubject) {
356 AssertIsOnMainThread();
357 #ifdef DEBUG
358 // Prevent us from re-entrance
359 MOZ_ASSERT(!sAdvancingShutdownPhase);
360 sAdvancingShutdownPhase = true;
361 auto exit = MakeScopeExit([] { sAdvancingShutdownPhase = false; });
362 #endif
364 // We ensure that we can move only forward. We cannot
365 // MOZ_ASSERT here as there are some tests that fire
366 // notifications out of shutdown order.
367 // See for example test_sss_sanitizeOnShutdown.js
368 if (sCurrentShutdownPhase >= aPhase) {
369 return;
372 nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
374 // AppShutdownConfirmed is special in some ways as
375 // - we can be called on top of a nested event loop (and it is the phase for
376 // which SpinEventLoopUntilOrQuit breaks, so we want to return soon)
377 // - we can be called from a sync marionette function that wants immediate
378 // feedback, too
379 // - in general, AppShutdownConfirmed will fire the "quit-application"
380 // notification which in turn will cause an event to be dispatched that
381 // runs all the rest of our shutdown sequence which we do not want to be
382 // processed on top of the running event.
383 // Thus we never do any NS_ProcessPendingEvents for it.
384 bool mayProcessPending = (aPhase > ShutdownPhase::AppShutdownConfirmed);
386 // Give runnables dispatched between two calls to AdvanceShutdownPhase
387 // a chance to run before actually advancing the phase. As we excluded
388 // AppShutdownConfirmed above we can be sure that the processing is
389 // covered by the terminator's timer of the previous phase during normal
390 // shutdown (except out-of-order calls from some test).
391 // Note that this affects only main thread runnables, such that the correct
392 // way of ensuring shutdown processing remains to have an async shutdown
393 // blocker.
394 if (mayProcessPending && thread) {
395 NS_ProcessPendingEvents(thread);
398 // From now on any IsInOrBeyond checks will find the new phase set.
399 sCurrentShutdownPhase = aPhase;
401 #ifndef ANDROID
402 if (sTerminator) {
403 sTerminator->AdvancePhase(aPhase);
405 #endif
407 AppShutdown::MaybeFastShutdown(aPhase);
409 // This will null out the gathered pointers for this phase synchronously.
410 // Note that we keep the old order here to avoid breakage, so be aware that
411 // the notifications fired below will find these already cleared in case
412 // you expected the opposite.
413 mozilla::KillClearOnShutdown(aPhase);
415 // Empty our MT event queue to process any side effects thereof.
416 if (mayProcessPending && thread) {
417 NS_ProcessPendingEvents(thread);
420 if (doNotify) {
421 const char* aTopic = AppShutdown::GetObserverKey(aPhase);
422 if (aTopic) {
423 nsCOMPtr<nsIObserverService> obsService =
424 mozilla::services::GetObserverService();
425 if (obsService) {
426 #ifdef DEBUG
427 sNotifyingShutdownObservers = true;
428 auto reset = MakeScopeExit([] { sNotifyingShutdownObservers = false; });
429 #endif
430 obsService->NotifyObservers(aNotificationSubject, aTopic,
431 aNotificationData);
432 // Empty our MT event queue again after the notification has finished
433 if (mayProcessPending && thread) {
434 NS_ProcessPendingEvents(thread);
442 * XXX: Before tackling bug 1697745 we need the
443 * possibility to advance the phase without notification
444 * in the content process.
446 void AppShutdown::AdvanceShutdownPhaseWithoutNotify(ShutdownPhase aPhase) {
447 AdvanceShutdownPhaseInternal(aPhase, /* doNotify */ false, nullptr, nullptr);
450 void AppShutdown::AdvanceShutdownPhase(
451 ShutdownPhase aPhase, const char16_t* aNotificationData,
452 const nsCOMPtr<nsISupports>& aNotificationSubject) {
453 AdvanceShutdownPhaseInternal(aPhase, /* doNotify */ true, aNotificationData,
454 aNotificationSubject);
457 ShutdownPhase AppShutdown::GetShutdownPhaseFromTopic(const char* aTopic) {
458 for (size_t i = 0; i < std::size(sPhaseObserverKeys); ++i) {
459 if (sPhaseObserverKeys[i] && !strcmp(sPhaseObserverKeys[i], aTopic)) {
460 return static_cast<ShutdownPhase>(i);
463 return ShutdownPhase::NotInShutdown;
466 } // namespace mozilla