Bug 876029 - Make Gonk memory pressure by-pass the event queue. r=jlebar
☠☠ backed out by 17a47dcef75d ☠ ☠
authorNicolas B. Pierron <nicolas.b.pierron@mozilla.com>
Sun, 07 Jul 2013 15:59:50 -0700
changeset 137586 1c6223f7c74f2b161b430815386e77fe90621565
parent 137585 040df3280d8c514bffc13c894ee86c74e0fb4bc5
child 137587 5acfb878895f676fa519b33bd2f4af9741352d5d
push id24927
push userryanvm@gmail.com
push dateMon, 08 Jul 2013 01:19:31 +0000
treeherdermozilla-central@17a47dcef75d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlebar
bugs876029
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 876029 - Make Gonk memory pressure by-pass the event queue. r=jlebar
widget/gonk/GonkMemoryPressureMonitoring.cpp
xpcom/base/AvailableMemoryTracker.cpp
xpcom/threads/moz.build
xpcom/threads/nsMemoryPressure.cpp
xpcom/threads/nsMemoryPressure.h
xpcom/threads/nsThread.cpp
xpcom/threads/nsThread.h
--- a/widget/gonk/GonkMemoryPressureMonitoring.cpp
+++ b/widget/gonk/GonkMemoryPressureMonitoring.cpp
@@ -6,60 +6,30 @@
 
 #include "GonkMemoryPressureMonitoring.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
