summaryrefslogtreecommitdiffstats
path: root/xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp')
-rw-r--r--xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp227
1 files changed, 227 insertions, 0 deletions
diff --git a/xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp b/xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp
new file mode 100644
index 0000000000..56d6f03ff8
--- /dev/null
+++ b/xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp
@@ -0,0 +1,227 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <sys/mman.h> // For memory-locking.
+
+#include "gtest/gtest.h"
+
+#include "AvailableMemoryWatcher.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "nsIObserverService.h"
+#include "nsISupports.h"
+#include "nsITimer.h"
+#include "nsMemoryPressure.h"
+
+using namespace mozilla;
+
+namespace {
+
+// Dummy tab unloader whose one job is to dispatch a low memory event.
+class MockTabUnloader final : public nsITabUnloader {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ public:
+ MockTabUnloader() = default;
+
+ NS_IMETHOD UnloadTabAsync() override {
+ // We want to issue a memory pressure event for
+ NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory);
+ return NS_OK;
+ }
+
+ private:
+ ~MockTabUnloader() = default;
+};
+
+NS_IMPL_ISUPPORTS(MockTabUnloader, nsITabUnloader)
+
+// Class that gradually increases the percent memory threshold
+// until it reaches 100%, which should guarantee a memory pressure
+// notification.
+class AvailableMemoryChecker final : public nsITimerCallback, public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ AvailableMemoryChecker();
+ void Init();
+ void Shutdown();
+
+ private:
+ ~AvailableMemoryChecker() = default;
+
+ bool mResolved;
+ nsCOMPtr<nsITimer> mTimer;
+ RefPtr<nsAvailableMemoryWatcherBase> mWatcher;
+ RefPtr<MockTabUnloader> mTabUnloader;
+
+ const uint32_t kPollingInterval = 50;
+ const uint32_t kPrefIncrement = 5;
+};
+
+AvailableMemoryChecker::AvailableMemoryChecker() : mResolved(false) {}
+
+NS_IMPL_ISUPPORTS(AvailableMemoryChecker, nsITimerCallback, nsINamed);
+
+void AvailableMemoryChecker::Init() {
+ mTabUnloader = new MockTabUnloader;
+
+ mWatcher = nsAvailableMemoryWatcherBase::GetSingleton();
+ mWatcher->RegisterTabUnloader(mTabUnloader);
+
+ mTimer = NS_NewTimer();
+ mTimer->InitWithCallback(this, kPollingInterval,
+ nsITimer::TYPE_REPEATING_SLACK);
+}
+
+void AvailableMemoryChecker::Shutdown() {
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ Preferences::ClearUser("browser.low_commit_space_threshold_percent");
+}
+
+// Timer callback to increase the pref threshold.
+NS_IMETHODIMP
+AvailableMemoryChecker::Notify(nsITimer* aTimer) {
+ uint32_t threshold =
+ StaticPrefs::browser_low_commit_space_threshold_percent();
+ if (threshold >= 100) {
+ mResolved = true;
+ return NS_OK;
+ }
+ threshold += kPrefIncrement;
+ Preferences::SetUint("browser.low_commit_space_threshold_percent", threshold);
+ return NS_OK;
+}
+
+NS_IMETHODIMP AvailableMemoryChecker::GetName(nsACString& aName) {
+ aName.AssignLiteral("AvailableMemoryChecker");
+ return NS_OK;
+}
+
+// Class that listens for a given notification, then records
+// if it was received.
+class Spinner final : public nsIObserver {
+ nsCOMPtr<nsIObserverService> mObserverSvc;
+ nsDependentCString mTopic;
+ bool mTopicObserved;
+
+ ~Spinner() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ Spinner(nsIObserverService* aObserverSvc, const char* aTopic)
+ : mObserverSvc(aObserverSvc), mTopic(aTopic), mTopicObserved(false) {}
+
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override {
+ if (mTopic == aTopic) {
+ mTopicObserved = true;
+ mObserverSvc->RemoveObserver(this, aTopic);
+
+ // Force the loop to move in case there is no event in the queue.
+ nsCOMPtr<nsIRunnable> dummyEvent = new Runnable(__func__);
+ NS_DispatchToMainThread(dummyEvent);
+ }
+ return NS_OK;
+ }
+ void StartListening() {
+ mObserverSvc->AddObserver(this, mTopic.get(), false);
+ }
+ bool TopicObserved() { return mTopicObserved; }
+ bool WaitForNotification();
+};
+NS_IMPL_ISUPPORTS(Spinner, nsIObserver);
+
+bool Spinner::WaitForNotification() {
+ bool isTimeout = false;
+
+ nsCOMPtr<nsITimer> timer;
+
+ // This timer should time us out if we never observe our notification.
+ // Set to 5000 since the memory checker should finish incrementing the
+ // pref by then, and if it hasn't then it is probably stuck somehow.
+ NS_NewTimerWithFuncCallback(
+ getter_AddRefs(timer),
+ [](nsITimer*, void* isTimeout) {
+ *reinterpret_cast<bool*>(isTimeout) = true;
+ },
+ &isTimeout, 5000, nsITimer::TYPE_ONE_SHOT, __func__);
+
+ SpinEventLoopUntil("Spinner:WaitForNotification"_ns, [&]() -> bool {
+ if (isTimeout) {
+ return true;
+ }
+ return mTopicObserved;
+ });
+ return !isTimeout;
+}
+
+void StartUserInteraction(const nsCOMPtr<nsIObserverService>& aObserverSvc) {
+ aObserverSvc->NotifyObservers(nullptr, "user-interaction-active", nullptr);
+}
+
+TEST(AvailableMemoryWatcher, BasicTest)
+{
+ nsCOMPtr<nsIObserverService> observerSvc = services::GetObserverService();
+ RefPtr<Spinner> aSpinner = new Spinner(observerSvc, "memory-pressure");
+ aSpinner->StartListening();
+
+ // Start polling for low memory.
+ StartUserInteraction(observerSvc);
+
+ RefPtr<AvailableMemoryChecker> checker = new AvailableMemoryChecker();
+ checker->Init();
+
+ aSpinner->WaitForNotification();
+
+ // The checker should have dispatched a low memory event before reaching 100%
+ // memory pressure threshold, so the topic should be observed by the spinner.
+ EXPECT_TRUE(aSpinner->TopicObserved());
+ checker->Shutdown();
+}
+
+TEST(AvailableMemoryWatcher, MemoryLowToHigh)
+{
+ // Setting this pref to 100 ensures we start in a low memory scenario.
+ Preferences::SetUint("browser.low_commit_space_threshold_percent", 100);
+
+ nsCOMPtr<nsIObserverService> observerSvc = services::GetObserverService();
+ RefPtr<Spinner> lowMemorySpinner =
+ new Spinner(observerSvc, "memory-pressure");
+ lowMemorySpinner->StartListening();
+
+ StartUserInteraction(observerSvc);
+
+ // Start polling for low memory. We should start with low memory when we start
+ // the checker.
+ RefPtr<AvailableMemoryChecker> checker = new AvailableMemoryChecker();
+ checker->Init();
+
+ lowMemorySpinner->WaitForNotification();
+
+ EXPECT_TRUE(lowMemorySpinner->TopicObserved());
+
+ RefPtr<Spinner> highMemorySpinner =
+ new Spinner(observerSvc, "memory-pressure-stop");
+ highMemorySpinner->StartListening();
+
+ // Now that we are definitely low on memory, let's reset the pref to 0 to
+ // exit low memory.
+ Preferences::SetUint("browser.low_commit_space_threshold_percent", 0);
+
+ highMemorySpinner->WaitForNotification();
+
+ EXPECT_TRUE(highMemorySpinner->TopicObserved());
+
+ checker->Shutdown();
+}
+} // namespace