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
--- 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