Bug 1586236 - Use memory resource notifications to detect low memory scenarios on Windows. r=gsvelto

This patch uses the low memory resource notification facility to detect
scenarios where physical memory is running low without polling. This is a
significant change compared to the previous behavior which measured both
available virtual memory (only on 32-bit builds) and available commit space.

Since we're not trying to avoid OOMs anymore we don't save memory reports
anymore when hitting a low-memory condition.

Differential Revision: https://phabricator.services.mozilla.com/D110354
This commit is contained in:
Toshihito Kikuchi 2021-03-31 15:18:05 +00:00
Родитель 198d29dc22
Коммит 29312fb769
7 изменённых файлов: 170 добавлений и 161 удалений

Просмотреть файл

@ -157,8 +157,6 @@
"JSMainRuntimeRealmsUser",
"imagesContentUsedUncompressed",
"storageSQLite",
"lowMemoryEventsVirtual",
"lowMemoryEventsCommitSpace",
"lowMemoryEventsPhysical",
"ghostWindows",
"pageFaultsHard",

Просмотреть файл

@ -606,10 +606,10 @@ LauncherProcessState:
mozilla::LauncherRegistryInfo::EnableState enum
type: integer
LowCommitSpaceEvents:
LowPhysicalMemoryEvents:
description: >
Number of times the available memory tracker has detected a that
commit-space was running low. This is a Windows-specific annotation.
Number of times the available memory tracker has detected that free
physical memory is running low. This is a Windows-specific annotation.
type: integer
ping: true

Просмотреть файл

