зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1234176 - Introduce a dynamic trigger to send memory pressure events before background applications are reaped by the LMK. r=dhylands
--HG-- extra : rebase_source : 3aab2373d3064854ff4639e78feff052c06ada8d
This commit is contained in:
Родитель
c7edb0ffaf
Коммит
6f8e5e39bd
|
@ -779,7 +779,12 @@ pref("hal.gonk.COMPOSITOR.nice", -4);
|
|||
// this too high, then we'll send out a memory pressure event every Z seconds
|
||||
// (see below), even while we have processes that we would happily kill in
|
||||
// order to free up memory.
|
||||
pref("hal.processPriorityManager.gonk.notifyLowMemUnderKB", 14336);
|
||||
pref("gonk.notifyHardLowMemUnderKB", 14336);
|
||||
|
||||
// Fire a memory pressure event when the system has less than Xmb of memory
|
||||
// remaining and then switch to the hard trigger, see above. This should be
|
||||
// placed above the BACKGROUND priority class.
|
||||
pref("gonk.notifySoftLowMemUnderKB", 43008);
|
||||
|
||||
// We wait this long before polling the memory-pressure fd after seeing one
|
||||
// memory pressure event. (When we're not under memory pressure, we sit
|
||||
|
|
|
@ -4,6 +4,12 @@
|
|||
* 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 <android/log.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <sys/sysinfo.h>
|
||||
|
||||
#include "GonkMemoryPressureMonitoring.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/FileUtils.h"
|
||||
|
@ -14,11 +20,8 @@
|
|||
#include "nsIObserver.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsMemoryPressure.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#define LOG(args...) \
|
||||
__android_log_print(ANDROID_LOG_INFO, "GonkMemoryPressure" , ## args)
|
||||
|
@ -67,7 +70,12 @@ class MemoryPressureWatcher final
|
|||
public:
|
||||
MemoryPressureWatcher()
|
||||
: mMonitor("MemoryPressureWatcher")
|
||||
, mLowMemTriggerKB(0)
|
||||
, mPageSize(0)
|
||||
, mShuttingDown(false)
|
||||
, mTriggerFd(-1)
|
||||
, mShutdownPipeRead(-1)
|
||||
, mShutdownPipeWrite(-1)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -81,17 +89,13 @@ public:
|
|||
// The observer service holds us alive.
|
||||
os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, /* holdsWeak */ false);
|
||||
|
||||
// While we're under memory pressure, we periodically read()
|
||||
// notify_trigger_active to try and see when we're no longer under memory
|
||||
// pressure. mPollMS indicates how many milliseconds we wait between those
|
||||
// read()s.
|
||||
mPollMS = Preferences::GetUint("gonk.systemMemoryPressureRecoveryPollMS",
|
||||
/* default */ 5000);
|
||||
// Initialize the internal state
|
||||
mPageSize = sysconf(_SC_PAGESIZE);
|
||||
ReadPrefs();
|
||||
nsresult rv = OpenFiles();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
SetLowMemTrigger(mSoftLowMemTriggerKB);
|
||||
|
||||
int pipes[2];
|
||||
NS_ENSURE_STATE(!pipe(pipes));
|
||||
mShutdownPipeRead = pipes[0];
|
||||
mShutdownPipeWrite = pipes[1];
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -125,28 +129,31 @@ public:
|
|||
}
|
||||
#endif
|
||||
|
||||
int lowMemFd = open("/sys/kernel/mm/lowmemkiller/notify_trigger_active",
|
||||
O_RDONLY | O_CLOEXEC);
|
||||
NS_ENSURE_STATE(lowMemFd != -1);
|
||||
ScopedClose autoClose(lowMemFd);
|
||||
|
||||
nsresult rv = CheckForMemoryPressure(lowMemFd, nullptr);
|
||||
int triggerResetTimeout = -1;
|
||||
bool memoryPressure;
|
||||
nsresult rv = CheckForMemoryPressure(&memoryPressure);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
while (true) {
|
||||
// Wait for a notification on lowMemFd or for data to be written to
|
||||
// mShutdownPipeWrite. (poll(lowMemFd, POLLPRI) blocks until we're under
|
||||
// memory pressure.)
|
||||
// Wait for a notification on mTriggerFd or for data to be written to
|
||||
// mShutdownPipeWrite. (poll(mTriggerFd, POLLPRI) blocks until we're
|
||||
// under memory pressure or until we time out, the time out is used
|
||||
// to adjust the trigger level after a memory pressure event.)
|
||||
struct pollfd pollfds[2];
|
||||
pollfds[0].fd = lowMemFd;
|
||||
pollfds[0].fd = mTriggerFd;
|
||||
pollfds[0].events = POLLPRI;
|
||||
pollfds[1].fd = mShutdownPipeRead;
|
||||
pollfds[1].events = POLLIN;
|
||||
|
||||
int pollRv;
|
||||
do {
|
||||
pollRv = poll(pollfds, ArrayLength(pollfds), /* timeout */ -1);
|
||||
} while (pollRv == -1 && errno == EINTR);
|
||||
int pollRv = MOZ_TEMP_FAILURE_RETRY(
|
||||
poll(pollfds, ArrayLength(pollfds), triggerResetTimeout)
|
||||
);
|
||||
|
||||
if (pollRv == 0) {
|
||||
// Timed out, adjust the trigger and update the timeout.
|
||||
triggerResetTimeout = AdjustTrigger(triggerResetTimeout);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pollfds[1].revents) {
|
||||
// Something was written to our shutdown pipe; we're outta here.
|
||||
|
@ -161,23 +168,24 @@ public:
|
|||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// POLLPRI on lowMemFd indicates that we're in a low-memory situation. We
|
||||
// could read lowMemFd to double-check, but we've observed that the read
|
||||
// sometimes completes after the memory-pressure event is over, so let's
|
||||
// just believe the result of poll().
|
||||
|
||||
// We use low-memory-no-forward because each process has its own watcher
|
||||
// and thus there is no need for the main process to forward this event.
|
||||
// POLLPRI on mTriggerFd indicates that we're in a low-memory situation.
|
||||
// We could read lowMemFd to double-check, but we've observed that the
|
||||
// read sometimes completes after the memory-pressure event is over, so
|
||||
// let's just believe the result of poll().
|
||||
rv = DispatchMemoryPressure(MemPressure_New);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Manually check lowMemFd until we observe that memory pressure is over.
|
||||
// We won't fire any more low-memory events until we observe that
|
||||
// Move to the hard level if we're on the soft one.
|
||||
if (mLowMemTriggerKB > mHardLowMemTriggerKB) {
|
||||
SetLowMemTrigger(mHardLowMemTriggerKB);
|
||||
}
|
||||
|
||||
// Manually check mTriggerFd until we observe that memory pressure is
|
||||
// over. We won't fire any more low-memory events until we observe that
|
||||
// we're no longer under pressure. Instead, we fire low-memory-ongoing
|
||||
// events, which cause processes to keep flushing caches but will not
|
||||
// trigger expensive GCs and other attempts to save memory that are
|
||||
// likely futile at this point.
|
||||
bool memoryPressure;
|
||||
do {
|
||||
{
|
||||
MonitorAutoLock lock(mMonitor);
|
||||
|
@ -197,7 +205,7 @@ public:
|
|||
}
|
||||
|
||||
LOG("Checking to see if memory pressure is over.");
|
||||
rv = CheckForMemoryPressure(lowMemFd, &memoryPressure);
|
||||
rv = CheckForMemoryPressure(&memoryPressure);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (memoryPressure) {
|
||||
|
@ -207,6 +215,11 @@ public:
|
|||
}
|
||||
} while (false);
|
||||
|
||||
if (XRE_IsParentProcess()) {
|
||||
// The main process will try to adjust the trigger.
|
||||
triggerResetTimeout = mPollMS * 2;
|
||||
}
|
||||
|
||||
LOG("Memory pressure is over.");
|
||||
}
|
||||
|
||||
|
@ -217,36 +230,95 @@ protected:
|
|||
~MemoryPressureWatcher() {}
|
||||
|
||||
private:
|
||||
void ReadPrefs() {
|
||||
// While we're under memory pressure, we periodically read()
|
||||
// notify_trigger_active to try and see when we're no longer under memory
|
||||
// pressure. mPollMS indicates how many milliseconds we wait between those
|
||||
// read()s.
|
||||
Preferences::AddUintVarCache(&mPollMS,
|
||||
"gonk.systemMemoryPressureRecoveryPollMS", /* default */ 5000);
|
||||
|
||||
// We have two values for the notify trigger, a soft one which is triggered
|
||||
// before we start killing background applications and an hard one which is
|
||||
// after we've killed background applications but before we start killing
|
||||
// foreground ones.
|
||||
Preferences::AddUintVarCache(&mSoftLowMemTriggerKB,
|
||||
"gonk.notifySoftLowMemUnderKB", /* default */ 43008);
|
||||
Preferences::AddUintVarCache(&mHardLowMemTriggerKB,
|
||||
"gonk.notifyHardLowMemUnderKB", /* default */ 14336);
|
||||
}
|
||||
|
||||
nsresult OpenFiles() {
|
||||
mTriggerFd = open("/sys/kernel/mm/lowmemkiller/notify_trigger_active",
|
||||
O_RDONLY | O_CLOEXEC);
|
||||
NS_ENSURE_STATE(mTriggerFd != -1);
|
||||
|
||||
int pipes[2];
|
||||
NS_ENSURE_STATE(!pipe(pipes));
|
||||
mShutdownPipeRead = pipes[0];
|
||||
mShutdownPipeWrite = pipes[1];
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from aLowMemFd, which we assume corresponds to the
|
||||
* notify_trigger_active sysfs node, and determine whether we're currently
|
||||
* under memory pressure.
|
||||
* Set the low memory trigger to the specified value, this can be done by
|
||||
* the main process alone.
|
||||
*/
|
||||
void SetLowMemTrigger(uint32_t aValue) {
|
||||
if (XRE_IsParentProcess()) {
|
||||
nsPrintfCString str("%ld", (aValue * 1024) / mPageSize);
|
||||
if (WriteSysFile("/sys/module/lowmemorykiller/parameters/notify_trigger",
|
||||
str.get())) {
|
||||
mLowMemTriggerKB = aValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from the trigger file descriptor and determine whether we're
|
||||
* currently under memory pressure.
|
||||
*
|
||||
* We don't expect this method to block.
|
||||
*/
|
||||
nsresult CheckForMemoryPressure(int aLowMemFd, bool* aOut)
|
||||
nsresult CheckForMemoryPressure(bool* aOut)
|
||||
{
|
||||
if (aOut) {
|
||||
*aOut = false;
|
||||
}
|
||||
*aOut = false;
|
||||
|
||||
lseek(aLowMemFd, 0, SEEK_SET);
|
||||
lseek(mTriggerFd, 0, SEEK_SET);
|
||||
|
||||
char buf[2];
|
||||
int nread;
|
||||
do {
|
||||
nread = read(aLowMemFd, buf, sizeof(buf));
|
||||
} while(nread == -1 && errno == EINTR);
|
||||
int nread = MOZ_TEMP_FAILURE_RETRY(read(mTriggerFd, buf, sizeof(buf)));
|
||||
NS_ENSURE_STATE(nread == 2);
|
||||
|
||||
// The notify_trigger_active sysfs node should contain either "0\n" or
|
||||
// "1\n". The latter indicates memory pressure.
|
||||
if (aOut) {
|
||||
*aOut = buf[0] == '1' && buf[1] == '\n';
|
||||
}
|
||||
*aOut = (buf[0] == '1');
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
int AdjustTrigger(int timeout)
|
||||
{
|
||||
if (!XRE_IsParentProcess()) {
|
||||
return -1; // Only the main process can adjust the trigger.
|
||||
}
|
||||
|
||||
struct sysinfo info;
|
||||
int rv = sysinfo(&info);
|
||||
if (rv < 0) {
|
||||
return -1; // Without system information we're blind, bail out.
|
||||
}
|
||||
|
||||
size_t freeMemory = (info.freeram * info.mem_unit) / 1024;
|
||||
|
||||
if (freeMemory > mSoftLowMemTriggerKB) {
|
||||
SetLowMemTrigger(mSoftLowMemTriggerKB);
|
||||
return -1; // Trigger adjusted, wait indefinitely.
|
||||
}
|
||||
|
||||
// Wait again but double the duration, max once per day.
|
||||
return std::min(86400000, timeout * 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the specified memory pressure event unless a high-priority
|
||||
* process is present. If a high-priority process is present then it's likely
|
||||
|
@ -263,9 +335,14 @@ private:
|
|||
}
|
||||
|
||||
Monitor mMonitor;
|
||||
uint32_t mPollMS;
|
||||
uint32_t mPollMS; // Ongoing pressure poll delay
|
||||
uint32_t mSoftLowMemTriggerKB; // Soft memory pressure level
|
||||
uint32_t mHardLowMemTriggerKB; // Hard memory pressure level
|
||||
uint32_t mLowMemTriggerKB; // Current value of the trigger
|
||||
size_t mPageSize;
|
||||
bool mShuttingDown;
|
||||
|
||||
ScopedClose mTriggerFd;
|
||||
ScopedClose mShutdownPipeRead;
|
||||
ScopedClose mShutdownPipeWrite;
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче