diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index c618dbe55631..8cb40434f9ba 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -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 diff --git a/widget/gonk/GonkMemoryPressureMonitoring.cpp b/widget/gonk/GonkMemoryPressureMonitoring.cpp index 83a727b2c71a..e0e54f3cd09c 100644 --- a/widget/gonk/GonkMemoryPressureMonitoring.cpp +++ b/widget/gonk/GonkMemoryPressureMonitoring.cpp @@ -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 +#include +#include +#include +#include + #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 -#include -#include -#include #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; };