Bug 1919083 - [ci] Enable os-integration variant for more suites, r=jmaher
[gecko.git] / xpcom / base / AvailableMemoryWatcherLinux.cpp
blobde8a1d79fce2980613f514dbbfabcd5c171e6554
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 // Polling interval to check for low memory. In high memory scenarios,
53 // default to 5000 ms between each check.
54 static const uint32_t kHighMemoryPollingIntervalMS = 5000;
56 // Polling interval to check for low memory. Default to 1000 ms between each
57 // check. Use this interval when memory is low,
58 static const uint32_t kLowMemoryPollingIntervalMS = 1000;
61 // A modern version of linux should keep memory information in the
62 // /proc/meminfo path.
63 static const char* kMeminfoPath = "/proc/meminfo";
65 nsAvailableMemoryWatcher::nsAvailableMemoryWatcher()
66 : mPolling(false), mUnderMemoryPressure(false) {}
68 nsresult nsAvailableMemoryWatcher::Init() {
69 nsresult rv = nsAvailableMemoryWatcherBase::Init();
70 if (NS_FAILED(rv)) {
71 return rv;
73 MutexAutoLock lock(mMutex);
74 mTimer = NS_NewTimer();
75 nsCOMPtr<nsIThread> thread;
76 // We have to make our own thread here instead of using the background pool,
77 // because some low memory scenarios can cause the background pool to fill.
78 rv = NS_NewNamedThread("MemoryPoller", getter_AddRefs(thread));
79 if (NS_FAILED(rv)) {
80 NS_WARNING("Couldn't make a thread for nsAvailableMemoryWatcher.");
81 // In this scenario we can't poll for low memory, since we can't dispatch
82 // to our memory watcher thread.
83 return rv;
85 mThread = thread;
87 // Set the crash annotation to its initial state.
88 UpdateCrashAnnotation(lock);
90 StartPolling(lock);
92 return NS_OK;
95 already_AddRefed<nsAvailableMemoryWatcherBase> CreateAvailableMemoryWatcher() {
96 RefPtr watcher(new nsAvailableMemoryWatcher);
98 if (NS_FAILED(watcher->Init())) {
99 return do_AddRef(new nsAvailableMemoryWatcherBase);
102 return watcher.forget();
105 NS_IMPL_ISUPPORTS_INHERITED(nsAvailableMemoryWatcher,
106 nsAvailableMemoryWatcherBase, nsITimerCallback,
107 nsIObserver, nsINamed);
109 void nsAvailableMemoryWatcher::StopPolling(const MutexAutoLock&)
110 MOZ_REQUIRES(mMutex) {
111 if (mPolling && mTimer) {
112 // stop dispatching memory checks to the thread.
113 mTimer->Cancel();
114 mPolling = false;
118 // Check /proc/meminfo for low memory. Largely C method for reading
119 // /proc/meminfo.
120 /* static */
121 bool nsAvailableMemoryWatcher::IsMemoryLow() {
122 MemoryInfo memInfo{0, 0};
123 nsresult rv = ReadMemoryFile(kMeminfoPath, memInfo);
125 if (NS_FAILED(rv) || (memInfo.memAvailable == 0) || (memInfo.memTotal == 0)) {
126 // If memAvailable cannot be found, then we are using an older system.
127 // We can't accurately poll on this.
128 // If memTotal is zero we can't calculate how much memory we're using.
129 return false;
132 unsigned long memoryAsPercentage =
133 (memInfo.memAvailable * 100) / memInfo.memTotal;
135 return memoryAsPercentage <=
136 StaticPrefs::browser_low_commit_space_threshold_percent() ||
137 memInfo.memAvailable <
138 StaticPrefs::browser_low_commit_space_threshold_mb() * 1024;
141 void nsAvailableMemoryWatcher::ShutDown() {
142 nsCOMPtr<nsIThread> thread;
144 MutexAutoLock lock(mMutex);
145 if (mTimer) {
146 mTimer->Cancel();
147 mTimer = nullptr;
149 thread = mThread.forget();
151 // thread->Shutdown() spins a nested event loop while waiting for the thread
152 // to end. But the thread might execute some previously dispatched event that
153 // wants to lock our mutex, too, before arriving at the shutdown event.
154 if (thread) {
155 thread->Shutdown();
159 // We will use this to poll for low memory.
160 NS_IMETHODIMP
161 nsAvailableMemoryWatcher::Notify(nsITimer* aTimer) {
162 MutexAutoLock lock(mMutex);
163 if (!mThread) {
164 // If we've made it this far and there's no |mThread|,
165 // we might have failed to dispatch it for some reason.
166 MOZ_ASSERT(mThread);
167 return NS_ERROR_FAILURE;
169 nsresult rv = mThread->Dispatch(
170 NS_NewRunnableFunction("MemoryPoller", [self = RefPtr{this}]() {
171 if (self->IsMemoryLow()) {
172 self->HandleLowMemory();
173 } else {
174 self->MaybeHandleHighMemory();
176 }));
178 if NS_FAILED (rv) {
179 NS_WARNING("Cannot dispatch memory polling event.");
181 return NS_OK;
184 void nsAvailableMemoryWatcher::HandleLowMemory() {
185 MutexAutoLock lock(mMutex);
186 if (!mTimer) {
187 // We have been shut down from outside while in flight.
188 return;
190 if (!mUnderMemoryPressure) {
191 mUnderMemoryPressure = true;
192 UpdateCrashAnnotation(lock);
193 // Poll more frequently under memory pressure.
194 StartPolling(lock);
196 UpdateLowMemoryTimeStamp();
197 // We handle low memory offthread, but we want to unload
198 // tabs only from the main thread, so we will dispatch this
199 // back to the main thread.
200 // Since we are doing this async, we don't need to unlock the mutex first;
201 // the AutoLock will unlock the mutex when we finish the dispatch.
202 NS_DispatchToMainThread(NS_NewRunnableFunction(
203 "nsAvailableMemoryWatcher::OnLowMemory",
204 [self = RefPtr{this}]() { self->mTabUnloader->UnloadTabAsync(); }));
207 void nsAvailableMemoryWatcher::UpdateCrashAnnotation(const MutexAutoLock&)
208 MOZ_REQUIRES(mMutex) {
209 CrashReporter::RecordAnnotationBool(
210 CrashReporter::Annotation::LinuxUnderMemoryPressure,
211 mUnderMemoryPressure);
214 // If memory is not low, we may need to dispatch an
215 // event for it if we have been under memory pressure.
216 // We can also adjust our polling interval.
217 void nsAvailableMemoryWatcher::MaybeHandleHighMemory() {
218 MutexAutoLock lock(mMutex);
219 if (!mTimer) {
220 // We have been shut down from outside while in flight.
221 return;
223 if (mUnderMemoryPressure) {
224 RecordTelemetryEventOnHighMemory(lock);
225 NS_NotifyOfEventualMemoryPressure(MemoryPressureState::NoPressure);
226 mUnderMemoryPressure = false;
227 UpdateCrashAnnotation(lock);
229 StartPolling(lock);
232 // When we change the polling interval, we will need to restart the timer
233 // on the new interval.
234 void nsAvailableMemoryWatcher::StartPolling(const MutexAutoLock& aLock)
235 MOZ_REQUIRES(mMutex) {
236 uint32_t pollingInterval = mUnderMemoryPressure
237 ? kLowMemoryPollingIntervalMS
238 : kHighMemoryPollingIntervalMS;
239 if (!mPolling) {
240 // Restart the timer with the new interval if it has stopped.
241 // For testing, use a small polling interval.
242 if (NS_SUCCEEDED(
243 mTimer->InitWithCallback(this, gIsGtest ? 10 : pollingInterval,
244 nsITimer::TYPE_REPEATING_SLACK))) {
245 mPolling = true;
247 } else {
248 mTimer->SetDelay(gIsGtest ? 10 : pollingInterval);
252 // Observe events for shutting down and starting/stopping the timer.
253 NS_IMETHODIMP
254 nsAvailableMemoryWatcher::Observe(nsISupports* aSubject, const char* aTopic,
255 const char16_t* aData) {
256 nsresult rv = nsAvailableMemoryWatcherBase::Observe(aSubject, aTopic, aData);
257 if (NS_FAILED(rv)) {
258 return rv;
261 if (strcmp(aTopic, "xpcom-shutdown") == 0) {
262 ShutDown();
263 } else {
264 MutexAutoLock lock(mMutex);
265 if (mTimer) {
266 if (strcmp(aTopic, "user-interaction-active") == 0) {
267 StartPolling(lock);
268 } else if (strcmp(aTopic, "user-interaction-inactive") == 0) {
269 StopPolling(lock);
274 return NS_OK;
277 NS_IMETHODIMP nsAvailableMemoryWatcher::GetName(nsACString& aName) {
278 aName.AssignLiteral("nsAvailableMemoryWatcher");
279 return NS_OK;
282 } // namespace mozilla