Bumping manifests a=b2g-bump
[gecko.git] / widget / gonk / GonkMemoryPressureMonitoring.cpp
blob949f36c5bf16323925ae0a83db2a70aee55fa804
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "GonkMemoryPressureMonitoring.h"
8 #include "mozilla/ArrayUtils.h"
9 #include "mozilla/FileUtils.h"
10 #include "mozilla/Monitor.h"
11 #include "mozilla/Preferences.h"
12 #include "mozilla/ProcessPriorityManager.h"
13 #include "mozilla/Services.h"
14 #include "nsIObserver.h"
15 #include "nsIObserverService.h"
16 #include "nsMemoryPressure.h"
17 #include "nsThreadUtils.h"
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <poll.h>
21 #include <android/log.h>
23 #define LOG(args...) \
24 __android_log_print(ANDROID_LOG_INFO, "GonkMemoryPressure" , ## args)
26 #ifdef MOZ_NUWA_PROCESS
27 #include "ipc/Nuwa.h"
28 #endif
30 using namespace mozilla;
32 namespace {
34 /**
35 * MemoryPressureWatcher watches sysfs from its own thread to notice when the
36 * system is under memory pressure. When we observe memory pressure, we use
37 * MemoryPressureRunnable to notify observers that they should release memory.
39 * When the system is under memory pressure, we don't want to constantly fire
40 * memory-pressure events. So instead, we try to detect when sysfs indicates
41 * that we're no longer under memory pressure, and only then start firing events
42 * again.
44 * (This is a bit problematic because we can't poll() to detect when we're no
45 * longer under memory pressure; instead we have to periodically read the sysfs
46 * node. If we remain under memory pressure for a long time, this means we'll
47 * continue waking up to read from the node for a long time, potentially wasting
48 * battery life. Hopefully we don't hit this case in practice! We write to
49 * logcat each time we go around this loop so it's at least noticable.)
51 * Shutting down safely is a bit of a chore. XPCOM won't shut down until all
52 * threads exit, so we need to exit the Run() method below on shutdown. But our
53 * thread might be blocked in one of two situations: We might be poll()'ing the
54 * sysfs node waiting for memory pressure to occur, or we might be asleep
55 * waiting to read() the sysfs node to see if we're no longer under memory
56 * pressure.
58 * To let us wake up from the poll(), we poll() not just the sysfs node but also
59 * a pipe, which we write to on shutdown. To let us wake up from sleeping
60 * between read()s, we sleep by Wait()'ing on a monitor, which we notify on
61 * shutdown.
63 class MemoryPressureWatcher
64 : public nsIRunnable
65 , public nsIObserver
67 public:
68 MemoryPressureWatcher()
69 : mMonitor("MemoryPressureWatcher")
70 , mShuttingDown(false)
74 NS_DECL_THREADSAFE_ISUPPORTS
76 nsresult Init()
78 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
79 NS_ENSURE_STATE(os);
81 // The observer service holds us alive.
82 os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, /* holdsWeak */ false);
84 // While we're under memory pressure, we periodically read()
85 // notify_trigger_active to try and see when we're no longer under memory
86 // pressure. mPollMS indicates how many milliseconds we wait between those
87 // read()s.
88 mPollMS = Preferences::GetUint("gonk.systemMemoryPressureRecoveryPollMS",
89 /* default */ 5000);
91 int pipes[2];
92 NS_ENSURE_STATE(!pipe(pipes));
93 mShutdownPipeRead = pipes[0];
94 mShutdownPipeWrite = pipes[1];
95 return NS_OK;
98 NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
99 const char16_t* aData)
101 MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
102 LOG("Observed XPCOM shutdown.");
104 MonitorAutoLock lock(mMonitor);
105 mShuttingDown = true;
106 mMonitor.Notify();
108 int rv;
109 do {
110 // Write something to the pipe; doesn't matter what.
111 uint32_t dummy = 0;
112 rv = write(mShutdownPipeWrite, &dummy, sizeof(dummy));
113 } while(rv == -1 && errno == EINTR);
115 return NS_OK;
118 NS_IMETHOD Run()
120 MOZ_ASSERT(!NS_IsMainThread());
122 #ifdef MOZ_NUWA_PROCESS
123 if (IsNuwaProcess()) {
124 NS_ASSERTION(NuwaMarkCurrentThread != nullptr,
125 "NuwaMarkCurrentThread is undefined!");
126 NuwaMarkCurrentThread(nullptr, nullptr);
128 #endif
130 int lowMemFd = open("/sys/kernel/mm/lowmemkiller/notify_trigger_active",
131 O_RDONLY | O_CLOEXEC);
132 NS_ENSURE_STATE(lowMemFd != -1);
133 ScopedClose autoClose(lowMemFd);
135 nsresult rv = CheckForMemoryPressure(lowMemFd, nullptr);
136 NS_ENSURE_SUCCESS(rv, rv);
138 while (true) {
139 // Wait for a notification on lowMemFd or for data to be written to
140 // mShutdownPipeWrite. (poll(lowMemFd, POLLPRI) blocks until we're under
141 // memory pressure.)
142 struct pollfd pollfds[2];
143 pollfds[0].fd = lowMemFd;
144 pollfds[0].events = POLLPRI;
145 pollfds[1].fd = mShutdownPipeRead;
146 pollfds[1].events = POLLIN;
148 int pollRv;
149 do {
150 pollRv = poll(pollfds, ArrayLength(pollfds), /* timeout */ -1);
151 } while (pollRv == -1 && errno == EINTR);
153 if (pollfds[1].revents) {
154 // Something was written to our shutdown pipe; we're outta here.
155 LOG("shutting down (1)");
156 return NS_OK;
159 // If pollfds[1] isn't happening, pollfds[0] ought to be!
160 if (!(pollfds[0].revents & POLLPRI)) {
161 LOG("Unexpected revents value after poll(): %d. "
162 "Shutting down GonkMemoryPressureMonitoring.", pollfds[0].revents);
163 return NS_ERROR_FAILURE;
166 // POLLPRI on lowMemFd indicates that we're in a low-memory situation. We
167 // could read lowMemFd to double-check, but we've observed that the read
168 // sometimes completes after the memory-pressure event is over, so let's
169 // just believe the result of poll().
171 // We use low-memory-no-forward because each process has its own watcher
172 // and thus there is no need for the main process to forward this event.
173 rv = DispatchMemoryPressure(MemPressure_New);
174 NS_ENSURE_SUCCESS(rv, rv);
176 // Manually check lowMemFd until we observe that memory pressure is over.
177 // We won't fire any more low-memory events until we observe that
178 // we're no longer under pressure. Instead, we fire low-memory-ongoing
179 // events, which cause processes to keep flushing caches but will not
180 // trigger expensive GCs and other attempts to save memory that are
181 // likely futile at this point.
182 bool memoryPressure;
183 do {
185 MonitorAutoLock lock(mMonitor);
187 // We need to check mShuttingDown before we wait here, in order to
188 // catch a shutdown signal sent after we poll()'ed mShutdownPipeRead
189 // above but before we started waiting on the monitor. But we don't
190 // need to check after we wait, because we'll either do another
191 // iteration of this inner loop, in which case we'll check
192 // mShuttingDown, or we'll exit this loop and do another iteration
193 // of the outer loop, in which case we'll check the shutdown pipe.
194 if (mShuttingDown) {
195 LOG("shutting down (2)");
196 return NS_OK;
198 mMonitor.Wait(PR_MillisecondsToInterval(mPollMS));
201 LOG("Checking to see if memory pressure is over.");
202 rv = CheckForMemoryPressure(lowMemFd, &memoryPressure);
203 NS_ENSURE_SUCCESS(rv, rv);
205 if (memoryPressure) {
206 rv = DispatchMemoryPressure(MemPressure_Ongoing);
207 NS_ENSURE_SUCCESS(rv, rv);
208 continue;
210 } while (false);
212 LOG("Memory pressure is over.");
215 return NS_OK;
218 private:
220 * Read from aLowMemFd, which we assume corresponds to the
221 * notify_trigger_active sysfs node, and determine whether we're currently
222 * under memory pressure.
224 * We don't expect this method to block.
226 nsresult CheckForMemoryPressure(int aLowMemFd, bool* aOut)
228 if (aOut) {
229 *aOut = false;
232 lseek(aLowMemFd, 0, SEEK_SET);
234 char buf[2];
235 int nread;
236 do {
237 nread = read(aLowMemFd, buf, sizeof(buf));
238 } while(nread == -1 && errno == EINTR);
239 NS_ENSURE_STATE(nread == 2);
241 // The notify_trigger_active sysfs node should contain either "0\n" or
242 // "1\n". The latter indicates memory pressure.
243 if (aOut) {
244 *aOut = buf[0] == '1' && buf[1] == '\n';
246 return NS_OK;
250 * Dispatch the specified memory pressure event unless a high-priority
251 * process is present. If a high-priority process is present then it's likely
252 * responding to an urgent event (an incoming call or message for example) so
253 * avoid wasting CPU time responding to low-memory events.
255 nsresult DispatchMemoryPressure(MemoryPressureState state)
257 if (ProcessPriorityManager::AnyProcessHasHighPriority()) {
258 return NS_OK;
261 return NS_DispatchMemoryPressure(state);
264 Monitor mMonitor;
265 uint32_t mPollMS;
266 bool mShuttingDown;
268 ScopedClose mShutdownPipeRead;
269 ScopedClose mShutdownPipeWrite;
272 NS_IMPL_ISUPPORTS(MemoryPressureWatcher, nsIRunnable, nsIObserver);
274 } // anonymous namespace
276 namespace mozilla {
278 void
279 InitGonkMemoryPressureMonitoring()
281 // memoryPressureWatcher is held alive by the observer service.
282 nsRefPtr<MemoryPressureWatcher> memoryPressureWatcher =
283 new MemoryPressureWatcher();
284 NS_ENSURE_SUCCESS_VOID(memoryPressureWatcher->Init());
286 nsCOMPtr<nsIThread> thread;
287 NS_NewNamedThread("MemoryPressure", getter_AddRefs(thread),
288 memoryPressureWatcher);
291 } // namespace mozilla