зеркало из https://github.com/mozilla/gecko-dev.git
626 строки
22 KiB
C++
626 строки
22 KiB
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/. */
|
|
|
|
#include <sys/sysctl.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
|
|
#include "AvailableMemoryWatcher.h"
|
|
#include "Logging.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "nsICrashReporter.h"
|
|
#include "nsISupports.h"
|
|
#include "nsITimer.h"
|
|
#include "nsMemoryPressure.h"
|
|
#include "nsPrintfCString.h"
|
|
|
|
#define MP_LOG(...) MOZ_LOG(gMPLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
|
|
static mozilla::LazyLogModule gMPLog("MemoryPressure");
|
|
|
|
namespace mozilla {
|
|
|
|
/*
|
|
* 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
|
|
|
|
nsAvailableMemoryWatcher();
|
|
nsresult Init() override;
|
|
|
|
void OnMemoryPressureChanged(MacMemoryPressureLevel aLevel) override;
|
|
void AddChildAnnotations(
|
|
const UniquePtr<ipc::CrashReporterHost>& aCrashReporter) override;
|
|
|
|
private:
|
|
~nsAvailableMemoryWatcher(){};
|
|
|
|
void OnMemoryPressureChangedInternal(MacMemoryPressureLevel aNewLevel,
|
|
bool aIsInitialLevel);
|
|
|
|
// 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;
|
|
|
|
void OnShutdown();
|
|
void OnPrefChange();
|
|
|
|
void InitParentAnnotations();
|
|
void UpdateParentAnnotations();
|
|
|
|
void AddParentAnnotation(CrashReporter::Annotation aAnnotation,
|
|
nsAutoCString aString) {
|
|
CrashReporter::AnnotateCrashReport(aAnnotation, aString);
|
|
}
|
|
void AddParentAnnotation(CrashReporter::Annotation aAnnotation,
|
|
uint32_t aData) {
|
|
CrashReporter::AnnotateCrashReport(aAnnotation, aData);
|
|
}
|
|
|
|
void LowMemoryResponse();
|
|
void StartPolling();
|
|
void StopPolling();
|
|
void RestartPolling();
|
|
inline bool IsPolling() { return mTimer; }
|
|
|
|
void ReadSysctls();
|
|
|
|
// 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";
|
|
static const uint32_t kResponseMaskDefault;
|
|
static const 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";
|
|
static const uint32_t kPollingIntervalMaxMS;
|
|
static const uint32_t kPollingIntervalMinMS;
|
|
static const uint32_t kPollingIntervalDefaultMS;
|
|
|
|
static constexpr char kResponseOnWarn[] = "browser.lowMemoryResponseOnWarn";
|
|
static const bool kResponseLevelOnWarnDefault = false;
|
|
|
|
// Init has been called.
|
|
bool mInitialized;
|
|
|
|
// 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;
|
|
static const int kSysctlLevelNormal = 0x1;
|
|
static const int kSysctlLevelWarning = 0x2;
|
|
static const int 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
|
|
|
|
// Saved pref values.
|
|
uint32_t mPollingInterval;
|
|
uint32_t mResponseMask;
|
|
};
|
|
|
|
const uint32_t nsAvailableMemoryWatcher::kResponseMaskDefault =
|
|
ResponseMask::eAll;
|
|
const uint32_t nsAvailableMemoryWatcher::kResponseMaskMax = ResponseMask::eAll;
|
|
|
|
// 10 seconds
|
|
const uint32_t nsAvailableMemoryWatcher::kPollingIntervalDefaultMS = 10'000;
|
|
// 10 minutes
|
|
const uint32_t nsAvailableMemoryWatcher::kPollingIntervalMaxMS = 600'000;
|
|
// 100 milliseconds
|
|
const uint32_t nsAvailableMemoryWatcher::kPollingIntervalMinMS = 100;
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED(nsAvailableMemoryWatcher,
|
|
nsAvailableMemoryWatcherBase, nsIObserver,
|
|
nsITimerCallback, nsINamed)
|
|
|
|
nsAvailableMemoryWatcher::nsAvailableMemoryWatcher()
|
|
: mInitialized(false),
|
|
mLevel(MacMemoryPressureLevel::Value::eUnset),
|
|
mResponseLevel(MacMemoryPressureLevel::Value::eCritical),
|
|
mLevelSysctl(0xFFFFFFFF),
|
|
mAvailMemSysctl(-1),
|
|
mLevelStr("Unset"),
|
|
mNormalTimeStr("Unset"),
|
|
mWarningTimeStr("Unset"),
|
|
mCriticalTimeStr("Unset"),
|
|
mPollingInterval(0),
|
|
mResponseMask(ResponseMask::eAll) {}
|
|
|
|
nsresult nsAvailableMemoryWatcher::Init() {
|
|
nsresult rv = nsAvailableMemoryWatcherBase::Init();
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
// Users of nsAvailableMemoryWatcher should use
|
|
// nsAvailableMemoryWatcherBase::GetSingleton() and not call Init directly.
|
|
MOZ_ASSERT(!mInitialized);
|
|
if (mInitialized) {
|
|
return NS_ERROR_ALREADY_INITIALIZED;
|
|
}
|
|
|
|
// Read polling frequency pref
|
|
mPollingInterval =
|
|
Preferences::GetUint(kPollingIntervalMS, kPollingIntervalDefaultMS);
|
|
mPollingInterval = std::clamp(mPollingInterval, kPollingIntervalMinMS,
|
|
kPollingIntervalMaxMS);
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Read response level pref
|
|
if (Preferences::GetBool(kResponseOnWarn, kResponseLevelOnWarnDefault)) {
|
|
mResponseLevel = MacMemoryPressureLevel::Value::eWarning;
|
|
} else {
|
|
mResponseLevel = MacMemoryPressureLevel::Value::eCritical;
|
|
}
|
|
|
|
ReadSysctls();
|
|
MP_LOG("Initial memory pressure sysctl: %d", mLevelSysctl);
|
|
MP_LOG("Initial available memory sysctl: %d", mAvailMemSysctl);
|
|
|
|
// 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::AnnotateCrashReport(
|
|
CrashReporter::Annotation::MacMemoryPressure, mLevelStr);
|
|
CrashReporter::AnnotateCrashReport(
|
|
CrashReporter::Annotation::MacMemoryPressureNormalTime, mNormalTimeStr);
|
|
CrashReporter::AnnotateCrashReport(
|
|
CrashReporter::Annotation::MacMemoryPressureWarningTime, mWarningTimeStr);
|
|
CrashReporter::AnnotateCrashReport(
|
|
CrashReporter::Annotation::MacMemoryPressureCriticalTime,
|
|
mCriticalTimeStr);
|
|
CrashReporter::AnnotateCrashReport(
|
|
CrashReporter::Annotation::MacMemoryPressureSysctl, mLevelSysctl);
|
|
CrashReporter::AnnotateCrashReport(
|
|
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;
|
|
}
|
|
|
|
OnMemoryPressureChangedInternal(initialLevel, /* aIsInitialLevel */ true);
|
|
mInitialized = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
already_AddRefed<nsAvailableMemoryWatcherBase> CreateAvailableMemoryWatcher() {
|
|
// Users of nsAvailableMemoryWatcher should use
|
|
// nsAvailableMemoryWatcherBase::GetSingleton().
|
|
RefPtr watcher(new nsAvailableMemoryWatcher());
|
|
watcher->Init();
|
|
return watcher.forget();
|
|
}
|
|
|
|
// Update the memory pressure level, level change timestamps, and sysctl
|
|
// level crash report annotations.
|
|
void nsAvailableMemoryWatcher::UpdateParentAnnotations() {
|
|
// Generate a string representation of the current Unix time.
|
|
time_t timeChanged = time(NULL);
|
|
nsAutoCString timeChangedString;
|
|
timeChangedString =
|
|
nsPrintfCString("%" PRIu64, static_cast<uint64_t>(timeChanged));
|
|
|
|
nsAutoCString pressureLevelString;
|
|
Maybe<CrashReporter::Annotation> pressureLevelKey;
|
|
|
|
switch (mLevel.GetValue()) {
|
|
case MacMemoryPressureLevel::Value::eNormal:
|
|
mNormalTimeStr = timeChangedString;
|
|
pressureLevelString = "Normal";
|
|
pressureLevelKey.emplace(
|
|
CrashReporter::Annotation::MacMemoryPressureNormalTime);
|
|
break;
|
|
case MacMemoryPressureLevel::Value::eWarning:
|
|
mWarningTimeStr = timeChangedString;
|
|
pressureLevelString = "Warning";
|
|
pressureLevelKey.emplace(
|
|
CrashReporter::Annotation::MacMemoryPressureWarningTime);
|
|
break;
|
|
case MacMemoryPressureLevel::Value::eCritical:
|
|
mCriticalTimeStr = timeChangedString;
|
|
pressureLevelString = "Critical";
|
|
pressureLevelKey.emplace(
|
|
CrashReporter::Annotation::MacMemoryPressureCriticalTime);
|
|
break;
|
|
default:
|
|
pressureLevelString = "Unexpected";
|
|
break;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
AddParentAnnotation(CrashReporter::Annotation::MacMemoryPressureSysctl,
|
|
mLevelSysctl);
|
|
AddParentAnnotation(CrashReporter::Annotation::MacAvailableMemorySysctl,
|
|
mAvailMemSysctl);
|
|
}
|
|
|
|
void nsAvailableMemoryWatcher::ReadSysctls() {
|
|
// Pressure level
|
|
uint32_t level;
|
|
size_t size = sizeof(level);
|
|
if (sysctlbyname("kern.memorystatus_vm_pressure_level", &level, &size, NULL,
|
|
0) == -1) {
|
|
MP_LOG("Failure reading memory pressure sysctl");
|
|
}
|
|
mLevelSysctl = level;
|
|
|
|
// Available memory percent
|
|
int availPercent;
|
|
size = sizeof(availPercent);
|
|
if (sysctlbyname("kern.memorystatus_level", &availPercent, &size, NULL, 0) ==
|
|
-1) {
|
|
MP_LOG("Failure reading available memory level");
|
|
}
|
|
mAvailMemSysctl = availPercent;
|
|
}
|
|
|
|
/* virtual */
|
|
void nsAvailableMemoryWatcher::OnMemoryPressureChanged(
|
|
MacMemoryPressureLevel aNewLevel) {
|
|
MOZ_ASSERT(mInitialized);
|
|
OnMemoryPressureChangedInternal(aNewLevel, /* aIsInitialLevel */ false);
|
|
}
|
|
|
|
void nsAvailableMemoryWatcher::OnMemoryPressureChangedInternal(
|
|
MacMemoryPressureLevel aNewLevel, bool aIsInitialLevel) {
|
|
MOZ_ASSERT(mInitialized || aIsInitialLevel);
|
|
MP_LOG("MemoryPressureChange: existing level: %s, new level: %s",
|
|
mLevel.ToString(), aNewLevel.ToString());
|
|
|
|
// 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)) {
|
|
RecordTelemetryEventOnHighMemory();
|
|
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();
|
|
}
|
|
|
|
/* virtual */
|
|
// Add all annotations to the provided crash reporter instance.
|
|
void nsAvailableMemoryWatcher::AddChildAnnotations(
|
|
const UniquePtr<ipc::CrashReporterHost>& aCrashReporter) {
|
|
aCrashReporter->AddAnnotation(CrashReporter::Annotation::MacMemoryPressure,
|
|
mLevelStr);
|
|
aCrashReporter->AddAnnotation(
|
|
CrashReporter::Annotation::MacMemoryPressureNormalTime, mNormalTimeStr);
|
|
aCrashReporter->AddAnnotation(
|
|
CrashReporter::Annotation::MacMemoryPressureWarningTime, mWarningTimeStr);
|
|
aCrashReporter->AddAnnotation(
|
|
CrashReporter::Annotation::MacMemoryPressureCriticalTime,
|
|
mCriticalTimeStr);
|
|
aCrashReporter->AddAnnotation(
|
|
CrashReporter::Annotation::MacMemoryPressureSysctl, mLevelSysctl);
|
|
aCrashReporter->AddAnnotation(
|
|
CrashReporter::Annotation::MacAvailableMemorySysctl, mAvailMemSysctl);
|
|
}
|
|
|
|
void nsAvailableMemoryWatcher::LowMemoryResponse() {
|
|
if (mResponseMask & ResponseMask::eTabUnload) {
|
|
MP_LOG("Attempting tab unload");
|
|
mTabUnloader->UnloadTabAsync();
|
|
} else {
|
|
// Re-use OnUnloadAttemptCompleted() to issue the internal
|
|
// memory pressure event.
|
|
OnUnloadAttemptCompleted(NS_ERROR_NOT_AVAILABLE);
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsAvailableMemoryWatcher::Notify(nsITimer* aTimer) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mLevel >= mResponseLevel);
|
|
LowMemoryResponse();
|
|
return NS_OK;
|
|
}
|
|
|
|
// 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) {
|
|
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;
|
|
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("Unexpected aResult");
|
|
break;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsAvailableMemoryWatcher::Observe(nsISupports* aSubject, const char* aTopic,
|
|
const char16_t* aData) {
|
|
nsresult rv = nsAvailableMemoryWatcherBase::Observe(aSubject, aTopic, aData);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
if (strcmp(aTopic, "xpcom-shutdown") == 0) {
|
|
OnShutdown();
|
|
} else if (strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
|
|
OnPrefChange();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsAvailableMemoryWatcher::OnShutdown() {
|
|
StopPolling();
|
|
Preferences::RemoveObserver(this, kResponseMask);
|
|
Preferences::RemoveObserver(this, kPollingIntervalMS);
|
|
}
|
|
|
|
void nsAvailableMemoryWatcher::OnPrefChange() {
|
|
MP_LOG("OnPrefChange()");
|
|
// Handle the polling interval changing.
|
|
uint32_t pollingInterval = Preferences::GetUint(kPollingIntervalMS);
|
|
if (pollingInterval != mPollingInterval) {
|
|
mPollingInterval = std::clamp(pollingInterval, kPollingIntervalMinMS,
|
|
kPollingIntervalMaxMS);
|
|
RestartPolling();
|
|
}
|
|
|
|
// 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)) {
|
|
RecordTelemetryEventOnHighMemory();
|
|
StopPolling();
|
|
MP_LOG("Issuing MemoryPressureState::NoPressure");
|
|
NS_NotifyOfMemoryPressure(MemoryPressureState::NoPressure);
|
|
}
|
|
mResponseLevel = newResponseLevel;
|
|
}
|
|
|
|
void nsAvailableMemoryWatcher::StartPolling() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
if (!mTimer) {
|
|
MP_LOG("Starting poller");
|
|
mTimer = NS_NewTimer();
|
|
if (mTimer) {
|
|
mTimer->InitWithCallback(this, mPollingInterval,
|
|
nsITimer::TYPE_REPEATING_SLACK);
|
|
}
|
|
}
|
|
}
|
|
|
|
void nsAvailableMemoryWatcher::StopPolling() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
if (mTimer) {
|
|
MP_LOG("Pausing poller");
|
|
mTimer->Cancel();
|
|
mTimer = nullptr;
|
|
}
|
|
}
|
|
|
|
void nsAvailableMemoryWatcher::RestartPolling() {
|
|
if (IsPolling()) {
|
|
StopPolling();
|
|
StartPolling();
|
|
} else {
|
|
MOZ_ASSERT(!mTimer);
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsAvailableMemoryWatcher::GetName(nsACString& aName) {
|
|
aName.AssignLiteral("nsAvailableMemoryWatcher");
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace mozilla
|