/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * 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" #include "mozilla/Monitor.h" #include "mozilla/Preferences.h" #include "mozilla/ProcessPriorityManager.h" #include "mozilla/Services.h" #include "nsIObserver.h" #include "nsIObserverService.h" #include "nsMemoryPressure.h" #include "nsPrintfCString.h" #include "nsThreadUtils.h" #define LOG(args...) \ __android_log_print(ANDROID_LOG_INFO, "GonkMemoryPressure" , ## args) using namespace mozilla; namespace { /** * MemoryPressureWatcher watches sysfs from its own thread to notice when the * system is under memory pressure. When we observe memory pressure, we use * MemoryPressureRunnable to notify observers that they should release memory. * * When the system is under memory pressure, we don't want to constantly fire * memory-pressure events. So instead, we try to detect when sysfs indicates * that we're no longer under memory pressure, and only then start firing events * again. * * (This is a bit problematic because we can't poll() to detect when we're no * longer under memory pressure; instead we have to periodically read the sysfs * node. If we remain under memory pressure for a long time, this means we'll * continue waking up to read from the node for a long time, potentially wasting * battery life. Hopefully we don't hit this case in practice! We write to * logcat each time we go around this loop so it's at least noticable.) * * Shutting down safely is a bit of a chore. XPCOM won't shut down until all * threads exit, so we need to exit the Run() method below on shutdown. But our * thread might be blocked in one of two situations: We might be poll()'ing the * sysfs node waiting for memory pressure to occur, or we might be asleep * waiting to read() the sysfs node to see if we're no longer under memory * pressure. * * To let us wake up from the poll(), we poll() not just the sysfs node but also * a pipe, which we write to on shutdown. To let us wake up from sleeping * between read()s, we sleep by Wait()'ing on a monitor, which we notify on * shutdown. */ class MemoryPressureWatcher final : public nsIRunnable , public nsIObserver { public: MemoryPressureWatcher() : mMonitor("MemoryPressureWatcher") , mLowMemTriggerKB(0) , mPageSize(0) , mShuttingDown(false) , mTriggerFd(-1) , mShutdownPipeRead(-1) , mShutdownPipeWrite(-1) { } NS_DECL_THREADSAFE_ISUPPORTS nsresult Init() { nsCOMPtr os = services::GetObserverService(); NS_ENSURE_STATE(os); // The observer service holds us alive. os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, /* ownsWeak */ false); // Initialize the internal state mPageSize = sysconf(_SC_PAGESIZE); ReadPrefs(); nsresult rv = OpenFiles(); NS_ENSURE_SUCCESS(rv, rv); SetLowMemTrigger(mSoftLowMemTriggerKB); return NS_OK; } NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0); LOG("Observed XPCOM shutdown."); MonitorAutoLock lock(mMonitor); mShuttingDown = true; mMonitor.Notify(); int rv; do { // Write something to the pipe; doesn't matter what. uint32_t dummy = 0; rv = write(mShutdownPipeWrite, &dummy, sizeof(dummy)); } while(rv == -1 && errno == EINTR); return NS_OK; } NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread()); int triggerResetTimeout = -1; bool memoryPressure; nsresult rv = CheckForMemoryPressure(&memoryPressure); NS_ENSURE_SUCCESS(rv, rv); while (true) { // 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 = mTriggerFd; pollfds[0].events = POLLPRI; pollfds[1].fd = mShutdownPipeRead; pollfds[1].events = POLLIN; 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. LOG("shutting down (1)"); return NS_OK; } // If pollfds[1] isn't happening, pollfds[0] ought to be! if (!(pollfds[0].revents & POLLPRI)) { LOG("Unexpected revents value after poll(): %d. " "Shutting down GonkMemoryPressureMonitoring.", pollfds[0].revents); return NS_ERROR_FAILURE; } // 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); // 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. do { { MonitorAutoLock lock(mMonitor); // We need to check mShuttingDown before we wait here, in order to // catch a shutdown signal sent after we poll()'ed mShutdownPipeRead // above but before we started waiting on the monitor. But we don't // need to check after we wait, because we'll either do another // iteration of this inner loop, in which case we'll check // mShuttingDown, or we'll exit this loop and do another iteration // of the outer loop, in which case we'll check the shutdown pipe. if (mShuttingDown) { LOG("shutting down (2)"); return NS_OK; } mMonitor.Wait(PR_MillisecondsToInterval(mPollMS)); } LOG("Checking to see if memory pressure is over."); rv = CheckForMemoryPressure(&memoryPressure); NS_ENSURE_SUCCESS(rv, rv); if (memoryPressure) { rv = DispatchMemoryPressure(MemPressure_Ongoing); NS_ENSURE_SUCCESS(rv, rv); continue; } } while (false); if (XRE_IsParentProcess()) { // The main process will try to adjust the trigger. triggerResetTimeout = mPollMS * 2; } LOG("Memory pressure is over."); } return NS_OK; } 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; } /** * 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(bool* aOut) { *aOut = false; lseek(mTriggerFd, 0, SEEK_SET); char buf[2]; 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. *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 * responding to an urgent event (an incoming call or message for example) so * avoid wasting CPU time responding to low-memory events. */ nsresult DispatchMemoryPressure(MemoryPressureState state) { if (ProcessPriorityManager::AnyProcessHasHighPriority()) { return NS_OK; } return NS_DispatchMemoryPressure(state); } Monitor mMonitor; 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; }; NS_IMPL_ISUPPORTS(MemoryPressureWatcher, nsIRunnable, nsIObserver); } // namespace namespace mozilla { void InitGonkMemoryPressureMonitoring() { // memoryPressureWatcher is held alive by the observer service. RefPtr memoryPressureWatcher = new MemoryPressureWatcher(); NS_ENSURE_SUCCESS_VOID(memoryPressureWatcher->Init()); nsCOMPtr thread; NS_NewNamedThread("MemoryPressure", getter_AddRefs(thread), memoryPressureWatcher); } } // namespace mozilla