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"
15 #include "nsIThread.h"
16 #include "nsMemoryPressure.h"
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
,
25 public nsAvailableMemoryWatcherBase
{
27 NS_DECL_ISUPPORTS_INHERITED
28 NS_DECL_NSITIMERCALLBACK
32 nsresult
Init() override
;
33 nsAvailableMemoryWatcher();
35 void HandleLowMemory();
36 void MaybeHandleHighMemory();
39 ~nsAvailableMemoryWatcher() = default;
40 void StartPolling(const MutexAutoLock
&);
41 void StopPolling(const MutexAutoLock
&);
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().
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()
71 mUnderMemoryPressure(false),
72 mMutex("Memory Poller mutex") {}
74 nsresult
nsAvailableMemoryWatcher::Init() {
75 nsresult rv
= nsAvailableMemoryWatcherBase::Init();
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
));
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.
93 // Set the crash annotation to its initial state.
94 UpdateCrashAnnotation(lock
);
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
,
115 void nsAvailableMemoryWatcher::StopPolling(const MutexAutoLock
&)
116 MOZ_REQUIRES(mMutex
) {
117 if (mPolling
&& mTimer
) {
118 // stop dispatching memory checks to the thread.
124 // Check /proc/meminfo for low memory. Largely C method for reading
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.
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) {
151 void nsAvailableMemoryWatcher::ShutDown() {
152 nsCOMPtr
<nsIThread
> thread
;
154 MutexAutoLock
lock(mMutex
);
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.
169 // We will use this to poll for low memory.
171 nsAvailableMemoryWatcher::Notify(nsITimer
* aTimer
) {
172 MutexAutoLock
lock(mMutex
);
174 // If we've made it this far and there's no |mThread|,
175 // we might have failed to dispatch it for some reason.
177 return NS_ERROR_FAILURE
;
179 nsresult rv
= mThread
->Dispatch(
180 NS_NewRunnableFunction("MemoryPoller", [self
= RefPtr
{this}]() {
181 if (self
->IsMemoryLow()) {
182 self
->HandleLowMemory();
184 self
->MaybeHandleHighMemory();
189 NS_WARNING("Cannot dispatch memory polling event.");
194 void nsAvailableMemoryWatcher::HandleLowMemory() {
195 MutexAutoLock
lock(mMutex
);
197 // We have been shut down from outside while in flight.
200 if (!mUnderMemoryPressure
) {
201 mUnderMemoryPressure
= true;
202 UpdateCrashAnnotation(lock
);
203 // Poll more frequently under memory pressure.
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
);
228 // We have been shut down from outside while in flight.
231 if (mUnderMemoryPressure
) {
232 RecordTelemetryEventOnHighMemory();
233 NS_NotifyOfEventualMemoryPressure(MemoryPressureState::NoPressure
);
234 mUnderMemoryPressure
= false;
235 UpdateCrashAnnotation(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
;
248 // Restart the timer with the new interval if it has stopped.
249 // For testing, use a small polling interval.
251 mTimer
->InitWithCallback(this, gIsGtest
? 10 : pollingInterval
,
252 nsITimer::TYPE_REPEATING_SLACK
))) {
256 mTimer
->SetDelay(gIsGtest
? 10 : pollingInterval
);
260 // Observe events for shutting down and starting/stopping the timer.
262 nsAvailableMemoryWatcher::Observe(nsISupports
* aSubject
, const char* aTopic
,
263 const char16_t
* aData
) {
264 nsresult rv
= nsAvailableMemoryWatcherBase::Observe(aSubject
, aTopic
, aData
);
269 if (strcmp(aTopic
, "xpcom-shutdown") == 0) {
272 MutexAutoLock
lock(mMutex
);
274 if (strcmp(aTopic
, "user-interaction-active") == 0) {
276 } else if (strcmp(aTopic
, "user-interaction-inactive") == 0) {
285 NS_IMETHODIMP
nsAvailableMemoryWatcher::GetName(nsACString
& aName
) {
286 aName
.AssignLiteral("nsAvailableMemoryWatcher");
290 } // namespace mozilla