--- a/xpcom/base/AvailableMemoryTracker.cpp
+++ b/xpcom/base/AvailableMemoryTracker.cpp
@@ -12,39 +12,46 @@
#include "nsIMemoryReporter.h"
#include "nsMemoryPressure.h"
#endif
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsIRunnable.h"
#include "nsISupports.h"
+#include "nsITimer.h"
#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
-#include "mozilla/Preferences.h"
+#include "mozilla/ResultExtensions.h"
#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
#if defined(XP_WIN)
# include "nsWindowsDllInterceptor.h"
# include <windows.h>
#endif
#if defined(MOZ_MEMORY)
# include "mozmemory.h"
#endif // MOZ_MEMORY
using namespace mozilla;
namespace {
-#if defined(_M_IX86) && defined(XP_WIN)
+#if defined(XP_WIN)
// 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 = 256 * 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 = 256 * 1024 * 1024;
// Fire a low-memory notification if we have less than this many bytes of
// physical memory available on the whole machine.
static const size_t kLowPhysicalMemoryThreshold = 0;
@@ -52,16 +59,18 @@ static const size_t kLowPhysicalMemoryTh
// Don't fire a low-memory notification because of low available physical
// memory or low commit space more often than this interval.
static const uint32_t kLowMemoryNotificationIntervalMS = 10000;
Atomic<uint32_t, MemoryOrdering::Relaxed> sNumLowVirtualMemEvents;
Atomic<uint32_t, MemoryOrdering::Relaxed> sNumLowCommitSpaceEvents;
Atomic<uint32_t, MemoryOrdering::Relaxed> sNumLowPhysicalMemEvents;
+#if !defined(HAVE_64BIT_BUILD)
+
WindowsDllInterceptor sKernel32Intercept;
WindowsDllInterceptor sGdi32Intercept;
// Has Init() been called?
bool sInitialized = false;
// Has Activate() been called? The hooks don't do anything until this happens.
bool sHooksActive = false;
@@ -248,16 +257,199 @@ CreateDIBSectionHook(HDC aDC,
if (doCheck) {
CheckMemAvailable();
}
return result;
}
+#else
+
+class nsAvailableMemoryWatcher final : public nsIObserver,
+ public nsITimerCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+
+ nsresult Init();
+
+private:
+ // Poll the amount of free memory at this rate.
+ static const uint32_t kPollingIntervalMS = 1000;
+
+ // Observer topics we subscribe to
+ static const char* const kObserverTopics[];
+
+ static bool IsVirtualMemoryLow(const MEMORYSTATUSEX& aStat);
+ static bool IsCommitSpaceLow(const MEMORYSTATUSEX& aStat);
+ static bool IsPhysicalMemoryLow(const MEMORYSTATUSEX& aStat);
+
+ ~nsAvailableMemoryWatcher() {};
+ void AdjustPollingInterval(const bool aLowMemory);
+ void SendMemoryPressureEvent();
+ void Shutdown();
+
+ nsCOMPtr<nsITimer> mTimer;
+ bool mUnderMemoryPressure;
+};
+
+const char* const nsAvailableMemoryWatcher::kObserverTopics[] = {
+ "quit-application",
+ "user-interaction-active",
+ "user-interaction-inactive",
+};
+
+NS_IMPL_ISUPPORTS(nsAvailableMemoryWatcher, nsIObserver, nsITimerCallback)
+
+nsresult
+nsAvailableMemoryWatcher::Init()
+{
+ mTimer = NS_NewTimer();
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ MOZ_ASSERT(observerService);
+
+ 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()
+{
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ MOZ_ASSERT(observerService);
+
+ for (auto topic : kObserverTopics) {
+ Unused << observerService->RemoveObserver(this, topic);
+ }
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+/* static */ bool
+nsAvailableMemoryWatcher::IsVirtualMemoryLow(const MEMORYSTATUSEX& aStat)
+{
+ if ((kLowVirtualMemoryThreshold != 0) &&
+ (aStat.ullAvailVirtual < kLowVirtualMemoryThreshold)) {
+ sNumLowVirtualMemEvents++;
+ return true;
+ }
+
+ return false;
+}
+
+/* static */ bool
+nsAvailableMemoryWatcher::IsCommitSpaceLow(const MEMORYSTATUSEX& aStat)
+{
+ if ((kLowCommitSpaceThreshold != 0) &&
+ (aStat.ullAvailPageFile < kLowCommitSpaceThreshold)) {
+ sNumLowCommitSpaceEvents++;
+ return true;
+ }
+
+ return false;
+}
+
+/* static */ bool
+nsAvailableMemoryWatcher::IsPhysicalMemoryLow(const MEMORYSTATUSEX& aStat)
+{
+ if ((kLowPhysicalMemoryThreshold != 0) &&
+ (aStat.ullAvailPhys < kLowPhysicalMemoryThreshold)) {
+ sNumLowPhysicalMemEvents++;
+ return true;
+ }
+
+ return false;
+}
+
+void
+nsAvailableMemoryWatcher::SendMemoryPressureEvent()
+{
+ MemoryPressureState state = mUnderMemoryPressure ? MemPressure_Ongoing
+ : MemPressure_New;
+ NS_DispatchEventualMemoryPressure(state);
+}
+
+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 (mUnderMemoryPressure) {
+ // We were under memory pressure but we're not anymore, resume polling at
+ // a faster pace.
+ mTimer->SetDelay(kPollingIntervalMS);
+ }
+}
+
+// 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.
+NS_IMETHODIMP
+nsAvailableMemoryWatcher::Notify(nsITimer* aTimer)
+{
+ MEMORYSTATUSEX stat;
+ stat.dwLength = sizeof(stat);
+ bool success = GlobalMemoryStatusEx(&stat);
+
+ if (success) {
+ bool lowMemory =
+ IsVirtualMemoryLow(stat) ||
+ IsCommitSpaceLow(stat) ||
+ IsPhysicalMemoryLow(stat);
+
+ if (lowMemory) {
+ SendMemoryPressureEvent();
+ }
+
+ AdjustPollingInterval(lowMemory);
+ mUnderMemoryPressure = lowMemory;
+ }
+
+ return NS_OK;
+}
+
+// Observer service callback, used to stop the polling timer when the user
+// stops interacting with Firefox and resuming it when they interact again.
+// Also used to shut down the service if the application is quitting.
+NS_IMETHODIMP
+nsAvailableMemoryWatcher::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ if (strcmp(aTopic, "quit-application") == 0) {
+ Shutdown();
+ } else if (strcmp(aTopic, "user-interaction-inactive") == 0) {
+ mTimer->Cancel();
+ } else if (strcmp(aTopic, "user-interaction-active") == 0) {
+ mTimer->InitWithCallback(this, kPollingIntervalMS,
+ nsITimer::TYPE_REPEATING_SLACK);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unknown topic");
+ }
+
+ return NS_OK;
+}
+
+#endif // !defined(HAVE_64BIT_BUILD)
+
static int64_t
LowMemoryEventsVirtualDistinguishedAmount()
{
return sNumLowVirtualMemEvents;
}
static int64_t
LowMemoryEventsPhysicalDistinguishedAmount()
@@ -302,17 +494,17 @@ public:
"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.");
return NS_OK;
}
};
NS_IMPL_ISUPPORTS(LowEventsReporter, nsIMemoryReporter)
-#endif // defined(_M_IX86) && defined(XP_WIN)
+#endif // defined(XP_WIN)
/**
* This runnable is executed in response to a memory-pressure event; we spin
* the event-loop when receiving the memory-pressure event in the hope that
* other observers will synchronously free some memory that we'll be able to
* purge here.
*/
class nsJemallocFreeDirtyPagesRunnable final : public nsIRunnable
@@ -391,46 +583,56 @@ nsMemoryPressureWatcher::Observe(nsISupp
} // namespace
namespace mozilla {
namespace AvailableMemoryTracker {
void
Activate()
{
-#if defined(_M_IX86) && defined(XP_WIN)
+#if defined(XP_WIN) && !defined(HAVE_64BIT_BUILD)
MOZ_ASSERT(sInitialized);
MOZ_ASSERT(!sHooksActive);
RegisterStrongMemoryReporter(new LowEventsReporter());
RegisterLowMemoryEventsVirtualDistinguishedAmount(
LowMemoryEventsVirtualDistinguishedAmount);
RegisterLowMemoryEventsPhysicalDistinguishedAmount(
LowMemoryEventsPhysicalDistinguishedAmount);
sHooksActive = true;
-#endif
+#endif // defined(XP_WIN) && !defined(HAVE_64BIT_BUILD)
- // This object is held alive by the observer service.
+ // The watchers are held alive by the observer service.
RefPtr<nsMemoryPressureWatcher> watcher = new nsMemoryPressureWatcher();
watcher->Init();
+
+#if defined(XP_WIN) && defined(HAVE_64BIT_BUILD)
+ if (XRE_IsParentProcess()) {
+ RefPtr<nsAvailableMemoryWatcher> poller = new nsAvailableMemoryWatcher();
+
+ if (NS_FAILED(poller->Init())) {
+ NS_WARNING("Could not start the available memory watcher");
+ }
+ }
+#endif // defined(XP_WIN) && defined(HAVE_64BIT_BUILD)
}
void
Init()
{
// Do nothing on x86-64, because nsWindowsDllInterceptor is not thread-safe
// on 64-bit. (On 32-bit, it's probably thread-safe.) Even if we run Init()
// before any other of our threads are running, another process may have
// started a remote thread which could call VirtualAlloc!
//
// Moreover, the benefit of this code is less clear when we're a 64-bit
// process, because we aren't going to run out of virtual memory, and the
// system is likely to have a fair bit of physical memory.
-#if defined(_M_IX86) && defined(XP_WIN)
+#if defined(XP_WIN) && !defined(HAVE_64BIT_BUILD)
// Don't register the hooks if we're a build instrumented for PGO: If we're
// an instrumented build, the compiler adds function calls all over the place
// which may call VirtualAlloc; this makes it hard to prevent
// VirtualAllocHook from reentering itself.
if (!PR_GetEnv("MOZ_PGO_INSTRUMENTED")) {
sKernel32Intercept.Init("Kernel32.dll");
sKernel32Intercept.AddHook("VirtualAlloc",
reinterpret_cast<intptr_t>(VirtualAllocHook),
@@ -441,13 +643,13 @@ Init()
sGdi32Intercept.Init("Gdi32.dll");
sGdi32Intercept.AddHook("CreateDIBSection",
reinterpret_cast<intptr_t>(CreateDIBSectionHook),
reinterpret_cast<void**>(&sCreateDIBSectionOrig));
}
sInitialized = true;
-#endif
+#endif // defined(XP_WIN) && !defined(HAVE_64BIT_BUILD)
}
} // namespace AvailableMemoryTracker
} // namespace mozilla