diff options
Diffstat (limited to 'xpcom/base/AvailableMemoryWatcher.cpp')
-rw-r--r-- | xpcom/base/AvailableMemoryWatcher.cpp | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/xpcom/base/AvailableMemoryWatcher.cpp b/xpcom/base/AvailableMemoryWatcher.cpp new file mode 100644 index 0000000000..a2de368b01 --- /dev/null +++ b/xpcom/base/AvailableMemoryWatcher.cpp @@ -0,0 +1,186 @@ +/* -*- 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 "AvailableMemoryWatcher.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Telemetry.h" +#include "nsExceptionHandler.h" +#include "nsMemoryPressure.h" +#include "nsXULAppAPI.h" + +namespace mozilla { + +// Use this class as the initial value of +// nsAvailableMemoryWatcherBase::mCallback until RegisterCallback() is called +// so that nsAvailableMemoryWatcherBase does not have to check if its callback +// object is valid or not. +class NullTabUnloader final : public nsITabUnloader { + ~NullTabUnloader() = default; + + public: + NullTabUnloader() = default; + + NS_DECL_ISUPPORTS + NS_DECL_NSITABUNLOADER +}; + +NS_IMPL_ISUPPORTS(NullTabUnloader, nsITabUnloader) + +NS_IMETHODIMP NullTabUnloader::UnloadTabAsync() { + return NS_ERROR_NOT_IMPLEMENTED; +} + +StaticRefPtr<nsAvailableMemoryWatcherBase> + nsAvailableMemoryWatcherBase::sSingleton; + +/*static*/ +already_AddRefed<nsAvailableMemoryWatcherBase> +nsAvailableMemoryWatcherBase::GetSingleton() { + if (!sSingleton) { + sSingleton = CreateAvailableMemoryWatcher(); + ClearOnShutdown(&sSingleton); + } + + return do_AddRef(sSingleton); +} + +NS_IMPL_ISUPPORTS(nsAvailableMemoryWatcherBase, nsIAvailableMemoryWatcherBase); + +nsAvailableMemoryWatcherBase::nsAvailableMemoryWatcherBase() + : mMutex("nsAvailableMemoryWatcher mutex"), + mNumOfTabUnloading(0), + mNumOfMemoryPressure(0), + mTabUnloader(new NullTabUnloader), + mInteracting(false) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Watching memory only in the main process."); +} + +const char* const nsAvailableMemoryWatcherBase::kObserverTopics[] = { + // Use this shutdown phase to make sure the instance is destroyed in GTest + "xpcom-shutdown", + "user-interaction-active", + "user-interaction-inactive", +}; + +nsresult nsAvailableMemoryWatcherBase::Init() { + MOZ_ASSERT(NS_IsMainThread(), + "nsAvailableMemoryWatcherBase needs to be initialized " + "in the main thread."); + + if (mObserverSvc) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + mObserverSvc = services::GetObserverService(); + MOZ_ASSERT(mObserverSvc); + + for (auto topic : kObserverTopics) { + nsresult rv = mObserverSvc->AddObserver(this, topic, + /* ownsWeak */ false); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +void nsAvailableMemoryWatcherBase::Shutdown() { + for (auto topic : kObserverTopics) { + mObserverSvc->RemoveObserver(this, topic); + } +} + +NS_IMETHODIMP +nsAvailableMemoryWatcherBase::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(NS_IsMainThread()); + + if (strcmp(aTopic, "xpcom-shutdown") == 0) { + Shutdown(); + } else if (strcmp(aTopic, "user-interaction-inactive") == 0) { + mInteracting = false; +#ifdef MOZ_CRASHREPORTER + CrashReporter::SetInactiveStateStart(); +#endif + } else if (strcmp(aTopic, "user-interaction-active") == 0) { + mInteracting = true; +#ifdef MOZ_CRASHREPORTER + CrashReporter::ClearInactiveStateStart(); +#endif + } + return NS_OK; +} + +nsresult nsAvailableMemoryWatcherBase::RegisterTabUnloader( + nsITabUnloader* aTabUnloader) { + mTabUnloader = aTabUnloader; + return NS_OK; +} + +// This method is called as a part of UnloadTabAsync(). Like Notify(), if we +// call this method synchronously without releasing the lock first we can lead +// to deadlock. +nsresult nsAvailableMemoryWatcherBase::OnUnloadAttemptCompleted( + nsresult aResult) { + MutexAutoLock lock(mMutex); + switch (aResult) { + // A tab was unloaded successfully. + case NS_OK: + ++mNumOfTabUnloading; + break; + + // There was no unloadable tab. + case NS_ERROR_NOT_AVAILABLE: + ++mNumOfMemoryPressure; + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); + break; + + // There was a pending task to unload a tab. + case NS_ERROR_ABORT: + break; + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected aResult"); + break; + } + return NS_OK; +} + +void nsAvailableMemoryWatcherBase::UpdateLowMemoryTimeStamp() { + if (mLowMemoryStart.IsNull()) { + mLowMemoryStart = TimeStamp::NowLoRes(); + } +} + +void nsAvailableMemoryWatcherBase::RecordTelemetryEventOnHighMemory( + const MutexAutoLock&) { + Telemetry::SetEventRecordingEnabled("memory_watcher"_ns, true); + Telemetry::RecordEvent( + Telemetry::EventID::Memory_watcher_OnHighMemory_Stats, + Some(nsPrintfCString( + "%u,%u,%f", mNumOfTabUnloading, mNumOfMemoryPressure, + (TimeStamp::NowLoRes() - mLowMemoryStart).ToSeconds())), + Nothing()); + mNumOfTabUnloading = mNumOfMemoryPressure = 0; + mLowMemoryStart = TimeStamp(); +} + +// Define the fallback method for a platform for which a platform-specific +// CreateAvailableMemoryWatcher() is not defined. +#if defined(ANDROID) || \ + !defined(XP_WIN) && !defined(XP_DARWIN) && !defined(XP_LINUX) +already_AddRefed<nsAvailableMemoryWatcherBase> CreateAvailableMemoryWatcher() { + RefPtr instance(new nsAvailableMemoryWatcherBase); + return do_AddRef(instance); +} +#endif + +} // namespace mozilla |