diff options
Diffstat (limited to '')
-rw-r--r-- | xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp | 227 |
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 |