Bug 977940, part 1 - Don't run the ghost window detector more than every 45 seconds. r=smaug
authorAndrew McCreight <continuation@gmail.com>
Sat, 08 Mar 2014 05:38:52 -0800
changeset 172644 f1106ddac0b5fd535e964aafa5d526a430bc8573
parent 172643 399e4c0b9a6ff902525301ee7853e4c495cc92a0
child 172645 32504c2466e34da205889d55b6df184af394ed31
push id26371
push userphilringnalda@gmail.com
push dateSun, 09 Mar 2014 01:40:12 +0000
treeherdermozilla-central@470276afb976 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs977940
milestone30.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 977940, part 1 - Don't run the ghost window detector more than every 45 seconds. r=smaug
dom/base/nsWindowMemoryReporter.cpp
dom/base/nsWindowMemoryReporter.h
--- a/dom/base/nsWindowMemoryReporter.cpp
+++ b/dom/base/nsWindowMemoryReporter.cpp
@@ -19,21 +19,32 @@
 #include "XPCJSMemoryReporter.h"
 #include "js/MemoryMetrics.h"
 #include "nsServiceManagerUtils.h"
 
 using namespace mozilla;
 
 StaticRefPtr<nsWindowMemoryReporter> sWindowReporter;
 
+/**
+ * Don't trigger a ghost window check when a DOM window is detached if we've
+ * run it this recently.
+ */
+const int32_t kTimeBetweenChecks = 45; /* seconds */
+
 nsWindowMemoryReporter::nsWindowMemoryReporter()
-  : mCheckForGhostWindowsCallbackPending(false)
+  : mLastCheckForGhostWindows(TimeStamp::NowLoRes())
 {
 }
 
+nsWindowMemoryReporter::~nsWindowMemoryReporter()
+{
+  KillCheckTimer();
+}
+
 NS_IMPL_ISUPPORTS3(nsWindowMemoryReporter, nsIMemoryReporter, nsIObserver,
                    nsSupportsWeakReference)
 
 static nsresult
 AddNonJSSizeOfWindowAndItsDescendents(nsGlobalWindow* aWindow,
                                       nsTabSizes* aSizes)
 {
   // Measure the window.
@@ -621,22 +632,48 @@ nsWindowMemoryReporter::ObserveDOMWindow
   nsWeakPtr weakWindow = do_GetWeakReference(aWindow);
   if (!weakWindow) {
     NS_WARNING("Couldn't take weak reference to a window?");
     return;
   }
 
   mDetachedWindows.Put(weakWindow, TimeStamp());
 
-  if (!mCheckForGhostWindowsCallbackPending) {
-    nsCOMPtr<nsIRunnable> runnable =
-      NS_NewRunnableMethod(this,
-                           &nsWindowMemoryReporter::CheckForGhostWindowsCallback);
-    NS_DispatchToCurrentThread(runnable);
-    mCheckForGhostWindowsCallbackPending = true;
+  AsyncCheckForGhostWindows();
+}
+
+// static
+void
+nsWindowMemoryReporter::CheckTimerFired(nsITimer* aTimer, void* aClosure)
+{
+  if (sWindowReporter) {
+    sWindowReporter->CheckForGhostWindows();
+  }
+}
+
+void
+nsWindowMemoryReporter::AsyncCheckForGhostWindows()
+{
+  if (mCheckTimer) {
+    return;
+  }
+
+  // If more than kTimeBetweenChecks seconds have elapsed since the last check,
+  // timerDelay is 0.  Otherwise, it is kTimeBetweenChecks, reduced by the time
+  // since the last check.  Reducing the delay by the time since the last check
+  // prevents the timer from being completely starved if it is repeatedly killed
+  // and restarted.
+  int32_t timeSinceLastCheck = (TimeStamp::NowLoRes() - mLastCheckForGhostWindows).ToSeconds();
+  int32_t timerDelay = (kTimeBetweenChecks - std::min(timeSinceLastCheck, kTimeBetweenChecks)) * PR_MSEC_PER_SEC;
+
+  CallCreateInstance<nsITimer>("@mozilla.org/timer;1", getter_AddRefs(mCheckTimer));
+
+  if (mCheckTimer) {
+    mCheckTimer->InitWithFuncCallback(CheckTimerFired, nullptr,
+                                      timerDelay, nsITimer::TYPE_ONE_SHOT);
   }
 }
 
 static PLDHashOperator
 BackdateTimeStampsEnumerator(nsISupports *aKey, TimeStamp &aTimeStamp,
                              void* aClosure)
 {
   TimeStamp *minTimeStamp = static_cast<TimeStamp*>(aClosure);
@@ -659,23 +696,16 @@ nsWindowMemoryReporter::ObserveAfterMini
 
   TimeStamp minTimeStamp = TimeStamp::Now() -
                            TimeDuration::FromSeconds(GetGhostTimeout());
 
   mDetachedWindows.Enumerate(BackdateTimeStampsEnumerator,
                              &minTimeStamp);
 }
 
-void
-nsWindowMemoryReporter::CheckForGhostWindowsCallback()
-{
-  mCheckForGhostWindowsCallbackPending = false;
-  CheckForGhostWindows();
-}
-
 struct CheckForGhostWindowsEnumeratorData
 {
   nsTHashtable<nsCStringHashKey> *nonDetachedDomains;
   nsTHashtable<nsUint64HashKey> *ghostWindowIDs;
   nsIEffectiveTLDService *tldService;
   uint32_t ghostTimeout;
   TimeStamp now;
 };
@@ -803,44 +833,56 @@ nsWindowMemoryReporter::CheckForGhostWin
 
   nsGlobalWindow::WindowByIdTable *windowsById =
     nsGlobalWindow::GetWindowsTable();
   if (!windowsById) {
     NS_WARNING("GetWindowsTable returned null");
     return;
   }
 
+  mLastCheckForGhostWindows = TimeStamp::NowLoRes();
+  KillCheckTimer();
+
   nsTHashtable<nsCStringHashKey> nonDetachedWindowDomains;
 
   // Populate nonDetachedWindowDomains.
   GetNonDetachedWindowDomainsEnumeratorData nonDetachedEnumData =
     { &nonDetachedWindowDomains, tldService };
   windowsById->EnumerateRead(GetNonDetachedWindowDomainsEnumerator,
                              &nonDetachedEnumData);
 
   // Update mDetachedWindows and write the ghost window IDs into aOutGhostIDs,
   // if it's not null.
   CheckForGhostWindowsEnumeratorData ghostEnumData =
     { &nonDetachedWindowDomains, aOutGhostIDs, tldService,
-      GetGhostTimeout(), TimeStamp::Now() };
+      GetGhostTimeout(), mLastCheckForGhostWindows };
   mDetachedWindows.Enumerate(CheckForGhostWindowsEnumerator,
                              &ghostEnumData);
 }
 
 NS_IMPL_ISUPPORTS1(nsWindowMemoryReporter::GhostWindowsReporter,
                    nsIMemoryReporter)
 
 /* static */ int64_t
 nsWindowMemoryReporter::GhostWindowsReporter::DistinguishedAmount()
 {
   nsTHashtable<nsUint64HashKey> ghostWindows;
   sWindowReporter->CheckForGhostWindows(&ghostWindows);
   return ghostWindows.Count();
 }
 
