michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "GonkMemoryPressureMonitoring.h" michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/FileUtils.h" michael@0: #include "mozilla/Monitor.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/ProcessPriorityManager.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsIObserver.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsMemoryPressure.h" michael@0: #include "nsThreadUtils.h" michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #define LOG(args...) \ michael@0: __android_log_print(ANDROID_LOG_INFO, "GonkMemoryPressure" , ## args) michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: #include "ipc/Nuwa.h" michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: michael@0: namespace { michael@0: michael@0: /** michael@0: * MemoryPressureWatcher watches sysfs from its own thread to notice when the michael@0: * system is under memory pressure. When we observe memory pressure, we use michael@0: * MemoryPressureRunnable to notify observers that they should release memory. michael@0: * michael@0: * When the system is under memory pressure, we don't want to constantly fire michael@0: * memory-pressure events. So instead, we try to detect when sysfs indicates michael@0: * that we're no longer under memory pressure, and only then start firing events michael@0: * again. michael@0: * michael@0: * (This is a bit problematic because we can't poll() to detect when we're no michael@0: * longer under memory pressure; instead we have to periodically read the sysfs michael@0: * node. If we remain under memory pressure for a long time, this means we'll michael@0: * continue waking up to read from the node for a long time, potentially wasting michael@0: * battery life. Hopefully we don't hit this case in practice! We write to michael@0: * logcat each time we go around this loop so it's at least noticable.) michael@0: * michael@0: * Shutting down safely is a bit of a chore. XPCOM won't shut down until all michael@0: * threads exit, so we need to exit the Run() method below on shutdown. But our michael@0: * thread might be blocked in one of two situations: We might be poll()'ing the michael@0: * sysfs node waiting for memory pressure to occur, or we might be asleep michael@0: * waiting to read() the sysfs node to see if we're no longer under memory michael@0: * pressure. michael@0: * michael@0: * To let us wake up from the poll(), we poll() not just the sysfs node but also michael@0: * a pipe, which we write to on shutdown. To let us wake up from sleeping michael@0: * between read()s, we sleep by Wait()'ing on a monitor, which we notify on michael@0: * shutdown. michael@0: */ michael@0: class MemoryPressureWatcher michael@0: : public nsIRunnable michael@0: , public nsIObserver michael@0: { michael@0: public: michael@0: MemoryPressureWatcher() michael@0: : mMonitor("MemoryPressureWatcher") michael@0: , mShuttingDown(false) michael@0: { michael@0: } michael@0: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: michael@0: nsresult Init() michael@0: { michael@0: nsCOMPtr os = services::GetObserverService(); michael@0: NS_ENSURE_STATE(os); michael@0: michael@0: // The observer service holds us alive. michael@0: os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, /* holdsWeak */ false); michael@0: michael@0: // While we're under memory pressure, we periodically read() michael@0: // notify_trigger_active to try and see when we're no longer under memory michael@0: // pressure. mPollMS indicates how many milliseconds we wait between those michael@0: // read()s. michael@0: mPollMS = Preferences::GetUint("gonk.systemMemoryPressureRecoveryPollMS", michael@0: /* default */ 5000); michael@0: michael@0: int pipes[2]; michael@0: NS_ENSURE_STATE(!pipe(pipes)); michael@0: mShutdownPipeRead = pipes[0]; michael@0: mShutdownPipeWrite = pipes[1]; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0); michael@0: LOG("Observed XPCOM shutdown."); michael@0: michael@0: MonitorAutoLock lock(mMonitor); michael@0: mShuttingDown = true; michael@0: mMonitor.Notify(); michael@0: michael@0: int rv; michael@0: do { michael@0: // Write something to the pipe; doesn't matter what. michael@0: uint32_t dummy = 0; michael@0: rv = write(mShutdownPipeWrite, &dummy, sizeof(dummy)); michael@0: } while(rv == -1 && errno == EINTR); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: if (IsNuwaProcess()) { michael@0: NS_ASSERTION(NuwaMarkCurrentThread != nullptr, michael@0: "NuwaMarkCurrentThread is undefined!"); michael@0: NuwaMarkCurrentThread(nullptr, nullptr); michael@0: } michael@0: #endif michael@0: michael@0: int lowMemFd = open("/sys/kernel/mm/lowmemkiller/notify_trigger_active", michael@0: O_RDONLY | O_CLOEXEC); michael@0: NS_ENSURE_STATE(lowMemFd != -1); michael@0: ScopedClose autoClose(lowMemFd); michael@0: michael@0: nsresult rv = CheckForMemoryPressure(lowMemFd, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: while (true) { michael@0: // Wait for a notification on lowMemFd or for data to be written to michael@0: // mShutdownPipeWrite. (poll(lowMemFd, POLLPRI) blocks until we're under michael@0: // memory pressure.) michael@0: struct pollfd pollfds[2]; michael@0: pollfds[0].fd = lowMemFd; michael@0: pollfds[0].events = POLLPRI; michael@0: pollfds[1].fd = mShutdownPipeRead; michael@0: pollfds[1].events = POLLIN; michael@0: michael@0: int pollRv; michael@0: do { michael@0: pollRv = poll(pollfds, ArrayLength(pollfds), /* timeout */ -1); michael@0: } while (pollRv == -1 && errno == EINTR); michael@0: michael@0: if (pollfds[1].revents) { michael@0: // Something was written to our shutdown pipe; we're outta here. michael@0: LOG("shutting down (1)"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If pollfds[1] isn't happening, pollfds[0] ought to be! michael@0: if (!(pollfds[0].revents & POLLPRI)) { michael@0: LOG("Unexpected revents value after poll(): %d. " michael@0: "Shutting down GonkMemoryPressureMonitoring.", pollfds[0].revents); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // POLLPRI on lowMemFd indicates that we're in a low-memory situation. We michael@0: // could read lowMemFd to double-check, but we've observed that the read michael@0: // sometimes completes after the memory-pressure event is over, so let's michael@0: // just believe the result of poll(). michael@0: michael@0: // We use low-memory-no-forward because each process has its own watcher michael@0: // and thus there is no need for the main process to forward this event. michael@0: rv = DispatchMemoryPressure(MemPressure_New); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Manually check lowMemFd until we observe that memory pressure is over. michael@0: // We won't fire any more low-memory events until we observe that michael@0: // we're no longer under pressure. Instead, we fire low-memory-ongoing michael@0: // events, which cause processes to keep flushing caches but will not michael@0: // trigger expensive GCs and other attempts to save memory that are michael@0: // likely futile at this point. michael@0: bool memoryPressure; michael@0: do { michael@0: { michael@0: MonitorAutoLock lock(mMonitor); michael@0: michael@0: // We need to check mShuttingDown before we wait here, in order to michael@0: // catch a shutdown signal sent after we poll()'ed mShutdownPipeRead michael@0: // above but before we started waiting on the monitor. But we don't michael@0: // need to check after we wait, because we'll either do another michael@0: // iteration of this inner loop, in which case we'll check michael@0: // mShuttingDown, or we'll exit this loop and do another iteration michael@0: // of the outer loop, in which case we'll check the shutdown pipe. michael@0: if (mShuttingDown) { michael@0: LOG("shutting down (2)"); michael@0: return NS_OK; michael@0: } michael@0: mMonitor.Wait(PR_MillisecondsToInterval(mPollMS)); michael@0: } michael@0: michael@0: LOG("Checking to see if memory pressure is over."); michael@0: rv = CheckForMemoryPressure(lowMemFd, &memoryPressure); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (memoryPressure) { michael@0: rv = DispatchMemoryPressure(MemPressure_Ongoing); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: continue; michael@0: } michael@0: } while (false); michael@0: michael@0: LOG("Memory pressure is over."); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: /** michael@0: * Read from aLowMemFd, which we assume corresponds to the michael@0: * notify_trigger_active sysfs node, and determine whether we're currently michael@0: * under memory pressure. michael@0: * michael@0: * We don't expect this method to block. michael@0: */ michael@0: nsresult CheckForMemoryPressure(int aLowMemFd, bool* aOut) michael@0: { michael@0: if (aOut) { michael@0: *aOut = false; michael@0: } michael@0: michael@0: lseek(aLowMemFd, 0, SEEK_SET); michael@0: michael@0: char buf[2]; michael@0: int nread; michael@0: do { michael@0: nread = read(aLowMemFd, buf, sizeof(buf)); michael@0: } while(nread == -1 && errno == EINTR); michael@0: NS_ENSURE_STATE(nread == 2); michael@0: michael@0: // The notify_trigger_active sysfs node should contain either "0\n" or michael@0: // "1\n". The latter indicates memory pressure. michael@0: if (aOut) { michael@0: *aOut = buf[0] == '1' && buf[1] == '\n'; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Dispatch the specified memory pressure event unless a high-priority michael@0: * process is present. If a high-priority process is present then it's likely michael@0: * responding to an urgent event (an incoming call or message for example) so michael@0: * avoid wasting CPU time responding to low-memory events. michael@0: */ michael@0: nsresult DispatchMemoryPressure(MemoryPressureState state) michael@0: { michael@0: if (ProcessPriorityManager::AnyProcessHasHighPriority()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_DispatchMemoryPressure(state); michael@0: } michael@0: michael@0: Monitor mMonitor; michael@0: uint32_t mPollMS; michael@0: bool mShuttingDown; michael@0: michael@0: ScopedClose mShutdownPipeRead; michael@0: ScopedClose mShutdownPipeWrite; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(MemoryPressureWatcher, nsIRunnable, nsIObserver); michael@0: michael@0: } // anonymous namespace michael@0: michael@0: namespace mozilla { michael@0: michael@0: void michael@0: InitGonkMemoryPressureMonitoring() michael@0: { michael@0: // memoryPressureWatcher is held alive by the observer service. michael@0: nsRefPtr memoryPressureWatcher = michael@0: new MemoryPressureWatcher(); michael@0: NS_ENSURE_SUCCESS_VOID(memoryPressureWatcher->Init()); michael@0: michael@0: nsCOMPtr thread; michael@0: NS_NewThread(getter_AddRefs(thread), memoryPressureWatcher); michael@0: } michael@0: michael@0: } // namespace mozilla