|
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/. */ |
|
6 |
|
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> |
|
22 |
|
23 #define LOG(args...) \ |
|
24 __android_log_print(ANDROID_LOG_INFO, "GonkMemoryPressure" , ## args) |
|
25 |
|
26 #ifdef MOZ_NUWA_PROCESS |
|
27 #include "ipc/Nuwa.h" |
|
28 #endif |
|
29 |
|
30 using namespace mozilla; |
|
31 |
|
32 namespace { |
|
33 |
|
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. |
|
38 * |
|
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. |
|
43 * |
|
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.) |
|
50 * |
|
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. |
|
57 * |
|
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. |
|
62 */ |
|
63 class MemoryPressureWatcher |
|
64 : public nsIRunnable |
|
65 , public nsIObserver |
|
66 { |
|
67 public: |
|
68 MemoryPressureWatcher() |
|
69 : mMonitor("MemoryPressureWatcher") |
|
70 , mShuttingDown(false) |
|
71 { |
|
72 } |
|
73 |
|
74 NS_DECL_THREADSAFE_ISUPPORTS |
|
75 |
|
76 nsresult Init() |
|
77 { |
|
78 nsCOMPtr<nsIObserverService> os = services::GetObserverService(); |
|
79 NS_ENSURE_STATE(os); |
|
80 |
|
81 // The observer service holds us alive. |
|
82 os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, /* holdsWeak */ false); |
|
83 |
|
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); |
|
90 |
|
91 int pipes[2]; |
|
92 NS_ENSURE_STATE(!pipe(pipes)); |
|
93 mShutdownPipeRead = pipes[0]; |
|
94 mShutdownPipeWrite = pipes[1]; |
|
95 return NS_OK; |
|
96 } |
|
97 |
|
98 NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, |
|
99 const char16_t* aData) |
|
100 { |
|
101 MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0); |
|
102 LOG("Observed XPCOM shutdown."); |
|
103 |
|
104 MonitorAutoLock lock(mMonitor); |
|
105 mShuttingDown = true; |
|
106 mMonitor.Notify(); |
|
107 |
|
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); |
|
114 |
|
115 return NS_OK; |
|
116 } |
|
117 |
|
118 NS_IMETHOD Run() |
|
119 { |
|
120 MOZ_ASSERT(!NS_IsMainThread()); |
|
121 |
|
122 #ifdef MOZ_NUWA_PROCESS |
|
123 if (IsNuwaProcess()) { |
|
124 NS_ASSERTION(NuwaMarkCurrentThread != nullptr, |
|
125 "NuwaMarkCurrentThread is undefined!"); |
|
126 NuwaMarkCurrentThread(nullptr, nullptr); |
|
127 } |
|
128 #endif |
|
129 |
|
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); |
|
134 |
|
135 nsresult rv = CheckForMemoryPressure(lowMemFd, nullptr); |
|
136 NS_ENSURE_SUCCESS(rv, rv); |
|
137 |
|
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; |
|
147 |
|
148 int pollRv; |
|
149 do { |
|
150 pollRv = poll(pollfds, ArrayLength(pollfds), /* timeout */ -1); |
|
151 } while (pollRv == -1 && errno == EINTR); |
|
152 |
|
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; |
|
157 } |
|
158 |
|
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; |
|
164 } |
|
165 |
|
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(). |
|
170 |
|
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); |
|
175 |
|
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 { |
|
184 { |
|
185 MonitorAutoLock lock(mMonitor); |
|
186 |
|
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; |
|
197 } |
|
198 mMonitor.Wait(PR_MillisecondsToInterval(mPollMS)); |
|
199 } |
|
200 |
|
201 LOG("Checking to see if memory pressure is over."); |
|
202 rv = CheckForMemoryPressure(lowMemFd, &memoryPressure); |
|
203 NS_ENSURE_SUCCESS(rv, rv); |
|
204 |
|
205 if (memoryPressure) { |
|
206 rv = DispatchMemoryPressure(MemPressure_Ongoing); |
|
207 NS_ENSURE_SUCCESS(rv, rv); |
|
208 continue; |
|
209 } |
|
210 } while (false); |
|
211 |
|
212 LOG("Memory pressure is over."); |
|
213 } |
|
214 |
|
215 return NS_OK; |
|
216 } |
|
217 |
|
218 private: |
|
219 /** |
|
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. |
|
223 * |
|
224 * We don't expect this method to block. |
|
225 */ |
|
226 nsresult CheckForMemoryPressure(int aLowMemFd, bool* aOut) |
|
227 { |
|
228 if (aOut) { |
|
229 *aOut = false; |
|
230 } |
|
231 |
|
232 lseek(aLowMemFd, 0, SEEK_SET); |
|
233 |
|
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); |
|
240 |
|
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'; |
|
245 } |
|
246 return NS_OK; |
|
247 } |
|
248 |
|
249 /** |
|
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. |
|
254 */ |
|
255 nsresult DispatchMemoryPressure(MemoryPressureState state) |
|
256 { |
|
257 if (ProcessPriorityManager::AnyProcessHasHighPriority()) { |
|
258 return NS_OK; |
|
259 } |
|
260 |
|
261 return NS_DispatchMemoryPressure(state); |
|
262 } |
|
263 |
|
264 Monitor mMonitor; |
|
265 uint32_t mPollMS; |
|
266 bool mShuttingDown; |
|
267 |
|
268 ScopedClose mShutdownPipeRead; |
|
269 ScopedClose mShutdownPipeWrite; |
|
270 }; |
|
271 |
|
272 NS_IMPL_ISUPPORTS(MemoryPressureWatcher, nsIRunnable, nsIObserver); |
|
273 |
|
274 } // anonymous namespace |
|
275 |
|
276 namespace mozilla { |
|
277 |
|
278 void |
|
279 InitGonkMemoryPressureMonitoring() |
|
280 { |
|
281 // memoryPressureWatcher is held alive by the observer service. |
|
282 nsRefPtr<MemoryPressureWatcher> memoryPressureWatcher = |
|
283 new MemoryPressureWatcher(); |
|
284 NS_ENSURE_SUCCESS_VOID(memoryPressureWatcher->Init()); |
|
285 |
|
286 nsCOMPtr<nsIThread> thread; |
|
287 NS_NewThread(getter_AddRefs(thread), memoryPressureWatcher); |
|
288 } |
|
289 |
|
290 } // namespace mozilla |