summaryrefslogtreecommitdiffstats
path: root/xpcom/base/AvailableMemoryWatcher.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/base/AvailableMemoryWatcher.cpp')
-rw-r--r--xpcom/base/AvailableMemoryWatcher.cpp186
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