@ -9,9 +9,9 @@
#if defined(XP_WIN)
# include "mozilla/WindowsVersion.h"
# include "nsExceptionHandler.h"
# include "nsICrashReporter.h"
# include "nsIMemoryReporter.h"
# include "nsMemoryPressure.h"
# include "memoryapi.h"
#endif
#include "nsIObserver.h"
@ -22,6 +22,7 @@
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "mozilla/Mutex.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/Services.h"
#include "mozilla/Unused.h"
@ -51,13 +52,19 @@ typedef struct _HEAP_OPTIMIZE_RESOURCES_INFORMATION {
} HEAP_OPTIMIZE_RESOURCES_INFORMATION, *PHEAP_OPTIMIZE_RESOURCES_INFORMATION;
# endif
Atomic<uint32_t, MemoryOrdering::Relaxed> sNumLowVirtualMemEvents;
Atomic<uint32_t, MemoryOrdering::Relaxed> sNumLowCommitSpaceEvents;
Atomic<uint32_t, MemoryOrdering::Relaxed> sNumLowPhysicalMemEvents;
// This class is used to monitor low memory events delivered by Windows via
// memory resource notification objects. When we enter a low memory scenario
// the LowMemoryCallback() is invoked by Windows. This initial call triggers
// an nsITimer that polls to see when the low memory condition has been lifted.
// When it has, we'll stop polling and start waiting for the next
// LowMemoryCallback(). Meanwhile, the polling may be stopped and restarted by
// user-interaction events from the observer service.
class nsAvailableMemoryWatcher final : public nsIObserver,
public nsITimerCallback {
public:
NS_DECL_ISUPPORTS
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIOBSERVER
NS_DECL_NSITIMERCALLBACK
@ -65,41 +72,40 @@ class nsAvailableMemoryWatcher final : public nsIObserver,
nsresult Init();
private:
// Fire a low-memory notification if we have less than this many bytes of
// virtual address space available.
# if defined(HAVE_64BIT_BUILD)
static const size_t kLowVirtualMemoryThreshold = 0;
# else
static const size_t kLowVirtualMemoryThreshold = 384 * 1024 * 1024;
# endif
// Fire a low-memory notification if we have less than this many bytes of
// commit space (physical memory plus page file) left.
static const size_t kLowCommitSpaceThreshold = 384 * 1024 * 1024;
// Don't fire a low-memory notification more often than this interval.
static const uint32_t kLowMemoryNotificationIntervalMS = 10000;
// Poll the amount of free memory at this rate.
static const uint32_t kPollingIntervalMS = 1000;
// Observer topics we subscribe to, see below.
static const char* const kObserverTopics[];
static bool IsVirtualMemoryLow(const MEMORYSTATUSEX& aStat);
static bool IsCommitSpaceLow(const MEMORYSTATUSEX& aStat);
static VOID CALLBACK LowMemoryCallback(PVOID aContext, BOOLEAN aIsTimer);
static void RecordLowMemoryEvent();
~nsAvailableMemoryWatcher(){};
bool OngoingMemoryPressure() { return mUnderMemoryPressure; }
void AdjustPollingInterval(const bool aLowMemory);
void SendMemoryPressureEvent();
void MaybeSendMemoryPressureStopEvent();
void MaybeSaveMemoryReport();
void Shutdown();
bool RegisterMemoryResourceHandler();
void UnregisterMemoryResourceHandler();
void Shutdown(const MutexAutoLock&);
bool ListenForLowMemory();
void OnLowMemory(const MutexAutoLock&);
void OnHighMemory(const MutexAutoLock&);
bool IsMemoryLow() const;
void StartPollingIfUserInteracting();
void StopPolling();
void StopPollingIfUserIdle(const MutexAutoLock&);
void OnUserInteracting(const MutexAutoLock&);
void OnUserIdle(const MutexAutoLock&);
// The publicly available methods (::Observe() and ::Notify()) are called on
// the main thread while the ::LowMemoryCallback() method is called by an
// external thread. All functions called from those must acquire a lock on
// this mutex before accessing the object's fields to prevent races.
Mutex mMutex;
nsCOMPtr<nsITimer> mTimer;
HANDLE mLowMemoryHandle;
HANDLE mWaitHandle;
bool mPolling;
bool mInteracting;
bool mUnderMemoryPressure;
bool mSavedReport;
};
const char* const nsAvailableMemoryWatcher::kObserverTopics[] = {
@ -111,7 +117,13 @@ const char* const nsAvailableMemoryWatcher::kObserverTopics[] = {
NS_IMPL_ISUPPORTS(nsAvailableMemoryWatcher, nsIObserver, nsITimerCallback)
nsAvailableMemoryWatcher::nsAvailableMemoryWatcher()
: mTimer(nullptr), mUnderMemoryPressure(false), mSavedReport(false) {}
: mMutex("low memory callback mutex"),
mTimer(nullptr),
mLowMemoryHandle(nullptr),
mWaitHandle(nullptr),
mPolling(false),
mInteracting(false),
mUnderMemoryPressure(false) {}
nsresult nsAvailableMemoryWatcher::Init() {
mTimer = NS_NewTimer();
@ -119,18 +131,62 @@ nsresult nsAvailableMemoryWatcher::Init() {
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
MOZ_ASSERT(observerService);
if (!RegisterMemoryResourceHandler()) {
return NS_ERROR_FAILURE;
}
for (auto topic : kObserverTopics) {
nsresult rv = observerService->AddObserver(this, topic,
/* ownsWeak */ false);
NS_ENSURE_SUCCESS(rv, rv);
}
MOZ_TRY(mTimer->InitWithCallback(this, kPollingIntervalMS,
nsITimer::TYPE_REPEATING_SLACK));
return NS_OK;
}
void nsAvailableMemoryWatcher::Shutdown() {
// static
VOID CALLBACK nsAvailableMemoryWatcher::LowMemoryCallback(PVOID aContext,
BOOLEAN aIsTimer) {
nsAvailableMemoryWatcher* watcher =
static_cast<nsAvailableMemoryWatcher*>(aContext);
if (!aIsTimer) {
MutexAutoLock lock(watcher->mMutex);
watcher->OnLowMemory(lock);
}
}
// static
void nsAvailableMemoryWatcher::RecordLowMemoryEvent() {
sNumLowPhysicalMemEvents++;
CrashReporter::AnnotateCrashReport(
CrashReporter::Annotation::LowPhysicalMemoryEvents,
sNumLowPhysicalMemEvents);
}
bool nsAvailableMemoryWatcher::RegisterMemoryResourceHandler() {
mLowMemoryHandle =
::CreateMemoryResourceNotification(LowMemoryResourceNotification);
if (!mLowMemoryHandle) {
return false;
}
return ListenForLowMemory();
}
void nsAvailableMemoryWatcher::UnregisterMemoryResourceHandler() {
if (mWaitHandle) {
Unused << ::UnregisterWait(mWaitHandle);
mWaitHandle = nullptr;
}
if (mLowMemoryHandle) {
Unused << ::CloseHandle(mLowMemoryHandle);
mLowMemoryHandle = nullptr;
}
}
void nsAvailableMemoryWatcher::Shutdown(const MutexAutoLock&) {
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
MOZ_ASSERT(observerService);
@ -142,95 +198,88 @@ void nsAvailableMemoryWatcher::Shutdown() {
mTimer->Cancel();
mTimer = nullptr;
}
UnregisterMemoryResourceHandler();
}
/* static */
bool nsAvailableMemoryWatcher::IsVirtualMemoryLow(const MEMORYSTATUSEX& aStat) {
if ((kLowVirtualMemoryThreshold != 0) &&
(aStat.ullAvailVirtual < kLowVirtualMemoryThreshold)) {
sNumLowVirtualMemEvents++;
return true;
bool nsAvailableMemoryWatcher::ListenForLowMemory() {
if (mLowMemoryHandle && !mWaitHandle) {
return ::RegisterWaitForSingleObject(
&mWaitHandle, mLowMemoryHandle, LowMemoryCallback, this, INFINITE,
WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE);
}
return false;
}
/* static */
bool nsAvailableMemoryWatcher::IsCommitSpaceLow(const MEMORYSTATUSEX& aStat) {
if ((kLowCommitSpaceThreshold != 0) &&
(aStat.ullAvailPageFile < kLowCommitSpaceThreshold)) {
sNumLowCommitSpaceEvents++;
CrashReporter::AnnotateCrashReport(
CrashReporter::Annotation::LowCommitSpaceEvents,
uint32_t(sNumLowCommitSpaceEvents));
return true;
}
void nsAvailableMemoryWatcher::OnLowMemory(const MutexAutoLock&) {
mUnderMemoryPressure = true;
::UnregisterWait(mWaitHandle);
mWaitHandle = nullptr;
RecordLowMemoryEvent();
NS_DispatchEventualMemoryPressure(MemPressure_New);
StartPollingIfUserInteracting();
}
void nsAvailableMemoryWatcher::OnHighMemory(const MutexAutoLock&) {
mUnderMemoryPressure = false;
NS_DispatchEventualMemoryPressure(MemPressure_Stopping);
StopPolling();
ListenForLowMemory();
}
bool nsAvailableMemoryWatcher::IsMemoryLow() const {
BOOL lowMemory = FALSE;
if (::QueryMemoryResourceNotification(mLowMemoryHandle, &lowMemory)) {
return lowMemory;
}
return false;
}
void nsAvailableMemoryWatcher::SendMemoryPressureEvent() {
MemoryPressureState state =
OngoingMemoryPressure() ? MemPressure_Ongoing : MemPressure_New;
NS_DispatchEventualMemoryPressure(state);
}
void nsAvailableMemoryWatcher::MaybeSendMemoryPressureStopEvent() {
if (OngoingMemoryPressure()) {
NS_DispatchEventualMemoryPressure(MemPressure_Stopping);
}
}
void nsAvailableMemoryWatcher::MaybeSaveMemoryReport() {
if (!mSavedReport && OngoingMemoryPressure()) {
nsCOMPtr<nsICrashReporter> cr =
do_GetService("@mozilla.org/toolkit/crash-reporter;1");
if (cr) {
if (NS_SUCCEEDED(cr->SaveMemoryReport())) {
mSavedReport = true;
}
void nsAvailableMemoryWatcher::StartPollingIfUserInteracting() {
if (mInteracting && !mPolling) {
if (NS_SUCCEEDED(
mTimer->InitWithCallback(this, kLowMemoryNotificationIntervalMS,
nsITimer::TYPE_REPEATING_SLACK))) {
mPolling = true;
}
}
}
void nsAvailableMemoryWatcher::AdjustPollingInterval(const bool aLowMemory) {
if (aLowMemory) {
// We entered a low-memory state, wait for a longer interval before polling
// again as there's no point in rapidly sending further notifications.
mTimer->SetDelay(kLowMemoryNotificationIntervalMS);
} else if (OngoingMemoryPressure()) {
// We were under memory pressure but we're not anymore, resume polling at
// a faster pace.
mTimer->SetDelay(kPollingIntervalMS);
void nsAvailableMemoryWatcher::StopPolling() {
mTimer->Cancel();
mPolling = false;
}
void nsAvailableMemoryWatcher::StopPollingIfUserIdle(const MutexAutoLock&) {
if (!mInteracting) {
StopPolling();
}
}
// Timer callback, polls memory stats to detect low-memory conditions. This
// will send memory-pressure events if memory is running low and adjust the
// polling interval accordingly.
void nsAvailableMemoryWatcher::OnUserInteracting(const MutexAutoLock&) {
mInteracting = true;
if (mUnderMemoryPressure) {
StartPollingIfUserInteracting();
}
}
void nsAvailableMemoryWatcher::OnUserIdle(const MutexAutoLock&) {
mInteracting = false;
}
// Timer callback, polls the low memory resource notification to detect when
// we've freed enough memory or if we have to send more memory pressure events.
// Polling stops automatically when the user is not interacting with the UI.
NS_IMETHODIMP
nsAvailableMemoryWatcher::Notify(nsITimer* aTimer) {
MEMORYSTATUSEX stat;
stat.dwLength = sizeof(stat);
bool success = GlobalMemoryStatusEx(&stat);
MutexAutoLock lock(mMutex);
StopPollingIfUserIdle(lock);
if (success) {
bool lowMemory = IsVirtualMemoryLow(stat) || IsCommitSpaceLow(stat);
if (lowMemory) {
SendMemoryPressureEvent();
} else {
MaybeSendMemoryPressureStopEvent();
}
if (lowMemory) {
MaybeSaveMemoryReport();
} else {
mSavedReport = false; // Save a new report if memory gets low again
}
AdjustPollingInterval(lowMemory);
mUnderMemoryPressure = lowMemory;
if (IsMemoryLow()) {
NS_DispatchEventualMemoryPressure(MemPressure_Ongoing);
} else {
OnHighMemory(lock);
}
return NS_OK;
@ -242,13 +291,14 @@ nsAvailableMemoryWatcher::Notify(nsITimer* aTimer) {
NS_IMETHODIMP
nsAvailableMemoryWatcher::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
MutexAutoLock lock(mMutex);
if (strcmp(aTopic, "quit-application") == 0) {
Shutdown();
Shutdown(lock);
} else if (strcmp(aTopic, "user-interaction-inactive") == 0) {
mTimer->Cancel();
OnUserIdle(lock);
} else if (strcmp(aTopic, "user-interaction-active") == 0) {
mTimer->InitWithCallback(this, kPollingIntervalMS,
nsITimer::TYPE_REPEATING_SLACK);
OnUserInteracting(lock);
} else {
MOZ_ASSERT_UNREACHABLE("Unknown topic");
}
@ -256,12 +306,8 @@ nsAvailableMemoryWatcher::Observe(nsISupports* aSubject, const char* aTopic,
return NS_OK;
}
static int64_t LowMemoryEventsVirtualDistinguishedAmount() {
return sNumLowVirtualMemEvents;
}
static int64_t LowMemoryEventsCommitSpaceDistinguishedAmount() {
return sNumLowCommitSpaceEvents;
static int64_t LowMemoryEventsPhysicalDistinguishedAmount() {
return sNumLowPhysicalMemEvents;
}
class LowEventsReporter final : public nsIMemoryReporter {
@ -274,22 +320,12 @@ class LowEventsReporter final : public nsIMemoryReporter {
nsISupports* aData, bool aAnonymize) override {
// clang-format off
MOZ_COLLECT_REPORT(
"low-memory-events/virtual", KIND_OTHER, UNITS_COUNT_CUMULATIVE,
LowMemoryEventsVirtualDistinguishedAmount(),
"Number of low-virtual-memory events fired since startup. We fire such an "
"event if we notice there is less than predefined amount of virtual address "
"space available (if zero, this behavior is disabled, see "
"xpcom/base/AvailableMemoryTracker.cpp). The process will probably crash if "
"it runs out of virtual address space, so this event is dire.");
MOZ_COLLECT_REPORT(
"low-memory-events/commit-space", KIND_OTHER, UNITS_COUNT_CUMULATIVE,
LowMemoryEventsCommitSpaceDistinguishedAmount(),
"Number of low-commit-space events fired since startup. We fire such an "
"event if we notice there is less than a predefined amount of commit space "
"available (if zero, this behavior is disabled, see "
"xpcom/base/AvailableMemoryTracker.cpp). Windows will likely kill the process "
"if it runs out of commit space, so this event is dire.");
"low-memory-events/physical", KIND_OTHER, UNITS_COUNT_CUMULATIVE,
LowMemoryEventsPhysicalDistinguishedAmount(),
"Number of low-physical-memory events fired since startup. We fire such an "
"event when a windows low memory resource notification is signaled. The "
"machine will start to page if it runs out of physical memory. This may "
"cause it to run slowly, but it shouldn't cause it to crash.");
// clang-format on
return NS_OK;
@ -405,11 +441,8 @@ void Init() {
watcher->Init();
#if defined(XP_WIN)
RegisterStrongMemoryReporter(new LowEventsReporter());
RegisterLowMemoryEventsVirtualDistinguishedAmount(
LowMemoryEventsVirtualDistinguishedAmount);
RegisterLowMemoryEventsCommitSpaceDistinguishedAmount(
LowMemoryEventsCommitSpaceDistinguishedAmount);
RegisterLowMemoryEventsPhysicalDistinguishedAmount(
LowMemoryEventsPhysicalDistinguishedAmount);
if (XRE_IsParentProcess()) {
RefPtr<nsAvailableMemoryWatcher> poller = new nsAvailableMemoryWatcher();

Просмотреть файл

@ -257,10 +257,6 @@ nsresult MemoryTelemetry::GatherReports(
UNITS_BYTES);
RECORD(MEMORY_STORAGE_SQLITE, StorageSQLite, UNITS_BYTES);
#ifdef XP_WIN
RECORD(LOW_MEMORY_EVENTS_VIRTUAL, LowMemoryEventsVirtual,
UNITS_COUNT_CUMULATIVE);
RECORD(LOW_MEMORY_EVENTS_COMMIT_SPACE, LowMemoryEventsCommitSpace,
UNITS_COUNT_CUMULATIVE);
RECORD(LOW_MEMORY_EVENTS_PHYSICAL, LowMemoryEventsPhysical,
UNITS_COUNT_CUMULATIVE);
#endif

Просмотреть файл

@ -373,9 +373,9 @@ interface nsIMemoryReporterManager : nsISupports
*
* |storageSQLite| (UNITS_BYTES) Memory used by SQLite.
*
* |lowMemoryEvents{Virtual,CommitSpace,Physical}| (UNITS_COUNT_CUMULATIVE)
* The number of low-{virtual,commit-space,physical}-memory events that have
* occurred since the process started.
* |lowMemoryEventsPhysical| (UNITS_COUNT_CUMULATIVE)
* The number of low-physical-memory events that have occurred since the
* process started.
*
* |ghostWindows| (UNITS_COUNT) A cached value of the number of ghost
* windows. This should have been updated within the past 60s.
@ -404,8 +404,6 @@ interface nsIMemoryReporterManager : nsISupports
[must_use] readonly attribute int64_t storageSQLite;
[must_use] readonly attribute int64_t lowMemoryEventsVirtual;
[must_use] readonly attribute int64_t lowMemoryEventsCommitSpace;
[must_use] readonly attribute int64_t lowMemoryEventsPhysical;
[must_use] readonly attribute int64_t ghostWindows;
@ -506,8 +504,6 @@ DECL_UNREGISTER_DISTINGUISHED_AMOUNT(ImagesContentUsedUncompressed)
DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, StorageSQLite)
DECL_UNREGISTER_DISTINGUISHED_AMOUNT(StorageSQLite)
DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsVirtual)
DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsCommitSpace)
DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsPhysical)
DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, GhostWindows)

Просмотреть файл

@ -2495,16 +2495,6 @@ nsMemoryReporterManager::GetStorageSQLite(int64_t* aAmount) {
return GetInfallibleAmount(mAmountFns.mStorageSQLite, aAmount);
}
NS_IMETHODIMP
nsMemoryReporterManager::GetLowMemoryEventsVirtual(int64_t* aAmount) {
return GetInfallibleAmount(mAmountFns.mLowMemoryEventsVirtual, aAmount);
}
NS_IMETHODIMP
nsMemoryReporterManager::GetLowMemoryEventsCommitSpace(int64_t* aAmount) {
return GetInfallibleAmount(mAmountFns.mLowMemoryEventsCommitSpace, aAmount);
}
NS_IMETHODIMP
nsMemoryReporterManager::GetLowMemoryEventsPhysical(int64_t* aAmount) {
return GetInfallibleAmount(mAmountFns.mLowMemoryEventsPhysical, aAmount);
@ -2749,8 +2739,6 @@ DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(ImagesContentUsedUncompressed)
DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, StorageSQLite)
DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(StorageSQLite)
DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsVirtual)
DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsCommitSpace)
DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsPhysical)
DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, GhostWindows)

Просмотреть файл

@ -157,8 +157,6 @@ class nsMemoryReporterManager final : public nsIMemoryReporterManager,
mozilla::InfallibleAmountFn mStorageSQLite = nullptr;
mozilla::InfallibleAmountFn mLowMemoryEventsVirtual = nullptr;
mozilla::InfallibleAmountFn mLowMemoryEventsCommitSpace = nullptr;
mozilla::InfallibleAmountFn mLowMemoryEventsPhysical = nullptr;
mozilla::InfallibleAmountFn mGhostWindows = nullptr;