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 // 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();
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
));
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.
87 // Set the crash annotation to its initial state.
88 UpdateCrashAnnotation(lock
);
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.
118 // Check /proc/meminfo for low memory. Largely C method for reading
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.
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
);
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.
159 // We will use this to poll for low memory.
161 nsAvailableMemoryWatcher::Notify(nsITimer
* aTimer
) {
162 MutexAutoLock
lock(mMutex
);
164 // If we've made it this far and there's no |mThread|,
165 // we might have failed to dispatch it for some reason.
167 return NS_ERROR_FAILURE
;
169 nsresult rv
= mThread
->Dispatch(
170 NS_NewRunnableFunction("MemoryPoller", [self
= RefPtr
{this}]() {
171 if (self
->IsMemoryLow()) {
172 self
->HandleLowMemory();
174 self
->MaybeHandleHighMemory();
179 NS_WARNING("Cannot dispatch memory polling event.");
184 void nsAvailableMemoryWatcher::HandleLowMemory() {
185 MutexAutoLock
lock(mMutex
);
187 // We have been shut down from outside while in flight.
190 if (!mUnderMemoryPressure
) {
191 mUnderMemoryPressure
= true;
192 UpdateCrashAnnotation(lock
);
193 // Poll more frequently under memory pressure.
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
);
220 // We have been shut down from outside while in flight.
223 if (mUnderMemoryPressure
) {
224 RecordTelemetryEventOnHighMemory(lock
);
225 NS_NotifyOfEventualMemoryPressure(MemoryPressureState::NoPressure
);
226 mUnderMemoryPressure
= false;
227 UpdateCrashAnnotation(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
;
240 // Restart the timer with the new interval if it has stopped.
241 // For testing, use a small polling interval.
243 mTimer
->InitWithCallback(this, gIsGtest
? 10 : pollingInterval
,
244 nsITimer::TYPE_REPEATING_SLACK
))) {
248 mTimer
->SetDelay(gIsGtest
? 10 : pollingInterval
);
252 // Observe events for shutting down and starting/stopping the timer.
254 nsAvailableMemoryWatcher::Observe(nsISupports
* aSubject
, const char* aTopic
,
255 const char16_t
* aData
) {
256 nsresult rv
= nsAvailableMemoryWatcherBase::Observe(aSubject
, aTopic
, aData
);
261 if (strcmp(aTopic
, "xpcom-shutdown") == 0) {
264 MutexAutoLock
lock(mMutex
);
266 if (strcmp(aTopic
, "user-interaction-active") == 0) {
268 } else if (strcmp(aTopic
, "user-interaction-inactive") == 0) {
277 NS_IMETHODIMP
nsAvailableMemoryWatcher::GetName(nsACString
& aName
) {
278 aName
.AssignLiteral("nsAvailableMemoryWatcher");
282 } // namespace mozilla