Bug 1468207 - Use the new timer-based available memory tracker on Win32 r=dmajor

Differential Revision: https://phabricator.services.mozilla.com/D1959

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Gabriele Svelto 2018-07-05 22:44:15 +00:00
Родитель 80d44a0d4e
Коммит 9d2bf3db8a
6 изменённых файлов: 31 добавлений и 284 удалений

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

@ -487,10 +487,6 @@ int main()
TEST_HOOK(kernel32.dll, SetUnhandledExceptionFilter, Ignore, nullptr) &&
#endif
#ifdef _M_IX86
// Bug 670967: xpcom/base/AvailableMemoryTracker.cpp
TEST_HOOK(kernel32.dll, VirtualAlloc, Equals, nullptr) &&
TEST_HOOK(kernel32.dll, MapViewOfFile, Equals, nullptr) &&
TEST_HOOK(gdi32.dll, CreateDIBSection, Equals, nullptr) &&
TEST_HOOK_FOR_INVALID_HANDLE_VALUE(kernel32.dll, CreateFileW) &&
#endif
TEST_HOOK_FOR_INVALID_HANDLE_VALUE(kernel32.dll, CreateFileA) &&

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

@ -7,8 +7,6 @@
#include "mozilla/AvailableMemoryTracker.h"
#if defined(XP_WIN)
#include "prinrval.h"
#include "prenv.h"
#include "nsExceptionHandler.h"
#include "nsIMemoryReporter.h"
#include "nsMemoryPressure.h"
@ -27,11 +25,6 @@
#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
@ -42,228 +35,10 @@ namespace {
#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;
// 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;
// Alas, we'd like to use mozilla::TimeStamp, but we can't, because it acquires
// a lock!
volatile bool sUnderMemoryPressure = false;
volatile PRIntervalTime sLastLowMemoryNotificationTime;
// These are function pointers to the functions we wrap in Init().
static WindowsDllInterceptor::FuncHookType<decltype(&VirtualAlloc)>
sVirtualAllocOrig;
static WindowsDllInterceptor::FuncHookType<decltype(&MapViewOfFile)>
sMapViewOfFileOrig;
static WindowsDllInterceptor::FuncHookType<decltype(&CreateDIBSection)>
sCreateDIBSectionOrig;
/**
* Fire a memory pressure event if we were not under memory pressure yet, or
* fire an ongoing one if it's been long enough since the last one we
* fired.
*/
bool
MaybeScheduleMemoryPressureEvent()
{
MemoryPressureState state = MemPressure_New;
PRIntervalTime now = PR_IntervalNow();
// If this interval rolls over, we may fire an extra memory pressure
// event, but that's not a big deal.
PRIntervalTime interval = now - sLastLowMemoryNotificationTime;
if (sUnderMemoryPressure) {
if (PR_IntervalToMilliseconds(interval) <
kLowMemoryNotificationIntervalMS) {
return false;
}
state = MemPressure_Ongoing;
}
// There's a bit of a race condition here, since an interval may be a
// 64-bit number, and 64-bit writes aren't atomic on x86-32. But let's
// not worry about it -- the races only happen when we're already
// experiencing memory pressure and firing notifications, so the worst
// thing that can happen is that we fire two notifications when we
// should have fired only one.
sUnderMemoryPressure = true;
sLastLowMemoryNotificationTime = now;
NS_DispatchEventualMemoryPressure(state);
return true;
}
static bool
CheckLowMemory(DWORDLONG available, size_t threshold,
Atomic<uint32_t, MemoryOrdering::Relaxed>& counter)
{
if (available < threshold) {
if (MaybeScheduleMemoryPressureEvent()) {
counter++;
}
return true;
}
return false;
}
void
CheckMemAvailable()
{
if (!sHooksActive) {
return;
}
MEMORYSTATUSEX stat;
stat.dwLength = sizeof(stat);
bool success = GlobalMemoryStatusEx(&stat);
if (success) {
bool lowMemory = CheckLowMemory(stat.ullAvailVirtual,
kLowVirtualMemoryThreshold,
sNumLowVirtualMemEvents);
lowMemory |= CheckLowMemory(stat.ullAvailPageFile, kLowCommitSpaceThreshold,
sNumLowCommitSpaceEvents);
lowMemory |= CheckLowMemory(stat.ullAvailPhys, kLowPhysicalMemoryThreshold,
sNumLowPhysicalMemEvents);
sUnderMemoryPressure = lowMemory;
}
}
LPVOID WINAPI
VirtualAllocHook(LPVOID aAddress, SIZE_T aSize,
DWORD aAllocationType,
DWORD aProtect)
{
// It's tempting to see whether we have enough free virtual address space for
// this allocation and, if we don't, synchronously fire a low-memory
// notification to free some before we allocate.
//
// Unfortunately that doesn't work, principally because code doesn't expect a
// call to malloc could trigger a GC (or call into the other routines which
// are triggered by a low-memory notification).
//
// I think the best we can do here is try to allocate the memory and check
// afterwards how much free virtual address space we have. If we're running
// low, we schedule a low-memory notification to run as soon as possible.
LPVOID result = sVirtualAllocOrig(aAddress, aSize, aAllocationType, aProtect);
// Don't call CheckMemAvailable for MEM_RESERVE if we're not tracking low
// virtual memory. Similarly, don't call CheckMemAvailable for MEM_COMMIT if
// we're not tracking low physical memory.
if ((kLowVirtualMemoryThreshold != 0 && aAllocationType & MEM_RESERVE) ||
((kLowCommitSpaceThreshold != 0 || kLowPhysicalMemoryThreshold != 0) &&
aAllocationType & MEM_COMMIT)) {
CheckMemAvailable();
}
return result;
}
LPVOID WINAPI
MapViewOfFileHook(HANDLE aFileMappingObject,
DWORD aDesiredAccess,
DWORD aFileOffsetHigh,
DWORD aFileOffsetLow,
SIZE_T aNumBytesToMap)
{
LPVOID result = sMapViewOfFileOrig(aFileMappingObject, aDesiredAccess,
aFileOffsetHigh, aFileOffsetLow,
aNumBytesToMap);
CheckMemAvailable();
return result;
}
HBITMAP WINAPI
CreateDIBSectionHook(HDC aDC,
const BITMAPINFO* aBitmapInfo,
UINT aUsage,
VOID** aBits,
HANDLE aSection,
DWORD aOffset)
{
// There are a lot of calls to CreateDIBSection, so we make some effort not
// to CheckMemAvailable() for calls to CreateDIBSection which allocate only
// a small amount of memory.
// If aSection is non-null, CreateDIBSection won't allocate any new memory.
bool doCheck = false;
if (sHooksActive && !aSection && aBitmapInfo) {
uint16_t bitCount = aBitmapInfo->bmiHeader.biBitCount;
if (bitCount == 0) {
// MSDN says bitCount == 0 means that it figures out how many bits each
// pixel gets by examining the corresponding JPEG or PNG data. We'll just
// assume the worst.
bitCount = 32;
}
// |size| contains the expected allocation size in *bits*. Height may be
// negative (indicating the direction the DIB is drawn in), so we take the
// absolute value.
int64_t size = bitCount * aBitmapInfo->bmiHeader.biWidth *
aBitmapInfo->bmiHeader.biHeight;
if (size < 0) {
size *= -1;
}
// If we're allocating more than 1MB, check how much memory is left after
// the allocation.
if (size > 1024 * 1024 * 8) {
doCheck = true;
}
}
HBITMAP result = sCreateDIBSectionOrig(aDC, aBitmapInfo, aUsage, aBits,
aSection, aOffset);
if (doCheck) {
CheckMemAvailable();
}
return result;
}
#else
class nsAvailableMemoryWatcher final : public nsIObserver,
public nsITimerCallback
{
@ -275,10 +50,30 @@ public:
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 = 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;
// 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;
// Poll the amount of free memory at this rate.
static const uint32_t kPollingIntervalMS = 1000;
// Observer topics we subscribe to
// Observer topics we subscribe to, see below.
static const char* const kObserverTopics[];
static bool IsVirtualMemoryLow(const MEMORYSTATUSEX& aStat);
@ -446,8 +241,6 @@ nsAvailableMemoryWatcher::Observe(nsISupports* aSubject, const char* aTopic,
return NS_OK;
}
#endif // !defined(HAVE_64BIT_BUILD)
static int64_t
LowMemoryEventsVirtualDistinguishedAmount()
{
@ -595,25 +388,21 @@ namespace mozilla {
namespace AvailableMemoryTracker {
void
Activate()
Init()
{
#if defined(XP_WIN) && !defined(HAVE_64BIT_BUILD)
MOZ_ASSERT(sInitialized);
MOZ_ASSERT(!sHooksActive);
RegisterStrongMemoryReporter(new LowEventsReporter());
RegisterLowMemoryEventsVirtualDistinguishedAmount(
LowMemoryEventsVirtualDistinguishedAmount);
RegisterLowMemoryEventsCommitSpaceDistinguishedAmount(
LowMemoryEventsCommitSpaceDistinguishedAmount);
RegisterLowMemoryEventsPhysicalDistinguishedAmount(
LowMemoryEventsPhysicalDistinguishedAmount);
sHooksActive = true;
#endif // defined(XP_WIN) && !defined(HAVE_64BIT_BUILD)
// 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 defined(XP_WIN)
if (XRE_IsParentProcess()) {
RefPtr<nsAvailableMemoryWatcher> poller = new nsAvailableMemoryWatcher();
@ -621,38 +410,7 @@ Activate()
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(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");
sVirtualAllocOrig.Set(sKernel32Intercept, "VirtualAlloc", &VirtualAllocHook);
sMapViewOfFileOrig.Set(sKernel32Intercept, "MapViewOfFile", &MapViewOfFileHook);
sGdi32Intercept.Init("Gdi32.dll");
sCreateDIBSectionOrig.Set(sGdi32Intercept, "CreateDIBSection",
&CreateDIBSectionHook);
}
sInitialized = true;
#endif // defined(XP_WIN) && !defined(HAVE_64BIT_BUILD)
#endif // defined(XP_WIN)
}
} // namespace AvailableMemoryTracker

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

@ -14,15 +14,10 @@ namespace AvailableMemoryTracker {
// platforms to react to low-memory situations and on Windows it implements
// the full functionality used to monitor how much memory is available.
//
// Init() must be called before any other threads have started, because it
// modifies the in-memory implementations of some DLL functions in
// non-thread-safe ways.
//
// The hooks don't do anything until Activate() is called. It's an error to
// call Activate() without first calling Init().
// Init() requires the observer service to be already available so cannot be
// called too early during initialization.
void Init();
void Activate();
} // namespace AvailableMemoryTracker
} // namespace mozilla

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

@ -505,6 +505,7 @@ 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)

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

@ -2790,6 +2790,7 @@ 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)

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

@ -504,10 +504,6 @@ NS_InitXPCOM2(nsIServiceManager** aResult,
// We are not shutting down
gXPCOMShuttingDown = false;
// Initialize the available memory tracker before other threads have had a
// chance to start up, because the initialization is not thread-safe.
mozilla::AvailableMemoryTracker::Init();
#ifdef XP_UNIX
// Discover the current value of the umask, and save it where
// nsSystemInfo::Init can retrieve it when necessary. There is no way
@ -719,7 +715,7 @@ NS_InitXPCOM2(nsIServiceManager** aResult,
mozilla::ScriptPreloader::GetSingleton();
mozilla::scache::StartupCache::GetSingleton();
mozilla::AvailableMemoryTracker::Activate();
mozilla::AvailableMemoryTracker::Init();
// Notify observers of xpcom autoregistration start
NS_CreateServicesFromCategory(NS_XPCOM_STARTUP_CATEGORY,