Bug 1179894, part 2 - Optimize ghost windows out of the CC graph. draft
authorAndrew McCreight <continuation@gmail.com>
Tue, 09 Jan 2018 11:44:52 -0800
changeset 718276 8ee459b0e33c447e3b558e94fc75b7579075315f
parent 718275 25f811bf5877e2fc96a6d39913601391552ea809
child 745422 62d0604186af1211508a0dba3b21a3386584b3d8
push id94856
push userbmo:continuation@gmail.com
push dateWed, 10 Jan 2018 00:12:35 +0000
bugs1179894
milestone59.0a1
Bug 1179894, part 2 - Optimize ghost windows out of the CC graph. Ghost windows cause the cycle collector to get very slow. This patch fixes that by marking ghost windows as uncollectable. This means we will leak them forever. Every 30 or so CCs, we skip that optimization, in the hope that sometimes that will let us free the window. Also, remove a redundant null check of |window| and move a comment. MozReview-Commit-ID: 6E8J4f5l7Q5
dom/base/nsCCUncollectableMarker.cpp
dom/base/nsGlobalWindowInner.cpp
dom/base/nsPIDOMWindow.h
dom/base/nsWindowMemoryReporter.cpp
--- a/dom/base/nsCCUncollectableMarker.cpp
+++ b/dom/base/nsCCUncollectableMarker.cpp
@@ -32,16 +32,17 @@
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ProcessGlobal.h"
 #include "mozilla/dom/TimeoutManager.h"
 #include "xpcpublic.h"
 #include "nsObserverService.h"
 #include "nsFocusManager.h"
+#include "nsWindowMemoryReporter.h"
 #include "nsIInterfaceRequestorUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 static bool sInited = 0;
 // The initial value of sGeneration should not be the same as the
 // value it is given at xpcom-shutdown, because this will make any GCs
@@ -414,16 +415,38 @@ nsCCUncollectableMarker::Observe(nsISupp
       if (hw) {
         nsCOMPtr<nsIDocShell> shell;
         hw->GetDocShell(getter_AddRefs(shell));
         MarkDocShell(shell, cleanupJS, prepareForCC);
       }
     }
   }
 
+  // Iterate over all ghost windows.
+  static uint64_t numGhostSkips = 0;
+  ++numGhostSkips;
+  if (numGhostSkips < 30) {
+    if (nsWindowMemoryReporter::Get()->GhostWindowsDistinguishedAmount() > 0) {
+      nsGlobalWindowInner::InnerWindowByIdTable *windowsById =
+        nsGlobalWindowInner::GetWindowsTable();
+      if (windowsById) {
+        for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) {
+          nsGlobalWindowInner* window = iter.UserData();
+          if (window->GetIsGhost()) {
+            MarkDocument(window->GetDoc(), cleanupJS, prepareForCC);
+          }
+        }
+      }
+    }
+  } else {
+    // Skip marking ghost windows as uncollectable in the hope that
+    // some of them might be freeable now.
+    numGhostSkips = 0;
+  }
+
 #ifdef MOZ_XUL
   nsXULPrototypeCache* xulCache = nsXULPrototypeCache::GetInstance();
   if (xulCache) {
     xulCache->MarkInCCGeneration(sGeneration);
   }
 #endif
 
   enum ForgetSkippableCleanupState
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -7941,11 +7941,23 @@ nsPIDOMWindowInner::nsPIDOMWindowInner(n
   mNumOfIndexedDBDatabases(0),
   mNumOfOpenWebSockets(0)
 {
   MOZ_ASSERT(aOuterWindow);
 }
 
 nsPIDOMWindowInner::~nsPIDOMWindowInner() {}
 
+void
+nsPIDOMWindowInner::SetIsGhost(bool aIsGhost)
+{
+  mIsGhost = aIsGhost;
+}
+
+bool
+nsPIDOMWindowInner:: GetIsGhost() const
+{
+  return mIsGhost;
+}
+
 #undef FORWARD_TO_OUTER
 #undef FORWARD_TO_OUTER_OR_THROW
 #undef FORWARD_TO_OUTER_VOID
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -613,16 +613,19 @@ public:
   virtual nsresult Close() = 0;
 
   virtual nsresult UpdateCommands(const nsAString& anAction, nsISelection* aSel, int16_t aReason) = 0;
 
   mozilla::dom::DocGroup* GetDocGroup() const;
   virtual nsISerialEventTarget*
   EventTargetFor(mozilla::TaskCategory aCategory) const = 0;
 
+  void SetIsGhost(bool aIsGhost);
+  bool GetIsGhost() const;
+
 protected:
   void CreatePerformanceObjectIfNeeded();
 
   // Lazily instantiate an about:blank document if necessary, and if
   // we have what it takes to do so.
   void MaybeCreateDoc();
 
   void SetChromeEventHandlerInternal(mozilla::dom::EventTarget* aChromeEventHandler) {
@@ -697,16 +700,20 @@ protected:
   // be null.
   nsCOMPtr<nsPIDOMWindowInner> mTopInnerWindow;
 
   // The evidence that we have tried to cache mTopInnerWindow only once from
   // SetNewDocument(). Note: We need this extra flag because mTopInnerWindow
   // could be null and we don't want it to be set multiple times.
   bool mHasTriedToCacheTopInnerWindow;
 
+  // This is true if we've noticed that this is a ghost window. See
+  // nsWindowMemoryReporter.cpp.
+  bool mIsGhost;
+
   // The number of active IndexedDB databases.
   uint32_t mNumOfIndexedDBDatabases;
 
   // The number of open WebSockets.
   uint32_t mNumOfOpenWebSockets;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowInner, NS_PIDOMWINDOWINNER_IID)
--- a/dom/base/nsWindowMemoryReporter.cpp
+++ b/dom/base/nsWindowMemoryReporter.cpp
@@ -889,19 +889,22 @@ nsWindowMemoryReporter::CheckForGhostWin
 
   mLastCheckForGhostWindows = TimeStamp::NowLoRes();
   KillCheckTimer();
 
   nsTHashtable<nsPtrHashKey<TabGroup>> nonDetachedTabGroups;
 
   // Populate nonDetachedTabGroups.
   for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) {
+    nsGlobalWindowInner* window = iter.UserData();
+
+    window->SetIsGhost(false);
+
     // Null outer window implies null top, but calling GetTop() when there's no
     // outer window causes us to spew debug warnings.
-    nsGlobalWindowInner* window = iter.UserData();
     if (!window->GetOuterWindow() || !window->GetTopInternal()) {
       // This window is detached, so we don't care about its tab group.
       continue;
     }
 
     nonDetachedTabGroups.PutEntry(window->TabGroup());
   }
 
@@ -947,17 +950,20 @@ nsWindowMemoryReporter::CheckForGhostWin
       // window, so it meets ghost criterion (2).
       if (timeStamp.IsNull()) {
         // This may become a ghost window later; start its clock.
         timeStamp = now;
       } else if ((now - timeStamp).ToSeconds() > ghostTimeout) {
         // This definitely is a ghost window, so add it to aOutGhostIDs, if
         // that is not null.
         mGhostWindowCount++;
-        if (aOutGhostIDs && window) {
+
+        window->SetIsGhost(true);
+
+        if (aOutGhostIDs) {
           aOutGhostIDs->PutEntry(window->WindowID());
         }
       }
     }
   }
 }
 
 /* static */ int64_t