Impressum AvailableMemoryWatcherMac.cpp
Sprache: C
/* -*- 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/. */
/* * The Mac AvailableMemoryWatcher works as follows. When the OS memory pressure * level changes on macOS, nsAvailableMemoryWatcher::OnMemoryPressureChanged() * is called with the new memory pressure level. The level is represented in * Gecko by a MacMemoryPressureLevel instance and represents the states of * normal, warning, or critical which correspond to the native levels. When the * browser launches, the initial level is determined using a sysctl. Which * actions are taken in the browser in response to memory pressure, and the * level (warning or critical) which trigger the reponse is configurable with * prefs to make it easier to perform experiments to study how the response * affects the user experience. * * By default, the browser responds by attempting to reduce memory use when the * OS transitions to the critical level and while it stays in the critical * level. i.e., "critical" OS memory pressure is the default threshold for the * low memory response. Setting pref "browser.lowMemoryResponseOnWarn" to true * changes the memory response to occur at the "warning" level which is less * severe than "critical". When entering the critical level, we begin polling * the memory pressure level every 'n' milliseconds (specified via the pref * "browser.lowMemoryPollingIntervalMS"). Each time the poller wakes up and * finds the OS still under memory pressure, the low memory response is * executed. * * By default, the memory pressure response is, in order, to * 1) call nsITabUnloader::UnloadTabAsync(), * 2) if no tabs could be unloaded, issue a Gecko * MemoryPressureState::LowMemory notification. * The response can be changed via the pref "browser.lowMemoryResponseMask" to * limit the actions to only tab unloading or Gecko memory pressure * notifications. * * Polling occurs on the main thread because, at each polling interval, we * call into the tab unloader which requires being on the main thread. * Polling only occurs while under OS memory pressure at the critical (by * default) level.
*/ class nsAvailableMemoryWatcher final : public nsITimerCallback, public nsINamed, public nsAvailableMemoryWatcherBase { public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIOBSERVER
NS_DECL_NSITIMERCALLBACK
NS_DECL_NSINAMED
// Override OnUnloadAttemptCompleted() so that we can control whether // or not a Gecko memory-pressure event is sent after a tab unload attempt. // This method is called externally by the tab unloader after a tab unload // attempt. It is used internally when tab unloading is disabled in // mResponseMask.
nsresult OnUnloadAttemptCompleted(nsresult aResult) override;
// This enum represents the allowed values for the pref that controls // the low memory response - "browser.lowMemoryResponseMask". Specifically, // whether or not we unload tabs and/or issue the Gecko "memory-pressure" // internal notification. For tab unloading, the pref // "browser.tabs.unloadOnLowMemory" must also be set. enum ResponseMask {
eNone = 0x0,
eTabUnload = 0x1,
eInternalMemoryPressure = 0x2,
eAll = 0x3,
}; static constexpr char kResponseMask[] = "browser.lowMemoryResponseMask"; staticconst uint32_t kResponseMaskDefault; staticconst uint32_t kResponseMaskMax;
// Pref for controlling how often we wake up during an OS memory pressure // time period. At each wakeup, we unload tabs and issue the Gecko // "memory-pressure" internal notification. When not under OS memory pressure, // polling is disabled. static constexpr char kPollingIntervalMS[] = "browser.lowMemoryPollingIntervalMS"; staticconst uint32_t kPollingIntervalMaxMS; staticconst uint32_t kPollingIntervalMinMS; staticconst uint32_t kPollingIntervalDefaultMS;
// The memory pressure reported to the application by macOS.
MacMemoryPressureLevel mLevel;
// The OS memory pressure level that triggers the response.
MacMemoryPressureLevel mResponseLevel;
// The value of the kern.memorystatus_vm_pressure_level sysctl. The OS // notifies the application when the memory pressure level changes, // but the sysctl value can be read at any time. Unofficially, the sysctl // value corresponds to the OS memory pressure level with 4=>critical, // 2=>warning, and 1=>normal (values from kernel event.h file).
uint32_t mLevelSysctl; staticconstint kSysctlLevelNormal = 0x1; staticconstint kSysctlLevelWarning = 0x2; staticconstint kSysctlLevelCritical = 0x4;
// The value of the kern.memorystatus_level sysctl. Unofficially, // this is the percentage of available memory. (Also readable // via the undocumented memorystatus_get_level syscall.) int mAvailMemSysctl;
// The string representation of `mLevel`. i.e., normal, warning, or critical. // Set to "unset" until a memory pressure change is reported to the process // by the OS.
nsAutoCString mLevelStr;
// Timestamps for memory pressure level changes. Specifically, the Unix // time in string form. Saved as Unix time to allow comparisons with // the crash time.
nsAutoCString mNormalTimeStr;
nsAutoCString mWarningTimeStr;
nsAutoCString mCriticalTimeStr;
nsCOMPtr<nsITimer> mTimer; // non-null indicates the timer is active
// Users of nsAvailableMemoryWatcher should use // nsAvailableMemoryWatcherBase::GetSingleton() and not call Init directly.
MOZ_ASSERT(!mInitialized); if (mInitialized) { return NS_ERROR_ALREADY_INITIALIZED;
}
// Read response bitmask pref which (along with the main tab unloading // preference) controls whether or not tab unloading and Gecko (internal) // memory pressure notifications will be sent. The main tab unloading // preference must also be enabled for tab unloading to occur.
mResponseMask = Preferences::GetUint(kResponseMask, kResponseMaskDefault); if (mResponseMask > kResponseMaskMax) {
mResponseMask = kResponseMaskMax;
}
// Set the initial state of all annotations for parent crash reports. // Content process crash reports are set when a crash occurs and // AddChildAnnotations() is called.
CrashReporter::RecordAnnotationNSCString(
CrashReporter::Annotation::MacMemoryPressure, mLevelStr);
CrashReporter::RecordAnnotationNSCString(
CrashReporter::Annotation::MacMemoryPressureNormalTime, mNormalTimeStr);
CrashReporter::RecordAnnotationNSCString(
CrashReporter::Annotation::MacMemoryPressureWarningTime, mWarningTimeStr);
CrashReporter::RecordAnnotationNSCString(
CrashReporter::Annotation::MacMemoryPressureCriticalTime,
mCriticalTimeStr);
CrashReporter::RecordAnnotationU32(
CrashReporter::Annotation::MacMemoryPressureSysctl, mLevelSysctl);
CrashReporter::RecordAnnotationU32(
CrashReporter::Annotation::MacAvailableMemorySysctl, mAvailMemSysctl);
// To support running experiments, handle pref // changes without requiring a browser restart.
rv = Preferences::AddStrongObserver(this, kResponseMask); if (NS_FAILED(rv)) {
NS_WARNING(
nsPrintfCString("Failed to add %s observer", kResponseMask).get());
}
rv = Preferences::AddStrongObserver(this, kPollingIntervalMS); if (NS_FAILED(rv)) {
NS_WARNING(
nsPrintfCString("Failed to add %s observer", kPollingIntervalMS).get());
}
rv = Preferences::AddStrongObserver(this, kResponseOnWarn); if (NS_FAILED(rv)) {
NS_WARNING(
nsPrintfCString("Failed to add %s observer", kResponseOnWarn).get());
}
// Use the memory pressure sysctl to initialize our memory pressure state.
MacMemoryPressureLevel initialLevel; switch (mLevelSysctl) { case kSysctlLevelNormal:
initialLevel = MacMemoryPressureLevel::Value::eNormal; break; case kSysctlLevelWarning:
initialLevel = MacMemoryPressureLevel::Value::eWarning; break; case kSysctlLevelCritical:
initialLevel = MacMemoryPressureLevel::Value::eCritical; break; default:
initialLevel = MacMemoryPressureLevel::Value::eUnexpected;
}
// Save the current memory pressure level.
AddParentAnnotation(CrashReporter::Annotation::MacMemoryPressure,
pressureLevelString);
// Save the time we transitioned to the current memory pressure level. if (pressureLevelKey.isSome()) {
AddParentAnnotation(pressureLevelKey.value(), timeChangedString);
}
// If 'aNewLevel' is not one of normal, warning, or critical, ASSERT // here so we can debug this scenario. For non-debug builds, ignore // the unexpected value which will be logged in crash reports.
MOZ_ASSERT(aNewLevel.IsNormal() || aNewLevel.IsWarningOrAbove());
if (mLevel == aNewLevel) { return;
}
// Start the memory pressure response if the new level is high enough // and the existing level was not. if ((mLevel < mResponseLevel) && (aNewLevel >= mResponseLevel)) {
UpdateLowMemoryTimeStamp();
LowMemoryResponse(); if (mResponseMask) {
StartPolling();
}
}
// End the memory pressure reponse if the new level is not high enough. if ((mLevel >= mResponseLevel) && (aNewLevel < mResponseLevel)) {
{
MutexAutoLock lock(mMutex);
RecordTelemetryEventOnHighMemory(lock);
}
StopPolling();
MP_LOG("Issuing MemoryPressureState::NoPressure");
NS_NotifyOfMemoryPressure(MemoryPressureState::NoPressure);
}
mLevel = aNewLevel;
if (!aIsInitialLevel) { // Sysctls are already read by ::Init().
ReadSysctls();
MP_LOG("level sysctl: %d, available memory: %d percent", mLevelSysctl,
mAvailMemSysctl);
}
UpdateParentAnnotations();
}
// Override OnUnloadAttemptCompleted() so that we can issue Gecko memory // pressure notifications only if eInternalMemoryPressure is set in // mResponseMask. When called from the tab unloader, an |aResult| value of // NS_OK indicates the tab unloader successfully unloaded a tab. // NS_ERROR_NOT_AVAILABLE indicates the tab unloader did not unload any tabs.
NS_IMETHODIMP
nsAvailableMemoryWatcher::OnUnloadAttemptCompleted(nsresult aResult) { // On MacOS we don't access these members offthread; however we do on other // OSes and so they are guarded by the mutex.
MutexAutoLock lock(mMutex); switch (aResult) { // A tab was unloaded successfully. case NS_OK:
MP_LOG("Tab unloaded");
++mNumOfTabUnloading; break;
// Either the tab unloader found no unloadable tabs OR we've been called // locally to explicitly issue the internal memory pressure event because // tab unloading is disabled in |mResponseMask|. In either case, attempt // to reduce memory use using the internal memory pressure notification. case NS_ERROR_NOT_AVAILABLE: if (mResponseMask & ResponseMask::eInternalMemoryPressure) {
++mNumOfMemoryPressure;
MP_LOG("Tab not unloaded");
MP_LOG("Issuing MemoryPressureState::LowMemory");
NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory);
} break;
// There was a pending task to unload a tab. case NS_ERROR_ABORT: break;
// Handle the response mask changing.
uint32_t responseMask = Preferences::GetUint(kResponseMask); if (mResponseMask != responseMask) {
mResponseMask = std::min(responseMask, kResponseMaskMax);
// Do we need to turn on polling? if (mResponseMask && (mLevel >= mResponseLevel) && !IsPolling()) {
StartPolling();
}
// Do we need to turn off polling? if (!mResponseMask && IsPolling()) {
StopPolling();
}
}
// Handle the response level changing.
MacMemoryPressureLevel newResponseLevel; if (Preferences::GetBool(kResponseOnWarn, kResponseLevelOnWarnDefault)) {
newResponseLevel = MacMemoryPressureLevel::Value::eWarning;
} else {
newResponseLevel = MacMemoryPressureLevel::Value::eCritical;
} if (newResponseLevel == mResponseLevel) { return;
}
// Do we need to turn on polling? if (mResponseMask && (newResponseLevel <= mLevel)) {
UpdateLowMemoryTimeStamp();
LowMemoryResponse();
StartPolling();
}
// Do we need to turn off polling? if (IsPolling() && (newResponseLevel > mLevel)) {
{
MutexAutoLock lock(mMutex);
RecordTelemetryEventOnHighMemory(lock);
}
StopPolling();
MP_LOG("Issuing MemoryPressureState::NoPressure");
NS_NotifyOfMemoryPressure(MemoryPressureState::NoPressure);
}
mResponseLevel = newResponseLevel;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.