1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/gonk/GonkMemoryPressureMonitoring.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,290 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "GonkMemoryPressureMonitoring.h" 1.11 +#include "mozilla/ArrayUtils.h" 1.12 +#include "mozilla/FileUtils.h" 1.13 +#include "mozilla/Monitor.h" 1.14 +#include "mozilla/Preferences.h" 1.15 +#include "mozilla/ProcessPriorityManager.h" 1.16 +#include "mozilla/Services.h" 1.17 +#include "nsIObserver.h" 1.18 +#include "nsIObserverService.h" 1.19 +#include "nsMemoryPressure.h" 1.20 +#include "nsThreadUtils.h" 1.21 +#include <errno.h> 1.22 +#include <fcntl.h> 1.23 +#include <poll.h> 1.24 +#include <android/log.h> 1.25 + 1.26 +#define LOG(args...) \ 1.27 + __android_log_print(ANDROID_LOG_INFO, "GonkMemoryPressure" , ## args) 1.28 + 1.29 +#ifdef MOZ_NUWA_PROCESS 1.30 +#include "ipc/Nuwa.h" 1.31 +#endif 1.32 + 1.33 +using namespace mozilla; 1.34 + 1.35 +namespace { 1.36 + 1.37 +/** 1.38 + * MemoryPressureWatcher watches sysfs from its own thread to notice when the 1.39 + * system is under memory pressure. When we observe memory pressure, we use 1.40 + * MemoryPressureRunnable to notify observers that they should release memory. 1.41 + * 1.42 + * When the system is under memory pressure, we don't want to constantly fire 1.43 + * memory-pressure events. So instead, we try to detect when sysfs indicates 1.44 + * that we're no longer under memory pressure, and only then start firing events 1.45 + * again. 1.46 + * 1.47 + * (This is a bit problematic because we can't poll() to detect when we're no 1.48 + * longer under memory pressure; instead we have to periodically read the sysfs 1.49 + * node. If we remain under memory pressure for a long time, this means we'll 1.50 + * continue waking up to read from the node for a long time, potentially wasting 1.51 + * battery life. Hopefully we don't hit this case in practice! We write to 1.52 + * logcat each time we go around this loop so it's at least noticable.) 1.53 + * 1.54 + * Shutting down safely is a bit of a chore. XPCOM won't shut down until all 1.55 + * threads exit, so we need to exit the Run() method below on shutdown. But our 1.56 + * thread might be blocked in one of two situations: We might be poll()'ing the 1.57 + * sysfs node waiting for memory pressure to occur, or we might be asleep 1.58 + * waiting to read() the sysfs node to see if we're no longer under memory 1.59 + * pressure. 1.60 + * 1.61 + * To let us wake up from the poll(), we poll() not just the sysfs node but also 1.62 + * a pipe, which we write to on shutdown. To let us wake up from sleeping 1.63 + * between read()s, we sleep by Wait()'ing on a monitor, which we notify on 1.64 + * shutdown. 1.65 + */ 1.66 +class MemoryPressureWatcher 1.67 + : public nsIRunnable 1.68 + , public nsIObserver 1.69 +{ 1.70 +public: 1.71 + MemoryPressureWatcher() 1.72 + : mMonitor("MemoryPressureWatcher") 1.73 + , mShuttingDown(false) 1.74 + { 1.75 + } 1.76 + 1.77 + NS_DECL_THREADSAFE_ISUPPORTS 1.78 + 1.79 + nsresult Init() 1.80 + { 1.81 + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); 1.82 + NS_ENSURE_STATE(os); 1.83 + 1.84 + // The observer service holds us alive. 1.85 + os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, /* holdsWeak */ false); 1.86 + 1.87 + // While we're under memory pressure, we periodically read() 1.88 + // notify_trigger_active to try and see when we're no longer under memory 1.89 + // pressure. mPollMS indicates how many milliseconds we wait between those 1.90 + // read()s. 1.91 + mPollMS = Preferences::GetUint("gonk.systemMemoryPressureRecoveryPollMS", 1.92 + /* default */ 5000); 1.93 + 1.94 + int pipes[2]; 1.95 + NS_ENSURE_STATE(!pipe(pipes)); 1.96 + mShutdownPipeRead = pipes[0]; 1.97 + mShutdownPipeWrite = pipes[1]; 1.98 + return NS_OK; 1.99 + } 1.100 + 1.101 + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, 1.102 + const char16_t* aData) 1.103 + { 1.104 + MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0); 1.105 + LOG("Observed XPCOM shutdown."); 1.106 + 1.107 + MonitorAutoLock lock(mMonitor); 1.108 + mShuttingDown = true; 1.109 + mMonitor.Notify(); 1.110 + 1.111 + int rv; 1.112 + do { 1.113 + // Write something to the pipe; doesn't matter what. 1.114 + uint32_t dummy = 0; 1.115 + rv = write(mShutdownPipeWrite, &dummy, sizeof(dummy)); 1.116 + } while(rv == -1 && errno == EINTR); 1.117 + 1.118 + return NS_OK; 1.119 + } 1.120 + 1.121 + NS_IMETHOD Run() 1.122 + { 1.123 + MOZ_ASSERT(!NS_IsMainThread()); 1.124 + 1.125 +#ifdef MOZ_NUWA_PROCESS 1.126 + if (IsNuwaProcess()) { 1.127 + NS_ASSERTION(NuwaMarkCurrentThread != nullptr, 1.128 + "NuwaMarkCurrentThread is undefined!"); 1.129 + NuwaMarkCurrentThread(nullptr, nullptr); 1.130 + } 1.131 +#endif 1.132 + 1.133 + int lowMemFd = open("/sys/kernel/mm/lowmemkiller/notify_trigger_active", 1.134 + O_RDONLY | O_CLOEXEC); 1.135 + NS_ENSURE_STATE(lowMemFd != -1); 1.136 + ScopedClose autoClose(lowMemFd); 1.137 + 1.138 + nsresult rv = CheckForMemoryPressure(lowMemFd, nullptr); 1.139 + NS_ENSURE_SUCCESS(rv, rv); 1.140 + 1.141 + while (true) { 1.142 + // Wait for a notification on lowMemFd or for data to be written to 1.143 + // mShutdownPipeWrite. (poll(lowMemFd, POLLPRI) blocks until we're under 1.144 + // memory pressure.) 1.145 + struct pollfd pollfds[2]; 1.146 + pollfds[0].fd = lowMemFd; 1.147 + pollfds[0].events = POLLPRI; 1.148 + pollfds[1].fd = mShutdownPipeRead; 1.149 + pollfds[1].events = POLLIN; 1.150 + 1.151 + int pollRv; 1.152 + do { 1.153 + pollRv = poll(pollfds, ArrayLength(pollfds), /* timeout */ -1); 1.154 + } while (pollRv == -1 && errno == EINTR); 1.155 + 1.156 + if (pollfds[1].revents) { 1.157 + // Something was written to our shutdown pipe; we're outta here. 1.158 + LOG("shutting down (1)"); 1.159 + return NS_OK; 1.160 + } 1.161 + 1.162 + // If pollfds[1] isn't happening, pollfds[0] ought to be! 1.163 + if (!(pollfds[0].revents & POLLPRI)) { 1.164 + LOG("Unexpected revents value after poll(): %d. " 1.165 + "Shutting down GonkMemoryPressureMonitoring.", pollfds[0].revents); 1.166 + return NS_ERROR_FAILURE; 1.167 + } 1.168 + 1.169 + // POLLPRI on lowMemFd indicates that we're in a low-memory situation. We 1.170 + // could read lowMemFd to double-check, but we've observed that the read 1.171 + // sometimes completes after the memory-pressure event is over, so let's 1.172 + // just believe the result of poll(). 1.173 + 1.174 + // We use low-memory-no-forward because each process has its own watcher 1.175 + // and thus there is no need for the main process to forward this event. 1.176 + rv = DispatchMemoryPressure(MemPressure_New); 1.177 + NS_ENSURE_SUCCESS(rv, rv); 1.178 + 1.179 + // Manually check lowMemFd until we observe that memory pressure is over. 1.180 + // We won't fire any more low-memory events until we observe that 1.181 + // we're no longer under pressure. Instead, we fire low-memory-ongoing 1.182 + // events, which cause processes to keep flushing caches but will not 1.183 + // trigger expensive GCs and other attempts to save memory that are 1.184 + // likely futile at this point. 1.185 + bool memoryPressure; 1.186 + do { 1.187 + { 1.188 + MonitorAutoLock lock(mMonitor); 1.189 + 1.190 + // We need to check mShuttingDown before we wait here, in order to 1.191 + // catch a shutdown signal sent after we poll()'ed mShutdownPipeRead 1.192 + // above but before we started waiting on the monitor. But we don't 1.193 + // need to check after we wait, because we'll either do another 1.194 + // iteration of this inner loop, in which case we'll check 1.195 + // mShuttingDown, or we'll exit this loop and do another iteration 1.196 + // of the outer loop, in which case we'll check the shutdown pipe. 1.197 + if (mShuttingDown) { 1.198 + LOG("shutting down (2)"); 1.199 + return NS_OK; 1.200 + } 1.201 + mMonitor.Wait(PR_MillisecondsToInterval(mPollMS)); 1.202 + } 1.203 + 1.204 + LOG("Checking to see if memory pressure is over."); 1.205 + rv = CheckForMemoryPressure(lowMemFd, &memoryPressure); 1.206 + NS_ENSURE_SUCCESS(rv, rv); 1.207 + 1.208 + if (memoryPressure) { 1.209 + rv = DispatchMemoryPressure(MemPressure_Ongoing); 1.210 + NS_ENSURE_SUCCESS(rv, rv); 1.211 + continue; 1.212 + } 1.213 + } while (false); 1.214 + 1.215 + LOG("Memory pressure is over."); 1.216 + } 1.217 + 1.218 + return NS_OK; 1.219 + } 1.220 + 1.221 +private: 1.222 + /** 1.223 + * Read from aLowMemFd, which we assume corresponds to the 1.224 + * notify_trigger_active sysfs node, and determine whether we're currently 1.225 + * under memory pressure. 1.226 + * 1.227 + * We don't expect this method to block. 1.228 + */ 1.229 + nsresult CheckForMemoryPressure(int aLowMemFd, bool* aOut) 1.230 + { 1.231 + if (aOut) { 1.232 + *aOut = false; 1.233 + } 1.234 + 1.235 + lseek(aLowMemFd, 0, SEEK_SET); 1.236 + 1.237 + char buf[2]; 1.238 + int nread; 1.239 + do { 1.240 + nread = read(aLowMemFd, buf, sizeof(buf)); 1.241 + } while(nread == -1 && errno == EINTR); 1.242 + NS_ENSURE_STATE(nread == 2); 1.243 + 1.244 + // The notify_trigger_active sysfs node should contain either "0\n" or 1.245 + // "1\n". The latter indicates memory pressure. 1.246 + if (aOut) { 1.247 + *aOut = buf[0] == '1' && buf[1] == '\n'; 1.248 + } 1.249 + return NS_OK; 1.250 + } 1.251 + 1.252 + /** 1.253 + * Dispatch the specified memory pressure event unless a high-priority 1.254 + * process is present. If a high-priority process is present then it's likely 1.255 + * responding to an urgent event (an incoming call or message for example) so 1.256 + * avoid wasting CPU time responding to low-memory events. 1.257 + */ 1.258 + nsresult DispatchMemoryPressure(MemoryPressureState state) 1.259 + { 1.260 + if (ProcessPriorityManager::AnyProcessHasHighPriority()) { 1.261 + return NS_OK; 1.262 + } 1.263 + 1.264 + return NS_DispatchMemoryPressure(state); 1.265 + } 1.266 + 1.267 + Monitor mMonitor; 1.268 + uint32_t mPollMS; 1.269 + bool mShuttingDown; 1.270 + 1.271 + ScopedClose mShutdownPipeRead; 1.272 + ScopedClose mShutdownPipeWrite; 1.273 +}; 1.274 + 1.275 +NS_IMPL_ISUPPORTS(MemoryPressureWatcher, nsIRunnable, nsIObserver); 1.276 + 1.277 +} // anonymous namespace 1.278 + 1.279 +namespace mozilla { 1.280 + 1.281 +void 1.282 +InitGonkMemoryPressureMonitoring() 1.283 +{ 1.284 + // memoryPressureWatcher is held alive by the observer service. 1.285 + nsRefPtr<MemoryPressureWatcher> memoryPressureWatcher = 1.286 + new MemoryPressureWatcher(); 1.287 + NS_ENSURE_SUCCESS_VOID(memoryPressureWatcher->Init()); 1.288 + 1.289 + nsCOMPtr<nsIThread> thread; 1.290 + NS_NewThread(getter_AddRefs(thread), memoryPressureWatcher); 1.291 +} 1.292 + 1.293 +} // namespace mozilla