/* -*- 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 #include "gtest/gtest.h" #include "mozilla/Atomics.h" #include "mozilla/SpinEventLoopUntil.h" #include "nsMemoryPressure.h" #include "nsIObserver.h" #include "nsIObserverService.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" using namespace mozilla; namespace { enum class MemoryPressureEventType : int { LowMemory, LowMemoryOngoing, Stop, }; class MemoryPressureObserver final : public nsIObserver { nsCOMPtr mObserverSvc; Vector mEvents; ~MemoryPressureObserver() { EXPECT_TRUE( NS_SUCCEEDED(mObserverSvc->RemoveObserver(this, kTopicMemoryPressure))); EXPECT_TRUE(NS_SUCCEEDED( mObserverSvc->RemoveObserver(this, kTopicMemoryPressureStop))); } public: NS_DECL_ISUPPORTS MemoryPressureObserver() : mObserverSvc(do_GetService(NS_OBSERVERSERVICE_CONTRACTID)) { EXPECT_TRUE(NS_SUCCEEDED(mObserverSvc->AddObserver( this, kTopicMemoryPressure, /* ownsWeak */ false))); EXPECT_TRUE(NS_SUCCEEDED(mObserverSvc->AddObserver( this, kTopicMemoryPressureStop, /* ownsWeak */ false))); } NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) override { Maybe event; if (strcmp(aTopic, kTopicMemoryPressure) == 0) { if (nsDependentString(aData) == kSubTopicLowMemoryNew) { event = Some(MemoryPressureEventType::LowMemory); } else if (nsDependentString(aData) == kSubTopicLowMemoryOngoing) { event = Some(MemoryPressureEventType::LowMemoryOngoing); } else { fprintf(stderr, "Unexpected subtopic: %S\n", reinterpret_cast(aData)); EXPECT_TRUE(false); } } else if (strcmp(aTopic, kTopicMemoryPressureStop) == 0) { event = Some(MemoryPressureEventType::Stop); } else { fprintf(stderr, "Unexpected topic: %s\n", aTopic); EXPECT_TRUE(false); } if (event) { Unused << mEvents.emplaceBack(event.value()); } return NS_OK; } uint32_t GetCount() const { return mEvents.length(); } void Reset() { mEvents.clear(); } MemoryPressureEventType Top() const { return mEvents[0]; } bool ValidateTransitions() const { if (mEvents.length() == 0) { return true; } for (size_t i = 1; i < mEvents.length(); ++i) { MemoryPressureEventType eventFrom = mEvents[i - 1]; MemoryPressureEventType eventTo = mEvents[i]; if ((eventFrom == MemoryPressureEventType::LowMemory && eventTo == MemoryPressureEventType::LowMemoryOngoing) || (eventFrom == MemoryPressureEventType::LowMemoryOngoing && eventTo == MemoryPressureEventType::LowMemoryOngoing) || (eventFrom == MemoryPressureEventType::Stop && eventTo == MemoryPressureEventType::LowMemory) || (eventFrom == MemoryPressureEventType::LowMemoryOngoing && eventTo == MemoryPressureEventType::Stop) || (eventFrom == MemoryPressureEventType::LowMemory && eventTo == MemoryPressureEventType::Stop)) { // Only these transitions are valid. continue; } fprintf(stderr, "Invalid transition: %d -> %d\n", static_cast(eventFrom), static_cast(eventTo)); return false; } return true; } }; NS_IMPL_ISUPPORTS(MemoryPressureObserver, nsIObserver) template void PressureSender(Atomic& aContinue) { while (aContinue) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); NS_NotifyOfEventualMemoryPressure(State); } } template void PressureSenderQuick(Atomic& aContinue) { while (aContinue) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); NS_NotifyOfMemoryPressure(State); } } } // anonymous namespace TEST(MemoryPressure, Singlethread) { RefPtr observer(new MemoryPressureObserver); NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); SpinEventLoopUntil("xpcom:TEST(MemoryPressure, Singlethread) 1"_ns, [&observer]() { return observer->GetCount() == 1; }); EXPECT_EQ(observer->Top(), MemoryPressureEventType::LowMemory); observer->Reset(); NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); SpinEventLoopUntil("xpcom:TEST(MemoryPressure, Singlethread) 2"_ns, [&observer]() { return observer->GetCount() == 1; }); EXPECT_EQ(observer->Top(), MemoryPressureEventType::LowMemoryOngoing); observer->Reset(); NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); SpinEventLoopUntil("xpcom:TEST(MemoryPressure, Singlethread) 3"_ns, [&observer]() { return observer->GetCount() == 1; }); EXPECT_EQ(observer->Top(), MemoryPressureEventType::LowMemoryOngoing); observer->Reset(); NS_NotifyOfEventualMemoryPressure(MemoryPressureState::NoPressure); SpinEventLoopUntil("xpcom:TEST(MemoryPressure, Singlethread) 4"_ns, [&observer]() { return observer->GetCount() == 1; }); EXPECT_EQ(observer->Top(), MemoryPressureEventType::Stop); } TEST(MemoryPressure, Multithread) { // Start |kNumThreads| threads each for the following thread type: // - LowMemory via NS_NotifyOfEventualMemoryPressure // - LowMemory via NS_NotifyOfMemoryPressure // - LowMemoryOngoing via NS_NotifyOfEventualMemoryPressure // - LowMemoryOngoing via NS_NotifyOfMemoryPressure // and keep them running until |kNumEventsToValidate| memory-pressure events // are received. constexpr int kNumThreads = 5; constexpr int kNumEventsToValidate = 200; Atomic shouldContinue(true); Vector threads; for (int i = 0; i < kNumThreads; ++i) { Unused << threads.emplaceBack( PressureSender, std::ref(shouldContinue)); Unused << threads.emplaceBack( PressureSender, std::ref(shouldContinue)); Unused << threads.emplaceBack( PressureSenderQuick, std::ref(shouldContinue)); Unused << threads.emplaceBack( PressureSenderQuick, std::ref(shouldContinue)); } RefPtr observer(new MemoryPressureObserver); // We cannot sleep here because the main thread needs to keep running. SpinEventLoopUntil( "xpcom:TEST(MemoryPressure, Multithread)"_ns, [&observer]() { return observer->GetCount() >= kNumEventsToValidate; }); shouldContinue = false; for (auto& thread : threads) { thread.join(); } EXPECT_TRUE(observer->ValidateTransitions()); }