+void
+nsWindowMemoryReporter::KillCheckTimer()
+{
+  if (mCheckTimer) {
+    mCheckTimer->Cancel();
+    mCheckTimer = nullptr;
+  }
+}
+
 #ifdef DEBUG
 static PLDHashOperator
 UnlinkGhostWindowsEnumerator(nsUint64HashKey* aIDHashKey, void *)
 {
   nsGlobalWindow::WindowByIdTable* windowsById =
     nsGlobalWindow::GetWindowsTable();
   if (!windowsById) {
     return PL_DHASH_NEXT;
--- a/dom/base/nsWindowMemoryReporter.h
+++ b/dom/base/nsWindowMemoryReporter.h
@@ -4,16 +4,17 @@
  * 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 nsWindowMemoryReporter_h__
 #define nsWindowMemoryReporter_h__
 
 #include "nsIMemoryReporter.h"
 #include "nsIObserver.h"
+#include "nsITimer.h"
 #include "nsDataHashtable.h"
 #include "nsWeakReference.h"
 #include "nsAutoPtr.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/TimeStamp.h"
@@ -134,16 +135,18 @@ class nsWindowMemoryReporter MOZ_FINAL :
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIMEMORYREPORTER
   NS_DECL_NSIOBSERVER
 
   static void Init();
 
+  ~nsWindowMemoryReporter();
+
 #ifdef DEBUG
   /**
    * Unlink all known ghost windows, to enable investigating what caused them
    * to become ghost windows in the first place.
    */
   static void UnlinkGhostWindows();
 #endif
 
@@ -183,49 +186,58 @@ private:
    * (1) and (2) before we deem that it satisfies criterion (3).
    */
   uint32_t GetGhostTimeout();
 
   void ObserveDOMWindowDetached(nsISupports* aWindow);
   void ObserveAfterMinimizeMemoryUsage();
 
   /**
-   * When we observe a DOM window being detached, we enqueue an asynchronous
-   * event which calls this method.  This method then calls
-   * CheckForGhostWindows.
-   */
-  void CheckForGhostWindowsCallback();
-
-  /**
    * Iterate over all weak window pointers in mDetachedWindows and update our
    * accounting of which windows meet ghost criterion (2).
    *
    * This method also cleans up mDetachedWindows, removing entries for windows
    * which have been destroyed or are no longer detached.
    *
    * If aOutGhostIDs is non-null, we populate it with the Window IDs of the
    * ghost windows.
    *
    * This is called asynchronously after we observe a DOM window being detached
    * from its docshell, and also right before we generate a memory report.
    */
   void CheckForGhostWindows(nsTHashtable<nsUint64HashKey> *aOutGhostIDs = nullptr);
 
   /**
+   * Eventually do a check for ghost windows, if we haven't done one recently
+   * and we aren't already planning to do one soon.
+   */
+  void AsyncCheckForGhostWindows();
+
+  /**
+   * Kill the check timer, if it exists.
+   */
+  void KillCheckTimer();
+
+  static void CheckTimerFired(nsITimer* aTimer, void* aClosure);
+
+  /**
    * Maps a weak reference to a detached window (nsIWeakReference) to the time
    * when we observed that the window met ghost criterion (2) above.
    *
    * If the window has not yet met criterion (2) it maps to the null timestamp.
    *
    * (Although windows are not added to this table until they're detached, it's
    * possible for a detached window to become non-detached, and we won't
    * remove it from the table until CheckForGhostWindows runs.)
    */
   nsDataHashtable<nsISupportsHashKey, mozilla::TimeStamp> mDetachedWindows;
 
   /**
-   * True if we have an asynchronous call to CheckForGhostWindows pending.
+   * Track the last time we ran CheckForGhostWindows(), to avoid running it
+   * too often after a DOM window is detached.
    */
-  bool mCheckForGhostWindowsCallbackPending;
+  mozilla::TimeStamp mLastCheckForGhostWindows;
+
+  nsCOMPtr<nsITimer> mCheckTimer;
 };
 
 #endif // nsWindowMemoryReporter_h__