Bug 1825336 - Make toolkit/components/url-classifier/ buildable outside of a unified...
[gecko.git] / xpcom / base / AvailableMemoryWatcherLinux.cpp
blob094a1c3e2cf2d52c65bc508b024a86e53b9f33f2
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=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 #include "AvailableMemoryWatcher.h"
7 #include "AvailableMemoryWatcherUtils.h"
8 #include "mozilla/Services.h"
9 #include "mozilla/StaticPrefs_browser.h"
10 #include "mozilla/Unused.h"
11 #include "nsAppRunner.h"
12 #include "nsIObserverService.h"
13 #include "nsISupports.h"
14 #include "nsITimer.h"
15 #include "nsIThread.h"
16 #include "nsMemoryPressure.h"
18 namespace mozilla {
20 // Linux has no native low memory detection. This class creates a timer that
21 // polls for low memory and sends a low memory notification if it notices a
22 // memory pressure event.
23 class nsAvailableMemoryWatcher final : public nsITimerCallback,
24 public nsINamed,
25 public nsAvailableMemoryWatcherBase {
26 public:
27 NS_DECL_ISUPPORTS_INHERITED
28 NS_DECL_NSITIMERCALLBACK
29 NS_DECL_NSIOBSERVER
30 NS_DECL_NSINAMED
32 nsresult Init() override;
33 nsAvailableMemoryWatcher();
35 void HandleLowMemory();
36 void MaybeHandleHighMemory();
38 private:
39 ~nsAvailableMemoryWatcher() = default;
40 void StartPolling(const MutexAutoLock&);
41 void StopPolling(const MutexAutoLock&);
42 void ShutDown();
43 void UpdateCrashAnnotation(const MutexAutoLock&);
44 static bool IsMemoryLow();
46 nsCOMPtr<nsITimer> mTimer MOZ_GUARDED_BY(mMutex);
47 nsCOMPtr<nsIThread> mThread MOZ_GUARDED_BY(mMutex);
49 bool mPolling MOZ_GUARDED_BY(mMutex);
50 bool mUnderMemoryPressure MOZ_GUARDED_BY(mMutex);
52 // We might tell polling to start/stop from our polling thread
53 // or from the main thread during ::Observe().
54 Mutex mMutex;
56 // Polling interval to check for low memory. In high memory scenarios,
57 // default to 5000 ms between each check.
58 static const uint32_t kHighMemoryPollingIntervalMS = 5000;
60 // Polling interval to check for low memory. Default to 1000 ms between each
61 // check. Use this interval when memory is low,
62 static const uint32_t kLowMemoryPollingIntervalMS = 1000;
65 // A modern version of linux should keep memory information in the
66 // /proc/meminfo path.
67 static const char* kMeminfoPath = "/proc/meminfo";
69 nsAvailableMemoryWatcher::nsAvailableMemoryWatcher()
70 : mPolling(false),
71 mUnderMemoryPressure(false),
72 mMutex("Memory Poller mutex") {}
74 nsresult nsAvailableMemoryWatcher::Init() {
75 nsresult rv = nsAvailableMemoryWatcherBase::Init();
76 if (NS_FAILED(rv)) {
77 return rv;
79 MutexAutoLock lock(mMutex);
80 mTimer = NS_NewTimer();
81 nsCOMPtr<nsIThread> thread;
82 // We have to make our own thread here instead of using the background pool,
83 // because some low memory scenarios can cause the background pool to fill.
84 rv = NS_NewNamedThread("MemoryPoller", getter_AddRefs(thread));
85 if (NS_FAILED(rv)) {
86 NS_WARNING("Couldn't make a thread for nsAvailableMemoryWatcher.");
87 // In this scenario we can't poll for low memory, since we can't dispatch
88 // to our memory watcher thread.
89 return rv;
91 mThread = thread;
93 // Set the crash annotation to its initial state.
94 UpdateCrashAnnotation(lock);
96 StartPolling(lock);
98 return NS_OK;
101 already_AddRefed<nsAvailableMemoryWatcherBase> CreateAvailableMemoryWatcher() {
102 RefPtr watcher(new nsAvailableMemoryWatcher);
104 if (NS_FAILED(watcher->Init())) {
105 return do_AddRef(new nsAvailableMemoryWatcherBase);
108 return watcher.forget();
111 NS_IMPL_ISUPPORTS_INHERITED(nsAvailableMemoryWatcher,
112 nsAvailableMemoryWatcherBase, nsITimerCallback,
113 nsIObserver);
115 void nsAvailableMemoryWatcher::StopPolling(const MutexAutoLock&)
116 MOZ_REQUIRES(mMutex) {
117 if (mPolling && mTimer) {
118 // stop dispatching memory checks to the thread.
119 mTimer->Cancel();
120 mPolling = false;
124 // Check /proc/meminfo for low memory. Largely C method for reading
125 // /proc/meminfo.
126 /* static */
127 bool nsAvailableMemoryWatcher::IsMemoryLow() {
128 MemoryInfo memInfo{0, 0};
129 bool aResult = false;
131 nsresult rv = ReadMemoryFile(kMeminfoPath, memInfo);
133 if (NS_FAILED(rv) || memInfo.memAvailable == 0) {
134 // If memAvailable cannot be found, then we are using an older system.
135 // We can't accurately poll on this.
136 return aResult;
138 unsigned long memoryAsPercentage =
139 (memInfo.memAvailable * 100) / memInfo.memTotal;
141 if (memoryAsPercentage <=
142 StaticPrefs::browser_low_commit_space_threshold_percent() ||
143 memInfo.memAvailable <
144 StaticPrefs::browser_low_commit_space_threshold_mb() * 1024) {
145 aResult = true;
148 return aResult;
151 void nsAvailableMemoryWatcher::ShutDown() {
152 nsCOMPtr<nsIThread> thread;
154 MutexAutoLock lock(mMutex);
155 if (mTimer) {
156 mTimer->Cancel();
157 mTimer = nullptr;
159 thread = mThread.forget();
161 // thread->Shutdown() spins a nested event loop while waiting for the thread
162 // to end. But the thread might execute some previously dispatched event that
163 // wants to lock our mutex, too, before arriving at the shutdown event.
164 if (thread) {
165 thread->Shutdown();
169 // We will use this to poll for low memory.
170 NS_IMETHODIMP
171 nsAvailableMemoryWatcher::Notify(nsITimer* aTimer) {
172 MutexAutoLock lock(mMutex);
173 if (!mThread) {
174 // If we've made it this far and there's no |mThread|,
175 // we might have failed to dispatch it for some reason.
176 MOZ_ASSERT(mThread);
177 return NS_ERROR_FAILURE;
179 nsresult rv = mThread->Dispatch(
180 NS_NewRunnableFunction("MemoryPoller", [self = RefPtr{this}]() {
181 if (self->IsMemoryLow()) {
182 self->HandleLowMemory();
183 } else {
184 self->MaybeHandleHighMemory();
186 }));
188 if NS_FAILED (rv) {
189 NS_WARNING("Cannot dispatch memory polling event.");
191 return NS_OK;
194 void nsAvailableMemoryWatcher::HandleLowMemory() {
195 MutexAutoLock lock(mMutex);
196 if (!mTimer) {
197 // We have been shut down from outside while in flight.
198 return;
200 if (!mUnderMemoryPressure) {
201 mUnderMemoryPressure = true;
202 UpdateCrashAnnotation(lock);
203 // Poll more frequently under memory pressure.
204 StartPolling(lock);
206 UpdateLowMemoryTimeStamp();
207 // We handle low memory offthread, but we want to unload
208 // tabs only from the main thread, so we will dispatch this
209 // back to the main thread.
210 NS_DispatchToMainThread(NS_NewRunnableFunction(
211 "nsAvailableMemoryWatcher::OnLowMemory",
212 [self = RefPtr{this}]() { self->mTabUnloader->UnloadTabAsync(); }));
215 void nsAvailableMemoryWatcher::UpdateCrashAnnotation(const MutexAutoLock&)
216 MOZ_REQUIRES(mMutex) {
217 CrashReporter::AnnotateCrashReport(
218 CrashReporter::Annotation::LinuxUnderMemoryPressure,
219 mUnderMemoryPressure);
222 // If memory is not low, we may need to dispatch an
223 // event for it if we have been under memory pressure.
224 // We can also adjust our polling interval.
225 void nsAvailableMemoryWatcher::MaybeHandleHighMemory() {
226 MutexAutoLock lock(mMutex);
227 if (!mTimer) {
228 // We have been shut down from outside while in flight.
229 return;
231 if (mUnderMemoryPressure) {
232 RecordTelemetryEventOnHighMemory();
233 NS_NotifyOfEventualMemoryPressure(MemoryPressureState::NoPressure);
234 mUnderMemoryPressure = false;
235 UpdateCrashAnnotation(lock);
237 StartPolling(lock);
240 // When we change the polling interval, we will need to restart the timer
241 // on the new interval.
242 void nsAvailableMemoryWatcher::StartPolling(const MutexAutoLock& aLock)
243 MOZ_REQUIRES(mMutex) {
244 uint32_t pollingInterval = mUnderMemoryPressure
245 ? kLowMemoryPollingIntervalMS
246 : kHighMemoryPollingIntervalMS;
247 if (!mPolling) {
248 // Restart the timer with the new interval if it has stopped.
249 // For testing, use a small polling interval.
250 if (NS_SUCCEEDED(
251 mTimer->InitWithCallback(this, gIsGtest ? 10 : pollingInterval,
252 nsITimer::TYPE_REPEATING_SLACK))) {
253 mPolling = true;
255 } else {
256 mTimer->SetDelay(gIsGtest ? 10 : pollingInterval);
260 // Observe events for shutting down and starting/stopping the timer.
261 NS_IMETHODIMP
262 nsAvailableMemoryWatcher::Observe(nsISupports* aSubject, const char* aTopic,
263 const char16_t* aData) {
264 nsresult rv = nsAvailableMemoryWatcherBase::Observe(aSubject, aTopic, aData);
265 if (NS_FAILED(rv)) {
266 return rv;
269 if (strcmp(aTopic, "xpcom-shutdown") == 0) {
270 ShutDown();
271 } else {
272 MutexAutoLock lock(mMutex);
273 if (mTimer) {
274 if (strcmp(aTopic, "user-interaction-active") == 0) {
275 StartPolling(lock);
276 } else if (strcmp(aTopic, "user-interaction-inactive") == 0) {
277 StopPolling(lock);
282 return NS_OK;
285 NS_IMETHODIMP nsAvailableMemoryWatcher::GetName(nsACString& aName) {
286 aName.AssignLiteral("nsAvailableMemoryWatcher");
287 return NS_OK;
290 } // namespace mozilla