+#include "nsMemoryPressure.h"
 #include "nsThreadUtils.h"
 #include <errno.h>
 #include <fcntl.h>
 #include <poll.h>
 #include <android/log.h>
 
 #define LOG(args...)  \
   __android_log_print(ANDROID_LOG_INFO, "GonkMemoryPressure" , ## args)
 
 using namespace mozilla;
 
 namespace {
 
-class MemoryPressureRunnable : public nsRunnable
-{
-  const char *mTopic;
-  const PRUnichar *mData;
-public:
-  MemoryPressureRunnable(const char *aTopic, const PRUnichar *aData) :
-    mTopic(aTopic), mData(aData)
-  {
-  }
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    LOG("Dispatching low-memory memory-pressure event");
-
-    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
-    if (os) {
-      os->NotifyObservers(nullptr, mTopic, mData);
-    }
-    return NS_OK;
-  }
-};
-
-static void
-Dispatch(const char *aTopic, const PRUnichar *aData)
-{
-  nsRefPtr<MemoryPressureRunnable> memoryPressureRunnable =
-    new MemoryPressureRunnable(aTopic, aData);
-  NS_DispatchToMainThread(memoryPressureRunnable);
-}
-
 /**
  * MemoryPressureWatcher watches sysfs from its own thread to notice when the
  * system is under memory pressure.  When we observe memory pressure, we use
  * MemoryPressureRunnable to notify observers that they should release memory.
  *
  * When the system is under memory pressure, we don't want to constantly fire
  * memory-pressure events.  So instead, we try to detect when sysfs indicates
  * that we're no longer under memory pressure, and only then start firing events
@@ -181,18 +151,18 @@ public:
 
       // POLLPRI on lowMemFd indicates that we're in a low-memory situation.  We
       // could read lowMemFd to double-check, but we've observed that the read
       // sometimes completes after the memory-pressure event is over, so let's
       // just believe the result of poll().
 
       // We use low-memory-no-forward because each process has its own watcher
       // and thus there is no need for the main process to forward this event.
-      Dispatch("memory-pressure",
-               NS_LITERAL_STRING("low-memory-no-forward").get());
+      rv = NS_DispatchMemoryPressure(MemPressure_New);
+      NS_ENSURE_SUCCESS(rv, rv);
 
       // Manually check lowMemFd until we observe that memory pressure is over.
       // We won't fire any more low-memory events until we observe that
       // we're no longer under pressure. Instead, we fire low-memory-ongoing
       // events, which cause processes to keep flushing caches but will not
       // trigger expensive GCs and other attempts to save memory that are
       // likely futile at this point.
       bool memoryPressure;
@@ -214,18 +184,18 @@ public:
           mMonitor.Wait(PR_MillisecondsToInterval(mPollMS));
         }
 
         LOG("Checking to see if memory pressure is over.");
         rv = CheckForMemoryPressure(lowMemFd, &memoryPressure);
         NS_ENSURE_SUCCESS(rv, rv);
 
         if (memoryPressure) {
-          Dispatch("memory-pressure",
-                   NS_LITERAL_STRING("low-memory-ongoing-no-forward").get());
+          rv = NS_DispatchMemoryPressure(MemPressure_Ongoing);
+          NS_ENSURE_SUCCESS(rv, rv);
           continue;
         }
       } while (false);
 
       LOG("Memory pressure is over.");
     }
 
     return NS_OK;
--- a/xpcom/base/AvailableMemoryTracker.cpp
+++ b/xpcom/base/AvailableMemoryTracker.cpp
@@ -10,16 +10,17 @@
 #include "pratom.h"
 #include "prenv.h"
 
 #include "nsIMemoryReporter.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsIRunnable.h"
 #include "nsISupports.h"
+#include "nsMemoryPressure.h"
 #include "nsPrintfCString.h"
 #include "nsThread.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 
 #if defined(XP_WIN)
 #   include "nsWindowsDllInterceptor.h"
@@ -182,17 +183,17 @@ bool MaybeScheduleMemoryPressureEvent()
   // not worry about it -- the races only happen when we're already
   // experiencing memory pressure and firing notifications, so the worst
   // thing that can happen is that we fire two notifications when we
   // should have fired only one.
   sHasScheduledOneLowMemoryNotification = true;
   sLastLowMemoryNotificationTime = PR_IntervalNow();
 
   LOG("Scheduling memory pressure notification.");
-  ScheduleMemoryPressureEvent();
+  NS_DispatchEventualMemoryPressure(MemPressure_New);
   return true;
 }
 
 void CheckMemAvailable()
 {
   if (!sHooksActive) {
     return;
   }
@@ -207,17 +208,17 @@ void CheckMemAvailable()
   {
     // sLowVirtualMemoryThreshold is in MB, but ullAvailVirtual is in bytes.
     if (stat.ullAvailVirtual < sLowVirtualMemoryThreshold * 1024 * 1024) {
       // If we're running low on virtual memory, unconditionally schedule the
       // notification.  We'll probably crash if we run out of virtual memory,
       // so don't worry about firing this notification too often.
       LOG("Detected low virtual memory.");
       PR_ATOMIC_INCREMENT(&sNumLowVirtualMemEvents);
-      ScheduleMemoryPressureEvent();
+      NS_DispatchEventualMemoryPressure(MemPressure_New);
     }
     else if (stat.ullAvailPageFile < sLowCommitSpaceThreshold * 1024 * 1024) {
       LOG("Detected low available page file space.");
       if (MaybeScheduleMemoryPressureEvent()) {
         PR_ATOMIC_INCREMENT(&sNumLowCommitSpaceEvents);
       }
     }
     else if (stat.ullAvailPhys < sLowPhysicalMemoryThreshold * 1024 * 1024) {
--- a/xpcom/threads/moz.build
+++ b/xpcom/threads/moz.build
@@ -19,32 +19,34 @@ XPIDL_SOURCES += [
 ]
 
 XPIDL_MODULE = 'xpcom_threads'
 
 MODULE = 'xpcom'
 
 EXPORTS += [
     'nsEventQueue.h',
+    'nsMemoryPressure.h',
     'nsProcess.h',
     'nsThread.h',
 ]
 
 EXPORTS.mozilla += [
     'HangMonitor.h',
     'LazyIdleThread.h',
     'SyncRunnable.h',
 ]
 
 CPP_SOURCES += [
     'HangMonitor.cpp',
     'LazyIdleThread.cpp',
     'TimerThread.cpp',
     'nsEnvironment.cpp',
     'nsEventQueue.cpp',
+    'nsMemoryPressure.cpp',
     'nsProcessCommon.cpp',
     'nsThread.cpp',
     'nsThreadManager.cpp',
     'nsThreadPool.cpp',
     'nsTimerImpl.cpp',
 ]
 
 LIBRARY_NAME = 'xpcomthreads_s'
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/nsMemoryPressure.cpp
@@ -0,0 +1,48 @@
+#include "nsMemoryPressure.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+static Atomic<int32_t, Relaxed> sMemoryPressurePending;
+MOZ_STATIC_ASSERT(MemPressure_None == 0,
+                  "Bad static initialization with the default constructor.");
+
+MemoryPressureState
+NS_GetPendingMemoryPressure()
+{
+  int32_t value = sMemoryPressurePending.exchange(MemPressure_None);
+  return MemoryPressureState(value);
+}
+
+void
+NS_DispatchEventualMemoryPressure(MemoryPressureState state)
+{
+  /*
+   * A new memory pressure event erases an ongoing memory pressure, but an
+   * existing "new" memory pressure event takes precedence over a new "ongoing"
+   * memory pressure event.
+   */
+  switch (state) {
+    case MemPressure_None:
+      sMemoryPressurePending = MemPressure_None;
+      break;
+    case MemPressure_New:
+      sMemoryPressurePending = MemPressure_New;
+      break;
+    case MemPressure_Ongoing:
+      sMemoryPressurePending.compareExchange(MemPressure_None, MemPressure_Ongoing);
+      break;
+  }
+}
+
+nsresult
+NS_DispatchMemoryPressure(MemoryPressureState state)
+{
+  NS_DispatchEventualMemoryPressure(state);
+  nsCOMPtr<nsIRunnable> event = new nsRunnable;
+  NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
+  return NS_DispatchToMainThread(event);
+}
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/nsMemoryPressure.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef nsMemoryPressure_h__
+#define nsMemoryPressure_h__
+
+#include "nscore.h"
+
+enum MemoryPressureState {
+  /*
+   * No memory pressure.
+   */
+  MemPressure_None = 0,
+
+  /*
+   * New memory pressure deteced.
+   *
+   * On a new memory pressure, we stop everything to start cleaning
+   * aggresively the memory used, in order to free as much memory as
+   * possible.
+   */
+  MemPressure_New,
+
+  /*
+   * Repeated memory pressure.
+   *
+   * A repeated memory pressure implies to clean softly recent allocations.
+   * It is supposed to happen after a new memory pressure which already
+   * cleaned aggressivley.  So there is no need to damage the reactivity of
+   * Gecko by stopping the world again.
+   *
+   * In case of conflict with an new memory pressue, the new memory pressure
+   * takes precedence over an ongoing memory pressure.  The reason being
+   * that if no events are processed between 2 notifications (new followed
+   * by ongoing, or ongoing followed by a new) we want to be as aggresive as
+   * possible on the clean-up of the memory.  After all, we are trying to
+   * keep Gecko alive as long as possible.
+   */
+  MemPressure_Ongoing
+};
+
+/**
+ * Return and erase the latest state of the memory pressure event set by any of
+ * the corresponding dispatch function.
+ */
+MemoryPressureState
+NS_GetPendingMemoryPressure();
+
+/**
+ * This function causes the main thread to fire a memory pressure event
+ * before processing the next event, but if there are no events pending in
+ * the main thread's event queue, the memory pressure event would not be
+ * dispatched until one is enqueued. It is infallible and does not allocate
+ * any memory.
+ *
+ * You may call this function from any thread.
+ */
+void
+NS_DispatchEventualMemoryPressure(MemoryPressureState state);
+
+/**
+ * This function causes the main thread to fire a memory pressure event
+ * before processing the next event. We wake up the main thread by adding a
+ * dummy event to its event loop, so, unlike with
+ * NS_DispatchEventualMemoryPressure, this memory-pressure event is always
+ * fired relatively quickly, even if the event loop is otherwise empty.
+ *
+ * You may call this function from any thread.
+ */
+nsresult
+NS_DispatchMemoryPressure(MemoryPressureState state);
+
+#endif // nsMemoryPressure_h__
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "mozilla/ReentrantMonitor.h"
+#include "nsMemoryPressure.h"
 #include "nsThread.h"
 #include "nsThreadManager.h"
 #include "nsIClassInfoImpl.h"
 #include "nsIProgrammingLanguage.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "prlog.h"
 #include "nsIObserverService.h"
@@ -52,33 +53,16 @@ GetThreadLog()
 }
 #endif
 #define LOG(args) PR_LOG(GetThreadLog(), PR_LOG_DEBUG, args)
 
 NS_DECL_CI_INTERFACE_GETTER(nsThread)
 
 nsIThreadObserver* nsThread::sMainThreadObserver = nullptr;
 
-namespace mozilla {
-
-// Fun fact: Android's GCC won't convert bool* to int32_t*, so we can't
-// PR_ATOMIC_SET a bool.
-static int32_t sMemoryPressurePending = 0;
-
-/*
- * It's important that this function not acquire any locks, nor do anything
- * which might cause malloc to run.
- */
-void ScheduleMemoryPressureEvent()
-{
-  PR_ATOMIC_SET(&sMemoryPressurePending, 1);
-}
-
-} // namespace mozilla
-
 //-----------------------------------------------------------------------------
 // Because we do not have our own nsIFactory, we have to implement nsIClassInfo
 // somewhat manually.
 
 class nsThreadClassInfo : public nsIClassInfo {
 public:
   NS_DECL_ISUPPORTS_INHERITED  // no mRefCnt
   NS_DECL_NSICLASSINFO
@@ -575,24 +559,30 @@ nsThread::ProcessNextEvent(bool mayWait,
   NS_ENSURE_STATE(PR_GetCurrentThread() == mThread);
 
   if (MAIN_THREAD == mIsMainThread && mayWait && !ShuttingDown())
     HangMonitor::Suspend();
 
   // Fire a memory pressure notification, if we're the main thread and one is
   // pending.
   if (MAIN_THREAD == mIsMainThread && !ShuttingDown()) {
-    bool mpPending = PR_ATOMIC_SET(&sMemoryPressurePending, 0);
-    if (mpPending) {
+    MemoryPressureState mpPending = NS_GetPendingMemoryPressure();
+    if (mpPending != MemPressure_None) {
       nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+
+      // Use no-forward to prevent the notifications from being transferred to
+      // the children of this process.
+      NS_NAMED_LITERAL_STRING(lowMem, "low-memory-no-forward");
+      NS_NAMED_LITERAL_STRING(lowMemOngoing, "low-memory-ongoing-no-forward");
+
       if (os) {
         os->NotifyObservers(nullptr, "memory-pressure",
-                            NS_LITERAL_STRING("low-memory").get());
-      }
-      else {
+                            mpPending == MemPressure_New ? lowMem.get() :
+                                                           lowMemOngoing.get());
+      } else {
         NS_WARNING("Can't get observer service!");
       }
     }
   }
 
   bool notifyMainThreadObserver =
     (MAIN_THREAD == mIsMainThread) && sMainThreadObserver;
   if (notifyMainThreadObserver) 
--- a/xpcom/threads/nsThread.h
+++ b/xpcom/threads/nsThread.h
@@ -123,21 +123,9 @@ public:
 private:
   NS_DECL_NSIRUNNABLE
 
   nsCOMPtr<nsIThread> mOrigin;
   nsCOMPtr<nsIRunnable> mSyncTask;
   nsresult mResult;
 };
 
-namespace mozilla {
-
-/**
- * This function causes the main thread to fire a memory pressure event at its
- * next available opportunity.
- *
- * You may call this function from any thread.
- */
-void ScheduleMemoryPressureEvent();
-
-} // namespace mozilla
-
 #endif  // nsThread_h__