зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1689953: Harmonize shutdown phase definitions across nsTerminator and AppShutdown r=dthayer,chutten
This patch wants to solve several quirks around the shutdown terminator. - Use the same shutdown phase definitions in AppShutdown and nsTerminator. This touches quite a few files. - Ensure that the terminator phase shift is handled before any shutdown observer notifications are sent and reduce its heartbeat duration. - Add missing phases to the shutdown telemetry. Please note that this changes the unit of "tick" to 100ms rather than 1s. As a side effect, we also remove the obsolete "shutdown-persist" context. While the existing test coverage continues to prove the most important functions, we acknowledge the wish for better test coverage with [[ https://bugzilla.mozilla.org/show_bug.cgi?id=1693966 | bug 1693966 ]]. Differential Revision: https://phabricator.services.mozilla.com/D103626
This commit is contained in:
Родитель
d270fbf50c
Коммит
f6d52040b9
|
@ -22,7 +22,7 @@ DocAccessibleChild::DocAccessibleChild(DocAccessible* aDoc, IProtocol* aManager)
|
|||
MOZ_COUNT_CTOR_INHERITED(DocAccessibleChild, DocAccessibleChildBase);
|
||||
if (!sPlatformChild) {
|
||||
sPlatformChild = new PlatformChild();
|
||||
ClearOnShutdown(&sPlatformChild, ShutdownPhase::Shutdown);
|
||||
ClearOnShutdown(&sPlatformChild, ShutdownPhase::XPCOMShutdown);
|
||||
}
|
||||
|
||||
SetManager(aManager);
|
||||
|
|
|
@ -28,7 +28,7 @@ async function fake_profile_change() {
|
|||
}, "cookie-db-closed");
|
||||
Services.cookies
|
||||
.QueryInterface(Ci.nsIObserver)
|
||||
.observe(null, "profile-before-change", "shutdown-persist");
|
||||
.observe(null, "profile-before-change", null);
|
||||
});
|
||||
await new Promise(resolve => {
|
||||
Services.obs.addObserver(function waitForDBOpen() {
|
||||
|
|
|
@ -575,7 +575,7 @@ ContentChild::ContentChild()
|
|||
// happens without requiring the observer service at this time.
|
||||
if (!sShutdownCanary) {
|
||||
sShutdownCanary = new ShutdownCanary();
|
||||
ClearOnShutdown(&sShutdownCanary, ShutdownPhase::Shutdown);
|
||||
ClearOnShutdown(&sShutdownCanary, ShutdownPhase::XPCOMShutdown);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2839,7 +2839,7 @@ bool ContentParent::InitInternal(ProcessPriority aInitialPriority) {
|
|||
// can't init the process without it, and since we're going to be canceling
|
||||
// whatever load attempt that initiated this process creation anyway, just
|
||||
// bail out now if shutdown has already started.
|
||||
if (PastShutdownPhase(ShutdownPhase::Shutdown)) {
|
||||
if (PastShutdownPhase(ShutdownPhase::XPCOMShutdown)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -773,7 +773,7 @@ RefPtr<MediaCache> MediaCache::GetMediaCache(int64_t aContentLength,
|
|||
thread->Shutdown();
|
||||
}
|
||||
} sClearThread;
|
||||
ClearOnShutdown(&sClearThread, ShutdownPhase::ShutdownThreads);
|
||||
ClearOnShutdown(&sClearThread, ShutdownPhase::XPCOMShutdownThreads);
|
||||
}
|
||||
|
||||
if (!sThread) {
|
||||
|
|
|
@ -116,9 +116,11 @@ bool DecoderDoctorLogger::EnsureLogIsEnabled() {
|
|||
TaskCategory::Other,
|
||||
NS_NewRunnableFunction("DDLogger shutdown setup", [] {
|
||||
sDDLogShutdowner = MakeUnique<DDLogShutdowner>();
|
||||
ClearOnShutdown(&sDDLogShutdowner, ShutdownPhase::Shutdown);
|
||||
ClearOnShutdown(&sDDLogShutdowner,
|
||||
ShutdownPhase::XPCOMShutdown);
|
||||
sDDLogDeleter = MakeUnique<DDLogDeleter>();
|
||||
ClearOnShutdown(&sDDLogDeleter, ShutdownPhase::ShutdownThreads);
|
||||
ClearOnShutdown(&sDDLogDeleter,
|
||||
ShutdownPhase::XPCOMShutdownThreads);
|
||||
})));
|
||||
|
||||
// Nobody else should change the state when *we* are enabling logging.
|
||||
|
|
|
@ -257,7 +257,7 @@ already_AddRefed<Promise> MediaCapabilities::DecodingInfo(
|
|||
NS_NewRunnableFunction(
|
||||
"MediaCapabilities::AllocPolicy:Video", []() {
|
||||
ClearOnShutdown(&sVideoAllocPolicy,
|
||||
ShutdownPhase::ShutdownThreads);
|
||||
ShutdownPhase::XPCOMShutdownThreads);
|
||||
}));
|
||||
return new SingleAllocPolicy(TrackInfo::TrackType::kVideoTrack,
|
||||
taskQueue);
|
||||
|
|
|
@ -97,7 +97,8 @@ NotNull<AllocPolicy*> GlobalAllocPolicy::Instance(TrackType aTrack) {
|
|||
TaskCategory::Other,
|
||||
NS_NewRunnableFunction(
|
||||
"GlobalAllocPolicy::GlobalAllocPolicy:Audio", []() {
|
||||
ClearOnShutdown(&sAudioPolicy, ShutdownPhase::ShutdownThreads);
|
||||
ClearOnShutdown(&sAudioPolicy,
|
||||
ShutdownPhase::XPCOMShutdownThreads);
|
||||
}));
|
||||
return new AllocPolicyImpl(MediaDecoderLimitDefault());
|
||||
}();
|
||||
|
@ -108,7 +109,8 @@ NotNull<AllocPolicy*> GlobalAllocPolicy::Instance(TrackType aTrack) {
|
|||
TaskCategory::Other,
|
||||
NS_NewRunnableFunction(
|
||||
"GlobalAllocPolicy::GlobalAllocPolicy:Audio", []() {
|
||||
ClearOnShutdown(&sVideoPolicy, ShutdownPhase::ShutdownThreads);
|
||||
ClearOnShutdown(&sVideoPolicy,
|
||||
ShutdownPhase::XPCOMShutdownThreads);
|
||||
}));
|
||||
return new AllocPolicyImpl(MediaDecoderLimitDefault());
|
||||
}();
|
||||
|
|
|
@ -30,7 +30,7 @@ CubebDeviceEnumerator* CubebDeviceEnumerator::GetInstance() {
|
|||
sInstance = new CubebDeviceEnumerator();
|
||||
static bool clearOnShutdownSetup = []() -> bool {
|
||||
auto setClearOnShutdown = []() -> void {
|
||||
ClearOnShutdown(&sInstance, ShutdownPhase::ShutdownThreads);
|
||||
ClearOnShutdown(&sInstance, ShutdownPhase::XPCOMShutdownThreads);
|
||||
};
|
||||
if (NS_IsMainThread()) {
|
||||
setClearOnShutdown();
|
||||
|
|
|
@ -241,7 +241,7 @@ class SingletonThreadHolder final {
|
|||
static StaticRefPtr<SingletonThreadHolder> sThread;
|
||||
|
||||
static void ClearSingletonOnShutdown() {
|
||||
ClearOnShutdown(&sThread, ShutdownPhase::ShutdownLoaders);
|
||||
ClearOnShutdown(&sThread, ShutdownPhase::XPCOMShutdownLoaders);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -176,7 +176,7 @@ void PluginProcessChild::CleanUp() {
|
|||
NS_LogTerm();
|
||||
#endif
|
||||
|
||||
mozilla::KillClearOnShutdown(ShutdownPhase::ShutdownFinal);
|
||||
mozilla::KillClearOnShutdown(ShutdownPhase::XPCOMShutdownFinal);
|
||||
|
||||
AbstractThread::ShutdownMainThread();
|
||||
|
||||
|
|
|
@ -625,7 +625,7 @@ BackgroundSessionStorageManager* BackgroundSessionStorageManager::GetOrCreate(
|
|||
return;
|
||||
}
|
||||
},
|
||||
ShutdownPhase::Shutdown);
|
||||
ShutdownPhase::XPCOMShutdown);
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ class U2FPrefManager final : public nsIObserver {
|
|||
PREF_WEBAUTHN_ANDROID_FIDO2_ENABLED);
|
||||
Preferences::AddStrongObserver(gPrefManager,
|
||||
PREF_WEBAUTHN_ALLOW_DIRECT_ATTESTATION);
|
||||
ClearOnShutdown(&gPrefManager, ShutdownPhase::ShutdownThreads);
|
||||
ClearOnShutdown(&gPrefManager, ShutdownPhase::XPCOMShutdownThreads);
|
||||
}
|
||||
return gPrefManager;
|
||||
}
|
||||
|
|
|
@ -161,7 +161,7 @@ VRManager::VRManager()
|
|||
VRServiceHost::Init(mVRProcessEnabled);
|
||||
mServiceHost = VRServiceHost::Get();
|
||||
// We must shutdown before VRServiceHost, which is cleared
|
||||
// on ShutdownPhase::ShutdownFinal, potentially before VRManager.
|
||||
// on ShutdownPhase::XPCOMShutdownFinal, potentially before VRManager.
|
||||
// We hold a reference to VRServiceHost to ensure it stays
|
||||
// alive until we have shut down.
|
||||
#else
|
||||
|
|
|
@ -240,7 +240,8 @@ void VRServiceHost::PuppetReset() {
|
|||
// If we're already into ShutdownFinal, the VRPuppetCommandBuffer instance
|
||||
// will have been cleared, so don't try to access it after that point.
|
||||
if (!mVRProcessEnabled &&
|
||||
!(NS_IsMainThread() && PastShutdownPhase(ShutdownPhase::ShutdownFinal))) {
|
||||
!(NS_IsMainThread() &&
|
||||
PastShutdownPhase(ShutdownPhase::XPCOMShutdownFinal))) {
|
||||
// Puppet is running in this process, tell it to reset directly.
|
||||
VRPuppetCommandBuffer::Get().Reset();
|
||||
}
|
||||
|
|
|
@ -152,7 +152,7 @@ LocaleService* LocaleService::GetInstance() {
|
|||
}
|
||||
// DOM might use ICUUtils and LocaleService during UnbindFromTree by
|
||||
// final cycle collection.
|
||||
ClearOnShutdown(&sInstance, ShutdownPhase::ShutdownPostLastCycleCollection);
|
||||
ClearOnShutdown(&sInstance, ShutdownPhase::CCPostLastCycleCollection);
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
|
|
@ -472,7 +472,7 @@ nsresult nsStringBundleBase::ParseProperties(nsIPersistentProperties** aProps) {
|
|||
nsresult nsStringBundle::LoadProperties() {
|
||||
// Something such as Necko might use string bundle after ClearOnShutdown is
|
||||
// called. LocaleService etc is already down, so we cannot get bundle data.
|
||||
if (PastShutdownPhase(ShutdownPhase::Shutdown)) {
|
||||
if (PastShutdownPhase(ShutdownPhase::XPCOMShutdown)) {
|
||||
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
|
||||
}
|
||||
|
||||
|
@ -500,7 +500,7 @@ nsresult SharedStringBundle::LoadProperties() {
|
|||
// our string bundles come from). Since shared string bundles won't be
|
||||
// useful after shutdown has started anyway (and we almost certainly got
|
||||
// here from a pre-load attempt in an idle task), just bail out.
|
||||
if (PastShutdownPhase(ShutdownPhase::Shutdown)) {
|
||||
if (PastShutdownPhase(ShutdownPhase::XPCOMShutdown)) {
|
||||
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
|
||||
}
|
||||
|
||||
|
|
|
@ -126,7 +126,7 @@ nsCOMPtr<nsIThread> EnsureMTA::GetMTAThread() {
|
|||
BackgroundMTAData* bgData = new BackgroundMTAData();
|
||||
|
||||
auto setClearOnShutdown = [ptr = &sMTAData]() -> void {
|
||||
ClearOnShutdown(ptr, ShutdownPhase::ShutdownThreads);
|
||||
ClearOnShutdown(ptr, ShutdownPhase::XPCOMShutdownThreads);
|
||||
};
|
||||
|
||||
if (NS_IsMainThread()) {
|
||||
|
|
|
@ -500,7 +500,7 @@ void RegisterArrayData(const ArrayData* aArrayData, size_t aLength) {
|
|||
|
||||
if (!sArrayData) {
|
||||
sArrayData = new Vector<std::pair<const ArrayData*, size_t>>();
|
||||
ClearOnShutdown(&sArrayData, ShutdownPhase::ShutdownThreads);
|
||||
ClearOnShutdown(&sArrayData, ShutdownPhase::XPCOMShutdownThreads);
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_TRUE(sArrayData->emplaceBack(std::make_pair(aArrayData, aLength)));
|
||||
|
|
|
@ -1220,7 +1220,7 @@ nsresult mozJSComponentLoader::Import(JSContext* aCx,
|
|||
!mInProgressImports.Get(info.Key(), &mod)) {
|
||||
// We're trying to import a new JSM, but we're late in shutdown and this
|
||||
// will likely not succeed and might even crash, so fail here.
|
||||
if (PastShutdownPhase(ShutdownPhase::ShutdownFinal)) {
|
||||
if (PastShutdownPhase(ShutdownPhase::XPCOMShutdownFinal)) {
|
||||
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
|
||||
}
|
||||
|
||||
|
|
|
@ -172,8 +172,7 @@ already_AddRefed<nsHttpHandler> nsHttpHandler::GetInstance() {
|
|||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
// There is code that may be executed during the final cycle collection
|
||||
// shutdown and still referencing gHttpHandler.
|
||||
ClearOnShutdown(&gHttpHandler,
|
||||
ShutdownPhase::ShutdownPostLastCycleCollection);
|
||||
ClearOnShutdown(&gHttpHandler, ShutdownPhase::CCPostLastCycleCollection);
|
||||
}
|
||||
RefPtr<nsHttpHandler> httpHandler = gHttpHandler;
|
||||
return httpHandler.forget();
|
||||
|
|
|
@ -100,7 +100,7 @@ function do_close_profile(generator) {
|
|||
|
||||
// Close the db.
|
||||
let service = Services.cookies.QueryInterface(Ci.nsIObserver);
|
||||
service.observe(null, "profile-before-change", "shutdown-persist");
|
||||
service.observe(null, "profile-before-change", null);
|
||||
}
|
||||
|
||||
function _promise_observer(topic) {
|
||||
|
@ -131,7 +131,7 @@ function promise_close_profile() {
|
|||
|
||||
// Close the db.
|
||||
let service = Services.cookies.QueryInterface(Ci.nsIObserver);
|
||||
service.observe(null, "profile-before-change", "shutdown-persist");
|
||||
service.observe(null, "profile-before-change", null);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ static LinkedList<ChannelWrapper>& ChannelList() {
|
|||
static UniquePtr<ChannelListHolder> sChannelList;
|
||||
if (!sChannelList) {
|
||||
sChannelList.reset(new ChannelListHolder());
|
||||
ClearOnShutdown(&sChannelList, ShutdownPhase::Shutdown);
|
||||
ClearOnShutdown(&sChannelList, ShutdownPhase::XPCOMShutdown);
|
||||
}
|
||||
return *sChannelList;
|
||||
}
|
||||
|
|
|
@ -441,11 +441,10 @@ nsAppStartup::Quit(uint32_t aMode, int aExitCode, bool* aUserAllowedQuit) {
|
|||
|
||||
// No chance of the shutdown being cancelled from here on; tell people
|
||||
// we're shutting down for sure while all services are still available.
|
||||
if (obsService) {
|
||||
bool isRestarting = mozilla::AppShutdown::IsRestarting();
|
||||
obsService->NotifyObservers(nullptr, "quit-application",
|
||||
isRestarting ? u"restart" : u"shutdown");
|
||||
}
|
||||
bool isRestarting = mozilla::AppShutdown::IsRestarting();
|
||||
mozilla::AppShutdown::AdvanceShutdownPhase(
|
||||
mozilla::ShutdownPhase::AppShutdownConfirmed,
|
||||
isRestarting ? u"restart" : u"shutdown");
|
||||
|
||||
if (!mRunning) {
|
||||
postedExitEvent = true;
|
||||
|
|
|
@ -12632,8 +12632,21 @@
|
|||
"kind": "exponential",
|
||||
"high": 65,
|
||||
"n_buckets": 10,
|
||||
"bug_numbers": [1689953],
|
||||
"alert_emails": ["dothayer@mozilla.com, jstutte@mozilla.com"],
|
||||
"description": "Duration of shutdown phase quit-application, as measured by the shutdown terminator, in seconds of activity"
|
||||
},
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_CHANGE_NET_TEARDOWN": {
|
||||
"record_in_processes": ["main", "content"],
|
||||
"products": ["firefox", "fennec", "thunderbird"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": 65,
|
||||
"n_buckets": 10,
|
||||
"bug_numbers": [1689953],
|
||||
"alert_emails": ["dothayer@mozilla.com, jstutte@mozilla.com"],
|
||||
"description": "Duration of shutdown phase profile-change-net-teardown, as measured by the shutdown terminator, in seconds of activity"
|
||||
},
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_CHANGE_TEARDOWN": {
|
||||
"record_in_processes": ["main", "content"],
|
||||
"products": ["firefox", "fennec", "thunderbird"],
|
||||
|
@ -12641,17 +12654,10 @@
|
|||
"kind": "exponential",
|
||||
"high": 65,
|
||||
"n_buckets": 10,
|
||||
"bug_numbers": [1689953],
|
||||
"alert_emails": ["dothayer@mozilla.com, jstutte@mozilla.com"],
|
||||
"description": "Duration of shutdown phase profile-change-teardown, as measured by the shutdown terminator, in seconds of activity"
|
||||
},
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_XPCOM_WILL_SHUTDOWN": {
|
||||
"record_in_processes": ["main", "content"],
|
||||
"products": ["firefox", "fennec", "thunderbird"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": 65,
|
||||
"n_buckets": 10,
|
||||
"description": "Duration of shutdown phase xpcom-will-shutdown, as measured by the shutdown terminator, in seconds of activity"
|
||||
},
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_BEFORE_CHANGE": {
|
||||
"record_in_processes": ["main", "content"],
|
||||
"products": ["firefox", "fennec", "thunderbird"],
|
||||
|
@ -12659,8 +12665,43 @@
|
|||
"kind": "exponential",
|
||||
"high": 65,
|
||||
"n_buckets": 10,
|
||||
"bug_numbers": [1689953],
|
||||
"alert_emails": ["dothayer@mozilla.com, jstutte@mozilla.com"],
|
||||
"description": "Duration of shutdown phase profile-before-change, as measured by the shutdown terminator, in seconds of activity"
|
||||
},
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_BEFORE_CHANGE_QM": {
|
||||
"record_in_processes": ["main", "content"],
|
||||
"products": ["firefox", "fennec", "thunderbird"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": 65,
|
||||
"n_buckets": 10,
|
||||
"bug_numbers": [1689953],
|
||||
"alert_emails": ["dothayer@mozilla.com, jstutte@mozilla.com"],
|
||||
"description": "Duration of shutdown phase profile-before-change-qm, as measured by the shutdown terminator, in seconds of activity"
|
||||
},
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_XPCOM_WILL_SHUTDOWN": {
|
||||
"record_in_processes": ["main", "content"],
|
||||
"products": ["firefox", "fennec", "thunderbird"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": 65,
|
||||
"n_buckets": 10,
|
||||
"bug_numbers": [1689953],
|
||||
"alert_emails": ["dothayer@mozilla.com, jstutte@mozilla.com"],
|
||||
"description": "Duration of shutdown phase xpcom-will-shutdown, as measured by the shutdown terminator, in seconds of activity"
|
||||
},
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_XPCOM_SHUTDOWN": {
|
||||
"record_in_processes": ["main", "content"],
|
||||
"products": ["firefox", "fennec", "thunderbird"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": 65,
|
||||
"n_buckets": 10,
|
||||
"bug_numbers": [1689953],
|
||||
"alert_emails": ["dothayer@mozilla.com, jstutte@mozilla.com"],
|
||||
"description": "Duration of shutdown phase xpcom-shutdown, as measured by the shutdown terminator, in seconds of activity"
|
||||
},
|
||||
"TAP_TO_LOAD_ENABLED": {
|
||||
"record_in_processes": ["main", "content"],
|
||||
"products": ["firefox", "fennec"],
|
||||
|
|
|
@ -316,10 +316,6 @@
|
|||
"SERVICE_WORKER_WAS_SPAWNED",
|
||||
"SHOULD_AUTO_DETECT_LANGUAGE",
|
||||
"SHOULD_TRANSLATION_UI_APPEAR",
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_BEFORE_CHANGE",
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_CHANGE_TEARDOWN",
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_QUIT_APPLICATION",
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_XPCOM_WILL_SHUTDOWN",
|
||||
"SLOW_ADDON_WARNING_RESPONSE_TIME",
|
||||
"SLOW_ADDON_WARNING_STATES",
|
||||
"STARTUP_CRASH_DETECTED",
|
||||
|
@ -792,10 +788,6 @@
|
|||
"SERVICE_WORKER_WAS_SPAWNED",
|
||||
"SHOULD_AUTO_DETECT_LANGUAGE",
|
||||
"SHOULD_TRANSLATION_UI_APPEAR",
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_BEFORE_CHANGE",
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_CHANGE_TEARDOWN",
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_QUIT_APPLICATION",
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_XPCOM_WILL_SHUTDOWN",
|
||||
"SLOW_ADDON_WARNING_RESPONSE_TIME",
|
||||
"SLOW_ADDON_WARNING_STATES",
|
||||
"SLOW_SCRIPT_NOTICE_COUNT",
|
||||
|
|
|
@ -31,11 +31,16 @@ function nsTerminatorTelemetry() {}
|
|||
|
||||
var HISTOGRAMS = {
|
||||
"quit-application": "SHUTDOWN_PHASE_DURATION_TICKS_QUIT_APPLICATION",
|
||||
"profile-change-net-teardown":
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_CHANGE_NET_TEARDOWN",
|
||||
"profile-change-teardown":
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_CHANGE_TEARDOWN",
|
||||
"profile-before-change":
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_BEFORE_CHANGE",
|
||||
"profile-before-change-qm":
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_BEFORE_CHANGE_QM",
|
||||
"xpcom-will-shutdown": "SHUTDOWN_PHASE_DURATION_TICKS_XPCOM_WILL_SHUTDOWN",
|
||||
"xpcom-shutdown": "SHUTDOWN_PHASE_DURATION_TICKS_XPCOM_SHUTDOWN",
|
||||
};
|
||||
|
||||
nsTerminatorTelemetry.prototype = {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
* process as fast as possible, without any cleanup.
|
||||
*/
|
||||
|
||||
#include "mozilla/ShutdownPhase.h"
|
||||
#include "nsTerminator.h"
|
||||
|
||||
#include "prthread.h"
|
||||
|
@ -39,6 +40,7 @@
|
|||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "mozilla/AppShutdown.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
|
@ -64,6 +66,8 @@
|
|||
// forcefully.
|
||||
#define ADDITIONAL_WAIT_BEFORE_CRASH_MS 3000
|
||||
|
||||
#define HEARTBEAT_INTERVAL_MS 100
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace {
|
||||
|
@ -76,25 +80,34 @@ namespace {
|
|||
* ticks between the time we receive a notification and the next one.
|
||||
*/
|
||||
struct ShutdownStep {
|
||||
char const* const mTopic;
|
||||
mozilla::ShutdownPhase mPhase;
|
||||
int mTicks;
|
||||
|
||||
constexpr explicit ShutdownStep(const char* const topic)
|
||||
: mTopic(topic), mTicks(-1) {}
|
||||
constexpr explicit ShutdownStep(mozilla::ShutdownPhase aPhase)
|
||||
: mPhase(aPhase), mTicks(-1) {}
|
||||
};
|
||||
|
||||
static ShutdownStep sShutdownSteps[] = {
|
||||
ShutdownStep("quit-application"),
|
||||
ShutdownStep("profile-change-net-teardown"),
|
||||
ShutdownStep("profile-change-teardown"),
|
||||
ShutdownStep("profile-before-change"),
|
||||
ShutdownStep("profile-before-change-qm"),
|
||||
ShutdownStep("xpcom-will-shutdown"),
|
||||
ShutdownStep("xpcom-shutdown"),
|
||||
ShutdownStep(mozilla::ShutdownPhase::AppShutdownConfirmed),
|
||||
ShutdownStep(mozilla::ShutdownPhase::AppShutdownNetTeardown),
|
||||
ShutdownStep(mozilla::ShutdownPhase::AppShutdownTeardown),
|
||||
ShutdownStep(mozilla::ShutdownPhase::AppShutdown),
|
||||
ShutdownStep(mozilla::ShutdownPhase::AppShutdownQM),
|
||||
ShutdownStep(mozilla::ShutdownPhase::XPCOMWillShutdown),
|
||||
ShutdownStep(mozilla::ShutdownPhase::XPCOMShutdown),
|
||||
};
|
||||
|
||||
Atomic<bool> sShutdownNotified;
|
||||
|
||||
int GetStepForPhase(mozilla::ShutdownPhase aPhase) {
|
||||
for (size_t i = 0; i < std::size(sShutdownSteps); i++) {
|
||||
if (sShutdownSteps[i].mPhase >= aPhase) {
|
||||
return (int)i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Utility function: create a thread that is non-joinable,
|
||||
// does not prevent the process from terminating, is never
|
||||
// cooperatively scheduled, and uses a default stack size.
|
||||
|
@ -172,9 +185,9 @@ void RunWatchdog(void* arg) {
|
|||
// more reasonable.
|
||||
//
|
||||
#if defined(XP_WIN)
|
||||
Sleep(1000 /* ms */);
|
||||
Sleep(HEARTBEAT_INTERVAL_MS /* ms */);
|
||||
#else
|
||||
usleep(1000000 /* usec */);
|
||||
usleep(HEARTBEAT_INTERVAL_MS * 1000 /* usec */);
|
||||
#endif
|
||||
|
||||
if (gHeartbeat++ < timeToLive) {
|
||||
|
@ -185,24 +198,24 @@ void RunWatchdog(void* arg) {
|
|||
|
||||
// The shutdown steps are not completed yet. Let's report the last one.
|
||||
if (!sShutdownNotified) {
|
||||
const char* lastStep = nullptr;
|
||||
mozilla::ShutdownPhase lastStep = mozilla::ShutdownPhase::NotInShutdown;
|
||||
// Looping inverse here to make the search more robust in case
|
||||
// the observer that triggers UpdateHeartbeat was not called
|
||||
// at all or in the expected order on some step. This should
|
||||
// give us always the last known ShutdownStep.
|
||||
for (int i = ArrayLength(sShutdownSteps) - 1; i >= 0; --i) {
|
||||
if (sShutdownSteps[i].mTicks > -1) {
|
||||
lastStep = sShutdownSteps[i].mTopic;
|
||||
lastStep = sShutdownSteps[i].mPhase;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastStep) {
|
||||
if (lastStep != mozilla::ShutdownPhase::NotInShutdown) {
|
||||
nsCString msg;
|
||||
msg.AppendPrintf(
|
||||
"Shutdown hanging at step %s. "
|
||||
"Something is blocking the main-thread.",
|
||||
lastStep);
|
||||
mozilla::AppShutdown::GetObserverKey(lastStep));
|
||||
// This string will be leaked.
|
||||
MOZ_CRASH_UNSAFE(strdup(msg.BeginReading()));
|
||||
}
|
||||
|
@ -349,6 +362,7 @@ void RunWriter(void* arg) {
|
|||
// will be written correctly, but, again, we don't care enough
|
||||
// about the data to make more efforts.
|
||||
//
|
||||
Unused << PR_Delete(destinationPath.get());
|
||||
if (PR_Rename(tmpFilePath.get(), destinationPath.get()) != PR_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
|
@ -361,25 +375,11 @@ NS_IMPL_ISUPPORTS(nsTerminator, nsIObserver)
|
|||
|
||||
nsTerminator::nsTerminator() : mInitialized(false), mCurrentStep(-1) {}
|
||||
|
||||
// During startup, register as an observer for all interesting topics.
|
||||
nsresult nsTerminator::SelfInit() {
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
if (!os) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
for (auto& shutdownStep : sShutdownSteps) {
|
||||
DebugOnly<nsresult> rv = os->AddObserver(this, shutdownStep.mTopic, false);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AddObserver failed");
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Actually launch these threads. This takes place at the first sign of
|
||||
// shutdown.
|
||||
void nsTerminator::Start() {
|
||||
MOZ_ASSERT(!mInitialized);
|
||||
sShutdownNotified = false;
|
||||
StartWatchdog();
|
||||
#if !defined(NS_FREE_PERMANENT_DATA)
|
||||
// Only allow nsTerminator to write on non-leak-checked builds so we don't
|
||||
|
@ -388,7 +388,33 @@ void nsTerminator::Start() {
|
|||
StartWriter();
|
||||
#endif // !defined(NS_FREE_PERMANENT_DATA)
|
||||
mInitialized = true;
|
||||
sShutdownNotified = false;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTerminator::Observe(nsISupports*, const char* aTopic, const char16_t*) {
|
||||
// This Observe is now only used for testing purposes.
|
||||
// XXX: Check if we should change our testing strategy.
|
||||
if (strcmp(aTopic, "terminator-test-quit-application") == 0) {
|
||||
AdvancePhase(mozilla::ShutdownPhase::AppShutdownConfirmed);
|
||||
} else if (strcmp(aTopic, "terminator-test-profile-change-net-teardown") ==
|
||||
0) {
|
||||
AdvancePhase(mozilla::ShutdownPhase::AppShutdownNetTeardown);
|
||||
} else if (strcmp(aTopic, "terminator-test-profile-change-teardown") == 0) {
|
||||
AdvancePhase(mozilla::ShutdownPhase::AppShutdownTeardown);
|
||||
} else if (strcmp(aTopic, "terminator-test-profile-before-change") == 0) {
|
||||
AdvancePhase(mozilla::ShutdownPhase::AppShutdown);
|
||||
} else if (strcmp(aTopic, "terminator-test-profile-before-change-qm") == 0) {
|
||||
AdvancePhase(mozilla::ShutdownPhase::AppShutdownQM);
|
||||
} else if (strcmp(aTopic,
|
||||
"terminator-test-profile-before-change-telemetry") == 0) {
|
||||
AdvancePhase(mozilla::ShutdownPhase::AppShutdownTelemetry);
|
||||
} else if (strcmp(aTopic, "terminator-test-xpcom-will-shutdown") == 0) {
|
||||
AdvancePhase(mozilla::ShutdownPhase::XPCOMWillShutdown);
|
||||
} else if (strcmp(aTopic, "terminator-test-xpcom-shutdown") == 0) {
|
||||
AdvancePhase(mozilla::ShutdownPhase::XPCOMShutdown);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Prepare, allocate and start the watchdog thread.
|
||||
|
@ -431,11 +457,12 @@ void nsTerminator::StartWatchdog() {
|
|||
#endif
|
||||
|
||||
UniquePtr<Options> options(new Options());
|
||||
const PRIntervalTime ticksDuration = PR_MillisecondsToInterval(1000);
|
||||
const PRIntervalTime ticksDuration =
|
||||
PR_MillisecondsToInterval(HEARTBEAT_INTERVAL_MS);
|
||||
options->crashAfterTicks = crashAfterMS / ticksDuration;
|
||||
// Handle systems where ticksDuration is greater than crashAfterMS.
|
||||
if (options->crashAfterTicks == 0) {
|
||||
options->crashAfterTicks = crashAfterMS / 1000;
|
||||
options->crashAfterTicks = crashAfterMS / HEARTBEAT_INTERVAL_MS;
|
||||
}
|
||||
|
||||
DebugOnly<PRThread*> watchdogThread =
|
||||
|
@ -478,14 +505,11 @@ void nsTerminator::StartWriter() {
|
|||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTerminator::Observe(nsISupports*, const char* aTopic, const char16_t*) {
|
||||
if (strcmp(aTopic, "profile-after-change") == 0) {
|
||||
return SelfInit();
|
||||
void nsTerminator::AdvancePhase(mozilla::ShutdownPhase aPhase) {
|
||||
// If we are done, do nothing
|
||||
if (sShutdownNotified) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Other notifications are shutdown-related.
|
||||
|
||||
// As we have seen examples in the wild of shutdown notifications
|
||||
// not being sent (or not being sent in the expected order), we do
|
||||
// not assume a specific order.
|
||||
|
@ -493,41 +517,29 @@ nsTerminator::Observe(nsISupports*, const char* aTopic, const char16_t*) {
|
|||
Start();
|
||||
}
|
||||
|
||||
UpdateHeartbeat(aTopic);
|
||||
UpdateHeartbeat(GetStepForPhase(aPhase));
|
||||
#if !defined(NS_FREE_PERMANENT_DATA)
|
||||
// Only allow nsTerminator to write on non-leak checked builds so we don't get
|
||||
// leak warnings on shutdown for intentional leaks (see bug 1242084). This
|
||||
// will be enabled again by bug 1255484 when 1255478 lands.
|
||||
UpdateTelemetry();
|
||||
#endif // !defined(NS_FREE_PERMANENT_DATA)
|
||||
UpdateCrashReport(aTopic);
|
||||
|
||||
// Perform a little cleanup
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
MOZ_RELEASE_ASSERT(os);
|
||||
(void)os->RemoveObserver(this, aTopic);
|
||||
|
||||
return NS_OK;
|
||||
UpdateCrashReport(mozilla::AppShutdown::GetObserverKey(aPhase));
|
||||
}
|
||||
|
||||
void nsTerminator::UpdateHeartbeat(const char* aTopic) {
|
||||
// Reset the clock, find out how long the current phase has lasted.
|
||||
uint32_t ticks = gHeartbeat.exchange(0);
|
||||
if (mCurrentStep >= 0) {
|
||||
sShutdownSteps[mCurrentStep].mTicks = ticks;
|
||||
}
|
||||
void nsTerminator::UpdateHeartbeat(int32_t aStep) {
|
||||
MOZ_ASSERT(aStep >= mCurrentStep);
|
||||
|
||||
// Find out where we now are in the current shutdown.
|
||||
// Don't assume that shutdown takes place in the expected order.
|
||||
int nextStep = -1;
|
||||
for (size_t i = 0; i < ArrayLength(sShutdownSteps); ++i) {
|
||||
if (strcmp(sShutdownSteps[i].mTopic, aTopic) == 0) {
|
||||
nextStep = i;
|
||||
break;
|
||||
if (aStep > mCurrentStep) {
|
||||
// Reset the clock, find out how long the current phase has lasted.
|
||||
uint32_t ticks = gHeartbeat.exchange(0);
|
||||
if (mCurrentStep >= 0) {
|
||||
sShutdownSteps[mCurrentStep].mTicks = ticks;
|
||||
}
|
||||
sShutdownSteps[aStep].mTicks = 0;
|
||||
|
||||
mCurrentStep = aStep;
|
||||
}
|
||||
MOZ_ASSERT(nextStep != -1);
|
||||
mCurrentStep = nextStep;
|
||||
}
|
||||
|
||||
void nsTerminator::UpdateTelemetry() {
|
||||
|
@ -556,7 +568,8 @@ void nsTerminator::UpdateTelemetry() {
|
|||
telemetryData->AppendLiteral(", ");
|
||||
}
|
||||
telemetryData->AppendLiteral(R"(")");
|
||||
telemetryData->Append(shutdownStep.mTopic);
|
||||
telemetryData->Append(
|
||||
mozilla::AppShutdown::GetObserverKey(shutdownStep.mPhase));
|
||||
telemetryData->AppendLiteral(R"(": )");
|
||||
telemetryData->AppendInt(shutdownStep.mTicks);
|
||||
}
|
||||
|
|
|
@ -18,21 +18,21 @@ class nsTerminator final : public nsIObserver {
|
|||
NS_DECL_NSIOBSERVER
|
||||
|
||||
nsTerminator();
|
||||
void AdvancePhase(mozilla::ShutdownPhase aPhase);
|
||||
|
||||
private:
|
||||
nsresult SelfInit();
|
||||
void Start();
|
||||
void StartWatchdog();
|
||||
void StartWriter();
|
||||
|
||||
void UpdateHeartbeat(const char* aTopic);
|
||||
void UpdateHeartbeat(int aStep);
|
||||
void UpdateTelemetry();
|
||||
void UpdateCrashReport(const char* aTopic);
|
||||
|
||||
~nsTerminator() = default;
|
||||
|
||||
bool mInitialized;
|
||||
int32_t mCurrentStep;
|
||||
int mCurrentStep;
|
||||
};
|
||||
|
||||
// This is called by XPCOMInit when the shutdown is completed.
|
||||
|
|
|
@ -16,6 +16,17 @@ var PATH;
|
|||
var PATH_TMP;
|
||||
var terminator;
|
||||
|
||||
let KEYS = [
|
||||
"quit-application",
|
||||
"profile-change-net-teardown",
|
||||
"profile-change-teardown",
|
||||
"profile-before-change",
|
||||
"profile-before-change-qm",
|
||||
"profile-before-change-telemetry",
|
||||
"xpcom-will-shutdown",
|
||||
"xpcom-shutdown",
|
||||
];
|
||||
|
||||
add_task(async function init() {
|
||||
do_get_profile();
|
||||
PATH = Path.join(Constants.Path.localProfileDir, "ShutdownDuration.json");
|
||||
|
@ -28,7 +39,6 @@ add_task(async function init() {
|
|||
terminator = Cc["@mozilla.org/toolkit/shutdown-terminator;1"].createInstance(
|
||||
Ci.nsIObserver
|
||||
);
|
||||
terminator.observe(null, "profile-after-change", null);
|
||||
});
|
||||
|
||||
var promiseShutdownDurationData = async function() {
|
||||
|
@ -51,23 +61,22 @@ var promiseShutdownDurationData = async function() {
|
|||
};
|
||||
|
||||
add_task(async function test_record() {
|
||||
let PHASE0 = "profile-change-teardown";
|
||||
let PHASE1 = "profile-before-change";
|
||||
let PHASE2 = "xpcom-will-shutdown";
|
||||
let t0 = Date.now();
|
||||
|
||||
info("Starting shutdown");
|
||||
terminator.observe(null, "profile-change-teardown", null);
|
||||
terminator.observe(null, "terminator-test-" + KEYS[2], null);
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
info("Moving to next phase");
|
||||
terminator.observe(null, PHASE1, null);
|
||||
terminator.observe(null, "terminator-test-" + KEYS[3], null);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
let data = await promiseShutdownDurationData();
|
||||
|
||||
let t1 = Date.now();
|
||||
|
||||
Assert.ok(PHASE0 in data, "The file contains the expected key");
|
||||
let duration = data[PHASE0];
|
||||
Assert.ok(KEYS[2] in data, "The file contains the expected key");
|
||||
let duration = data[KEYS[2]];
|
||||
Assert.equal(typeof duration, "number");
|
||||
Assert.ok(duration >= 0, "Duration is a non-negative number");
|
||||
Assert.ok(
|
||||
|
@ -77,7 +86,7 @@ add_task(async function test_record() {
|
|||
|
||||
Assert.equal(
|
||||
Object.keys(data).length,
|
||||
1,
|
||||
2,
|
||||
"Data does not contain other durations"
|
||||
);
|
||||
|
||||
|
@ -89,7 +98,7 @@ add_task(async function test_record() {
|
|||
let WAIT_MS = 2000;
|
||||
await new Promise(resolve => setTimeout(resolve, WAIT_MS));
|
||||
|
||||
terminator.observe(null, PHASE2, null);
|
||||
terminator.observe(null, "terminator-test-" + KEYS[4], null);
|
||||
data = await promiseShutdownDurationData();
|
||||
|
||||
let t2 = Date.now();
|
||||
|
@ -98,18 +107,18 @@ add_task(async function test_record() {
|
|||
Object.keys(data)
|
||||
.sort()
|
||||
.join(", "),
|
||||
[PHASE0, PHASE1].sort().join(", "),
|
||||
[KEYS[2], KEYS[3], KEYS[4]].sort().join(", "),
|
||||
"The file contains the expected keys"
|
||||
);
|
||||
Assert.equal(data[PHASE0], duration, "Duration of phase 0 hasn't changed");
|
||||
let duration2 = data[PHASE1];
|
||||
Assert.equal(data[KEYS[2]], duration, "Duration of phase 0 hasn't changed");
|
||||
let duration2 = data[KEYS[3]];
|
||||
Assert.equal(typeof duration2, "number");
|
||||
Assert.ok(
|
||||
duration2 >= WAIT_MS / 2000,
|
||||
"We have waited at least " + WAIT_MS / 2000 + " ticks"
|
||||
duration2 >= WAIT_MS / 100,
|
||||
"We have waited at least " + WAIT_MS / 100 + " ticks"
|
||||
);
|
||||
Assert.ok(
|
||||
duration2 <= Math.ceil((t2 - t1) / 1000) + 1,
|
||||
duration2 <= Math.ceil((t2 - t1) / 100) + 1,
|
||||
"Duration is reasonable"
|
||||
);
|
||||
});
|
||||
|
|
|
@ -15,11 +15,16 @@ var PATH;
|
|||
|
||||
var HISTOGRAMS = {
|
||||
"quit-application": "SHUTDOWN_PHASE_DURATION_TICKS_QUIT_APPLICATION",
|
||||
"profile-change-net-teardown":
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_CHANGE_NET_TEARDOWN",
|
||||
"profile-change-teardown":
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_CHANGE_TEARDOWN",
|
||||
"profile-before-change":
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_BEFORE_CHANGE",
|
||||
"profile-before-change-qm":
|
||||
"SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_BEFORE_CHANGE_QM",
|
||||
"xpcom-will-shutdown": "SHUTDOWN_PHASE_DURATION_TICKS_XPCOM_WILL_SHUTDOWN",
|
||||
"xpcom-shutdown": "SHUTDOWN_PHASE_DURATION_TICKS_XPCOM_SHUTDOWN",
|
||||
};
|
||||
|
||||
add_task(async function init() {
|
||||
|
|
|
@ -11,7 +11,7 @@ function setup_crash() {
|
|||
);
|
||||
|
||||
Services.prefs.setBoolPref("toolkit.terminator.testing", true);
|
||||
Services.prefs.setIntPref("toolkit.asyncshutdown.crash_timeout", 10);
|
||||
Services.prefs.setIntPref("toolkit.asyncshutdown.crash_timeout", 150);
|
||||
|
||||
// Initialize the terminator
|
||||
// (normally, this is done through the manifest file, but xpcshell
|
||||
|
@ -19,12 +19,12 @@ function setup_crash() {
|
|||
let terminator = Cc[
|
||||
"@mozilla.org/toolkit/shutdown-terminator;1"
|
||||
].createInstance(Ci.nsIObserver);
|
||||
terminator.observe(null, "profile-after-change", null);
|
||||
terminator.observe(null, "terminator-test-profile-after-change", null);
|
||||
|
||||
// Inform the terminator that shutdown has started
|
||||
// Pick an arbitrary notification
|
||||
terminator.observe(null, "xpcom-will-shutdown", null);
|
||||
terminator.observe(null, "profile-before-change", null);
|
||||
terminator.observe(null, "terminator-test-profile-before-change", null);
|
||||
terminator.observe(null, "terminator-test-xpcom-will-shutdown", null);
|
||||
|
||||
dump("Waiting (actively) for the crash\n");
|
||||
Services.tm.spinEventLoopUntil(() => false);
|
||||
|
@ -32,7 +32,7 @@ function setup_crash() {
|
|||
|
||||
function after_crash(mdump, extra) {
|
||||
info("Crash signature: " + JSON.stringify(extra, null, "\t"));
|
||||
Assert.equal(extra.ShutdownProgress, "profile-before-change");
|
||||
Assert.equal(extra.ShutdownProgress, "xpcom-will-shutdown");
|
||||
}
|
||||
|
||||
add_task(async function run_test() {
|
||||
|
|
|
@ -44,15 +44,6 @@ observer's Observe() method.
|
|||
profile-do-change
|
||||
profile-after-change
|
||||
|
||||
"shutdown-persist"
|
||||
The user is logging out and whatever data the observer stores
|
||||
for the current profile should be released from memory and
|
||||
saved to disk.
|
||||
The following topics happen in this context:
|
||||
profile-change-net-teardown
|
||||
profile-change-teardown
|
||||
profile-before-change
|
||||
|
||||
See https://wiki.mozilla.org/XPCOM_Shutdown for more details about the shutdown
|
||||
process.
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
|
||||
#include "mozilla/AppShutdown.h"
|
||||
#include "mozilla/AutoRestore.h"
|
||||
#ifdef MOZ_BACKGROUNDTASKS
|
||||
# include "mozilla/BackgroundTasks.h"
|
||||
|
@ -1056,30 +1057,24 @@ void nsXREDirProvider::DoShutdown() {
|
|||
AUTO_PROFILER_LABEL("nsXREDirProvider::DoShutdown", OTHER);
|
||||
|
||||
if (mProfileNotified) {
|
||||
nsCOMPtr<nsIObserverService> obsSvc =
|
||||
mozilla::services::GetObserverService();
|
||||
NS_ASSERTION(obsSvc, "No observer service?");
|
||||
if (obsSvc) {
|
||||
static const char16_t kShutdownPersist[] = u"shutdown-persist";
|
||||
obsSvc->NotifyObservers(nullptr, "profile-change-net-teardown",
|
||||
kShutdownPersist);
|
||||
obsSvc->NotifyObservers(nullptr, "profile-change-teardown",
|
||||
kShutdownPersist);
|
||||
mozilla::AppShutdown::AdvanceShutdownPhase(
|
||||
mozilla::ShutdownPhase::AppShutdownNetTeardown, nullptr);
|
||||
mozilla::AppShutdown::AdvanceShutdownPhase(
|
||||
mozilla::ShutdownPhase::AppShutdownTeardown, nullptr);
|
||||
|
||||
#ifdef DEBUG
|
||||
// Not having this causes large intermittent leaks. See bug 1340425.
|
||||
if (JSContext* cx = mozilla::dom::danger::GetJSContext()) {
|
||||
JS_GC(cx);
|
||||
}
|
||||
// Not having this causes large intermittent leaks. See bug 1340425.
|
||||
if (JSContext* cx = mozilla::dom::danger::GetJSContext()) {
|
||||
JS_GC(cx);
|
||||
}
|
||||
#endif
|
||||
|
||||
obsSvc->NotifyObservers(nullptr, "profile-before-change",
|
||||
kShutdownPersist);
|
||||
obsSvc->NotifyObservers(nullptr, "profile-before-change-qm",
|
||||
kShutdownPersist);
|
||||
obsSvc->NotifyObservers(nullptr, "profile-before-change-telemetry",
|
||||
kShutdownPersist);
|
||||
}
|
||||
mozilla::AppShutdown::AdvanceShutdownPhase(
|
||||
mozilla::ShutdownPhase::AppShutdown, nullptr);
|
||||
mozilla::AppShutdown::AdvanceShutdownPhase(
|
||||
mozilla::ShutdownPhase::AppShutdownQM, nullptr);
|
||||
mozilla::AppShutdown::AdvanceShutdownPhase(
|
||||
mozilla::ShutdownPhase::AppShutdownTelemetry, nullptr);
|
||||
mProfileNotified = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -400,7 +400,7 @@ void ProfilerParentTracker::EnsureInstance() {
|
|||
// The tracker should get destroyed before threads are shutdown, because its
|
||||
// destruction closes extant channels, which could trigger promise rejections
|
||||
// that need to be dispatched to other threads.
|
||||
ClearOnShutdown(&sInstance, ShutdownPhase::ShutdownThreads);
|
||||
ClearOnShutdown(&sInstance, ShutdownPhase::XPCOMShutdownThreads);
|
||||
}
|
||||
|
||||
/* static */
|
||||
|
|
|
@ -2137,7 +2137,7 @@ const WinUtils::WhitelistVec& WinUtils::GetWhitelistedPaths() {
|
|||
static WhitelistVec sWhitelist([]() -> WhitelistVec {
|
||||
auto setClearFn = [ptr = &sWhitelist]() -> void {
|
||||
RunOnShutdown([ptr]() -> void { ptr->clear(); },
|
||||
ShutdownPhase::ShutdownFinal);
|
||||
ShutdownPhase::XPCOMShutdownFinal);
|
||||
};
|
||||
|
||||
if (NS_IsMainThread()) {
|
||||
|
|
|
@ -5197,22 +5197,30 @@ bool nsWindow::ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam,
|
|||
// Windows won't let us do that. Bug 212316.
|
||||
nsCOMPtr<nsIObserverService> obsServ =
|
||||
mozilla::services::GetObserverService();
|
||||
const char16_t* context = u"shutdown-persist";
|
||||
const char16_t* syncShutdown = u"syncShutdown";
|
||||
const char16_t* quitType = GetQuitType();
|
||||
|
||||
AppShutdown::Init(AppShutdownMode::Normal, 0);
|
||||
|
||||
obsServ->NotifyObservers(nullptr, "quit-application-granted",
|
||||
syncShutdown);
|
||||
obsServ->NotifyObservers(nullptr, "quit-application-forced", nullptr);
|
||||
obsServ->NotifyObservers(nullptr, "quit-application", quitType);
|
||||
obsServ->NotifyObservers(nullptr, "profile-change-net-teardown",
|
||||
context);
|
||||
obsServ->NotifyObservers(nullptr, "profile-change-teardown", context);
|
||||
obsServ->NotifyObservers(nullptr, "profile-before-change", context);
|
||||
obsServ->NotifyObservers(nullptr, "profile-before-change-qm", context);
|
||||
obsServ->NotifyObservers(nullptr, "profile-before-change-telemetry",
|
||||
context);
|
||||
mozilla::AppShutdown::DoImmediateExit();
|
||||
|
||||
AppShutdown::OnShutdownConfirmed();
|
||||
|
||||
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownConfirmed,
|
||||
quitType);
|
||||
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownNetTeardown,
|
||||
nullptr);
|
||||
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTeardown,
|
||||
nullptr);
|
||||
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdown, nullptr);
|
||||
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownQM,
|
||||
nullptr);
|
||||
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTelemetry,
|
||||
nullptr);
|
||||
|
||||
AppShutdown::DoImmediateExit();
|
||||
}
|
||||
sCanQuit = TRI_UNKNOWN;
|
||||
result = true;
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
* 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 "AppShutdown.h"
|
||||
|
||||
#ifdef XP_WIN
|
||||
# include <windows.h>
|
||||
#else
|
||||
|
@ -13,6 +11,7 @@
|
|||
#endif
|
||||
|
||||
#include "GeckoProfiler.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/CmdLineAndEnvUtils.h"
|
||||
#include "mozilla/PoisonIOInterposer.h"
|
||||
#include "mozilla/Printf.h"
|
||||
|
@ -21,11 +20,19 @@
|
|||
#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 "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_NEW_XULSTORE
|
||||
|
@ -34,6 +41,29 @@
|
|||
|
||||
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, // XPCOMShutdownLoaders
|
||||
nullptr, // XPCOMShutdownFinal
|
||||
nullptr // CCPostLastCycleCollection
|
||||
};
|
||||
|
||||
static_assert(sizeof(sPhaseObserverKeys) / sizeof(sPhaseObserverKeys[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;
|
||||
|
@ -54,11 +84,11 @@ static char* sSavedProfLDEnvVar = nullptr;
|
|||
ShutdownPhase GetShutdownPhaseFromPrefValue(int32_t aPrefValue) {
|
||||
switch (aPrefValue) {
|
||||
case 1:
|
||||
return ShutdownPhase::ShutdownPostLastCycleCollection;
|
||||
return ShutdownPhase::CCPostLastCycleCollection;
|
||||
case 2:
|
||||
return ShutdownPhase::ShutdownThreads;
|
||||
return ShutdownPhase::XPCOMShutdownThreads;
|
||||
case 3:
|
||||
return ShutdownPhase::Shutdown;
|
||||
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.
|
||||
|
@ -78,6 +108,11 @@ void AppShutdown::SaveEnvVarsForPotentialRestart() {
|
|||
}
|
||||
}
|
||||
|
||||
const char* AppShutdown::GetObserverKey(ShutdownPhase aPhase) {
|
||||
return sPhaseObserverKeys[static_cast<std::underlying_type_t<ShutdownPhase>>(
|
||||
aPhase)];
|
||||
}
|
||||
|
||||
void AppShutdown::MaybeDoRestart() {
|
||||
if (sShutdownMode == AppShutdownMode::Restart) {
|
||||
StopLateWriteChecks();
|
||||
|
@ -135,6 +170,10 @@ void AppShutdown::Init(AppShutdownMode aMode, int aExitCode) {
|
|||
|
||||
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.
|
||||
|
@ -243,4 +282,28 @@ bool AppShutdown::IsRestarting() {
|
|||
return sShutdownMode == AppShutdownMode::Restart;
|
||||
}
|
||||
|
||||
void AppShutdown::AdvanceShutdownPhase(
|
||||
ShutdownPhase aPhase, const char16_t* aNotificationData,
|
||||
nsCOMPtr<nsISupports> aNotificationSubject) {
|
||||
#ifndef ANDROID
|
||||
if (sTerminator) {
|
||||
sTerminator->AdvancePhase(aPhase);
|
||||
}
|
||||
#endif
|
||||
|
||||
mozilla::KillClearOnShutdown(aPhase);
|
||||
|
||||
MaybeFastShutdown(aPhase);
|
||||
|
||||
const char* aTopic = AppShutdown::GetObserverKey(aPhase);
|
||||
if (aTopic) {
|
||||
nsCOMPtr<nsIObserverService> obsService =
|
||||
mozilla::services::GetObserverService();
|
||||
if (obsService) {
|
||||
obsService->NotifyObservers(aNotificationSubject, aTopic,
|
||||
aNotificationData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#ifndef AppShutdown_h
|
||||
#define AppShutdown_h
|
||||
|
||||
#include <type_traits>
|
||||
#include "ShutdownPhase.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -19,7 +20,6 @@ enum class AppShutdownMode {
|
|||
class AppShutdown {
|
||||
public:
|
||||
static bool IsShuttingDown();
|
||||
|
||||
/**
|
||||
* Returns the current exit code that the process will be terminated with.
|
||||
*/
|
||||
|
@ -47,12 +47,6 @@ class AppShutdown {
|
|||
*/
|
||||
static void MaybeDoRestart();
|
||||
|
||||
/**
|
||||
* This will perform a fast shutdown via _exit(0) or similar if the user's
|
||||
* prefs are configured to do so at this phase.
|
||||
*/
|
||||
static void MaybeFastShutdown(ShutdownPhase aPhase);
|
||||
|
||||
/**
|
||||
* The _exit() call is not a safe way to terminate your own process on
|
||||
* Windows, because _exit runs DLL detach callbacks which run static
|
||||
|
@ -69,6 +63,25 @@ class AppShutdown {
|
|||
* restart.
|
||||
*/
|
||||
static bool IsRestarting();
|
||||
|
||||
/**
|
||||
* Wrapper for shutdown notifications that inform the terminator before
|
||||
* we notify other observers. Calls MaybeFastShutdown.
|
||||
*/
|
||||
static void AdvanceShutdownPhase(
|
||||
ShutdownPhase aPhase, const char16_t* aNotificationData = nullptr,
|
||||
nsCOMPtr<nsISupports> aNotificationSubject = nullptr);
|
||||
|
||||
/**
|
||||
* This will perform a fast shutdown via _exit(0) or similar if the user's
|
||||
* prefs are configured to do so at this phase.
|
||||
*/
|
||||
static void MaybeFastShutdown(ShutdownPhase aPhase);
|
||||
|
||||
/**
|
||||
* Map shutdown phases to observer keys
|
||||
*/
|
||||
static const char* GetObserverKey(ShutdownPhase aPhase);
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
* This header exports two public methods in the mozilla namespace:
|
||||
*
|
||||
* template<class SmartPtr>
|
||||
* void ClearOnShutdown(SmartPtr *aPtr, aPhase=ShutdownPhase::ShutdownFinal)
|
||||
* void ClearOnShutdown(SmartPtr *aPtr,
|
||||
* aPhase=ShutdownPhase::XPCOMShutdownFinal)
|
||||
*
|
||||
* This function takes a pointer to a smart pointer and nulls the smart pointer
|
||||
* on shutdown (and a particular phase of shutdown as needed). If a phase
|
||||
|
@ -40,7 +41,7 @@
|
|||
*
|
||||
* template <typename CallableT>
|
||||
* void RunOnShutdown(CallableT&& aCallable,
|
||||
* aPhase = ShutdownPhase::ShutdownFinal)
|
||||
* aPhase = ShutdownPhase::XPCOMShutdownFinal)
|
||||
*
|
||||
* This function takes a callable and executes it upon shutdown at the start of
|
||||
* the specified phase. If the phase has already occurred when RunOnShutdown()
|
||||
|
@ -108,7 +109,7 @@ extern ShutdownPhase sCurrentShutdownPhase;
|
|||
|
||||
template <class SmartPtr>
|
||||
inline void ClearOnShutdown(
|
||||
SmartPtr* aPtr, ShutdownPhase aPhase = ShutdownPhase::ShutdownFinal) {
|
||||
SmartPtr* aPtr, ShutdownPhase aPhase = ShutdownPhase::XPCOMShutdownFinal) {
|
||||
using namespace ClearOnShutdown_Internal;
|
||||
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
@ -118,8 +119,9 @@ inline void ClearOnShutdown(
|
|||
}
|
||||
|
||||
template <typename CallableT>
|
||||
inline void RunOnShutdown(CallableT&& aCallable,
|
||||
ShutdownPhase aPhase = ShutdownPhase::ShutdownFinal) {
|
||||
inline void RunOnShutdown(
|
||||
CallableT&& aCallable,
|
||||
ShutdownPhase aPhase = ShutdownPhase::XPCOMShutdownFinal) {
|
||||
using namespace ClearOnShutdown_Internal;
|
||||
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
|
|
@ -12,15 +12,21 @@ namespace mozilla {
|
|||
// Must be contiguous starting at 0
|
||||
enum class ShutdownPhase {
|
||||
NotInShutdown = 0,
|
||||
WillShutdown,
|
||||
Shutdown,
|
||||
ShutdownThreads,
|
||||
ShutdownLoaders,
|
||||
ShutdownFinal,
|
||||
ShutdownPostLastCycleCollection,
|
||||
ShutdownPhase_Length, // never pass this value
|
||||
First = WillShutdown, // for iteration
|
||||
Last = ShutdownFinal
|
||||
AppShutdownConfirmed,
|
||||
AppShutdownNetTeardown,
|
||||
AppShutdownTeardown,
|
||||
AppShutdown,
|
||||
AppShutdownQM,
|
||||
AppShutdownTelemetry,
|
||||
XPCOMWillShutdown,
|
||||
XPCOMShutdown,
|
||||
XPCOMShutdownThreads,
|
||||
XPCOMShutdownLoaders,
|
||||
XPCOMShutdownFinal,
|
||||
CCPostLastCycleCollection,
|
||||
ShutdownPhase_Length, // never pass this value
|
||||
First = AppShutdownConfirmed, // for iteration
|
||||
Last = XPCOMShutdownFinal
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -598,45 +598,29 @@ nsresult ShutdownXPCOM(nsIServiceManager* aServMgr) {
|
|||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
RefPtr<nsObserverService> observerService;
|
||||
CallGetService("@mozilla.org/observer-service;1",
|
||||
(nsObserverService**)getter_AddRefs(observerService));
|
||||
mozilla::AppShutdown::AdvanceShutdownPhase(
|
||||
mozilla::ShutdownPhase::XPCOMWillShutdown);
|
||||
|
||||
if (observerService) {
|
||||
mozilla::KillClearOnShutdown(ShutdownPhase::WillShutdown);
|
||||
mozilla::AppShutdown::MaybeFastShutdown(
|
||||
mozilla::ShutdownPhase::WillShutdown);
|
||||
observerService->NotifyObservers(
|
||||
nullptr, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, nullptr);
|
||||
|
||||
nsCOMPtr<nsIServiceManager> mgr;
|
||||
rv = NS_GetServiceManager(getter_AddRefs(mgr));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
mozilla::KillClearOnShutdown(ShutdownPhase::Shutdown);
|
||||
mozilla::AppShutdown::MaybeFastShutdown(
|
||||
mozilla::ShutdownPhase::Shutdown);
|
||||
observerService->NotifyObservers(mgr, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
|
||||
nullptr);
|
||||
}
|
||||
nsCOMPtr<nsIServiceManager> mgr;
|
||||
rv = NS_GetServiceManager(getter_AddRefs(mgr));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// We want the service manager to be the subject of notifications
|
||||
mozilla::AppShutdown::AdvanceShutdownPhase(
|
||||
mozilla::ShutdownPhase::XPCOMShutdown, nullptr,
|
||||
do_QueryInterface(mgr));
|
||||
}
|
||||
|
||||
#ifndef ANDROID
|
||||
mozilla::XPCOMShutdownNotified();
|
||||
mozilla::XPCOMShutdownNotified();
|
||||
#endif
|
||||
}
|
||||
|
||||
// This must happen after the shutdown of media and widgets, which
|
||||
// are triggered by the NS_XPCOM_SHUTDOWN_OBSERVER_ID notification.
|
||||
NS_ProcessPendingEvents(thread);
|
||||
gfxPlatform::ShutdownLayersIPC();
|
||||
|
||||
if (observerService) {
|
||||
mozilla::KillClearOnShutdown(ShutdownPhase::ShutdownThreads);
|
||||
mozilla::AppShutdown::MaybeFastShutdown(
|
||||
mozilla::ShutdownPhase::ShutdownThreads);
|
||||
observerService->NotifyObservers(
|
||||
nullptr, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, nullptr);
|
||||
}
|
||||
|
||||
mozilla::AppShutdown::AdvanceShutdownPhase(
|
||||
mozilla::ShutdownPhase::XPCOMShutdownThreads);
|
||||
gXPCOMThreadsShutDown = true;
|
||||
NS_ProcessPendingEvents(thread);
|
||||
|
||||
|
@ -646,8 +630,13 @@ nsresult ShutdownXPCOM(nsIServiceManager* aServMgr) {
|
|||
|
||||
NS_ProcessPendingEvents(thread);
|
||||
|
||||
mozilla::KillClearOnShutdown(ShutdownPhase::XPCOMShutdownLoaders);
|
||||
// XXX: Why don't we try a MaybeFastShutdown for XPCOMShutdownLoaders ?
|
||||
|
||||
RefPtr<nsObserverService> observerService;
|
||||
CallGetService("@mozilla.org/observer-service;1",
|
||||
(nsObserverService**)getter_AddRefs(observerService));
|
||||
if (observerService) {
|
||||
mozilla::KillClearOnShutdown(ShutdownPhase::ShutdownLoaders);
|
||||
observerService->Shutdown();
|
||||
}
|
||||
|
||||
|
@ -655,7 +644,7 @@ nsresult ShutdownXPCOM(nsIServiceManager* aServMgr) {
|
|||
// we've finished notifying observers of XPCOM shutdown, because shutdown
|
||||
// observers themselves might call ClearOnShutdown().
|
||||
// Some destructors may fire extra runnables that will be processed below.
|
||||
mozilla::KillClearOnShutdown(ShutdownPhase::ShutdownFinal);
|
||||
mozilla::KillClearOnShutdown(ShutdownPhase::XPCOMShutdownFinal);
|
||||
|
||||
// Shutdown all remaining threads. This method does not return until
|
||||
// all threads created using the thread manager (with the exception of
|
||||
|
@ -675,7 +664,7 @@ nsresult ShutdownXPCOM(nsIServiceManager* aServMgr) {
|
|||
AbstractThread::ShutdownMainThread();
|
||||
|
||||
mozilla::AppShutdown::MaybeFastShutdown(
|
||||
mozilla::ShutdownPhase::ShutdownFinal);
|
||||
mozilla::ShutdownPhase::XPCOMShutdownFinal);
|
||||
|
||||
// XPCOM is officially in shutdown mode NOW
|
||||
// Set this only after the observers have been notified as this
|
||||
|
@ -718,9 +707,9 @@ nsresult ShutdownXPCOM(nsIServiceManager* aServMgr) {
|
|||
|
||||
// There can be code trying to refer to global objects during the final cc
|
||||
// shutdown. This is the phase for such global objects to correctly release.
|
||||
mozilla::KillClearOnShutdown(ShutdownPhase::ShutdownPostLastCycleCollection);
|
||||
mozilla::KillClearOnShutdown(ShutdownPhase::CCPostLastCycleCollection);
|
||||
mozilla::AppShutdown::MaybeFastShutdown(
|
||||
mozilla::ShutdownPhase::ShutdownPostLastCycleCollection);
|
||||
mozilla::ShutdownPhase::CCPostLastCycleCollection);
|
||||
|
||||
mozilla::scache::StartupCache::DeleteSingleton();
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче