Bug 866681, part 2 - Make ContentUnbinder use DeferredFinalize. r=smaug
authorAndrew McCreight <continuation@gmail.com>
Wed, 13 May 2015 12:48:52 -0700
changeset 243754 7fcf6bf43eda78d42b098cc2b24c032ebd29b34e
parent 243753 8e75b3eb23f44c52b6e22e2783bee7aba10c2823
child 243755 82ddfd3ddce00175739c74cdf32863886c8abc50
push id28753
push userkwierso@gmail.com
push dateThu, 14 May 2015 22:33:43 +0000
treeherdermozilla-central@07e2e15703cb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs866681
milestone41.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 866681, part 2 - Make ContentUnbinder use DeferredFinalize. r=smaug
dom/base/FragmentOrElement.cpp
dom/base/FragmentOrElement.h
dom/base/nsCCUncollectableMarker.cpp
xpcom/base/CycleCollectedJSRuntime.h
xpcom/base/nsCycleCollector.cpp
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -8,16 +8,17 @@
  * Base class for all element classes; this provides an implementation
  * of DOM Core's nsIDOMElement, implements nsIContent, provides
  * utility methods for subclasses, and so forth.
  */
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/SegmentedVector.h"
 #include "mozilla/StaticPtr.h"
 
 #include "mozilla/dom/FragmentOrElement.h"
 
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStates.h"
@@ -1220,34 +1221,22 @@ FragmentOrElement::FireNodeInserted(nsID
       mozAutoSubtreeModified subtree(aDoc, aParent);
       (new AsyncEventDispatcher(childContent, mutation))->RunDOMEventWhenSafe();
     }
   }
 }
 
 //----------------------------------------------------------------------
 
-// nsISupports implementation
-
-#define SUBTREE_UNBINDINGS_PER_RUNNABLE 500
-
-class ContentUnbinder : public nsRunnable
+class ContentUnbinder
 {
-public:
-  ContentUnbinder()
-  {
-    mLast = this;
-  }
-
-  ~ContentUnbinder()
-  {
-    Run();
-  }
-
-  void UnbindSubtree(nsIContent* aNode)
+  static const size_t kSegmentSize = sizeof(void*) * 512;
+  typedef SegmentedVector<nsCOMPtr<nsIContent>, kSegmentSize, InfallibleAllocPolicy> ContentArray;
+
+  static void UnbindSubtree(nsIContent* aNode)
   {
     if (aNode->NodeType() != nsIDOMNode::ELEMENT_NODE &&
         aNode->NodeType() != nsIDOMNode::DOCUMENT_FRAGMENT_NODE) {
       return;
     }
     FragmentOrElement* container = static_cast<FragmentOrElement*>(aNode);
     uint32_t childCount = container->mAttrsAndChildren.ChildCount();
     if (childCount) {
@@ -1263,86 +1252,66 @@ public:
           container->mFirstChild = nullptr;
         }
         UnbindSubtree(child);
         child->UnbindFromTree();
       }
     }
   }
 
-  NS_IMETHOD Run()
+  // These two methods are based on DeferredFinalizerImpl.
+
+  static void*
+  AppendContentUnbinderPointer(void* aData, void* aObject)
   {
-    nsAutoScriptBlocker scriptBlocker;
-    uint32_t len = mSubtreeRoots.Length();
-    if (len) {
-      PRTime start = PR_Now();
-      for (uint32_t i = 0; i < len; ++i) {
-        UnbindSubtree(mSubtreeRoots[i]);
-      }
-      mSubtreeRoots.Clear();
-      Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_CONTENT_UNBIND,
-                            uint32_t(PR_Now() - start) / PR_USEC_PER_MSEC);
+    ContentArray* contentArray = static_cast<ContentArray*>(aData);
+    if (!contentArray) {
+      contentArray = new ContentArray();
     }
-    nsCycleCollector_dispatchDeferredDeletion();
-    if (this == sContentUnbinder) {
-      sContentUnbinder = nullptr;
-      if (mNext) {
-        nsRefPtr<ContentUnbinder> next;
-        next.swap(mNext);
-        sContentUnbinder = next;
-        next->mLast = mLast;
-        mLast = nullptr;
-        NS_DispatchToMainThread(next);
-      }
-    }
-    return NS_OK;
+
+    contentArray->InfallibleAppend(dont_AddRef(static_cast<nsIContent*>(aObject)));
+    return contentArray;
   }
 
-  static void UnbindAll()
+  static bool
+  DeferredFinalize(uint32_t aSliceBudget, void* aData)
   {
-    nsRefPtr<ContentUnbinder> ub = sContentUnbinder;
-    sContentUnbinder = nullptr;
-    while (ub) {
-      ub->Run();
-      ub = ub->mNext;
+    MOZ_ASSERT(aSliceBudget > 0, "nonsensical/useless call with aSliceBudget == 0");
+    nsAutoScriptBlocker scriptBlocker;
+    ContentArray* contentArray = static_cast<ContentArray*>(aData);
+
+    size_t numToRemove = contentArray->Length();
+    if (aSliceBudget < numToRemove) {
+      numToRemove = aSliceBudget;
     }
-  }
-
-  static void Append(nsIContent* aSubtreeRoot)
-  {
-    if (!sContentUnbinder) {
-      sContentUnbinder = new ContentUnbinder();
-      nsCOMPtr<nsIRunnable> e = sContentUnbinder;
-      NS_DispatchToMainThread(e);
+
+    for (size_t i = 0; i < numToRemove; ++i) {
+      nsCOMPtr<nsIContent> element = contentArray->GetLast().forget();
+      contentArray->PopLast();
+      UnbindSubtree(element);
     }
 
-    if (sContentUnbinder->mLast->mSubtreeRoots.Length() >=
-        SUBTREE_UNBINDINGS_PER_RUNNABLE) {
-      sContentUnbinder->mLast->mNext = new ContentUnbinder();
-      sContentUnbinder->mLast = sContentUnbinder->mLast->mNext;
+    nsCycleCollector_dispatchDeferredDeletion();
+
+    if (contentArray->Length() == 0) {
+      delete contentArray;
+      return true;
     }
-    sContentUnbinder->mLast->mSubtreeRoots.AppendElement(aSubtreeRoot);
+    return false;
   }
 
-private:
-  nsAutoTArray<nsCOMPtr<nsIContent>,
-               SUBTREE_UNBINDINGS_PER_RUNNABLE> mSubtreeRoots;
-  nsRefPtr<ContentUnbinder>                     mNext;
-  ContentUnbinder*                              mLast;
-  static ContentUnbinder*                       sContentUnbinder;
+public:
+  static void
+  Append(nsIContent* aSubtreeRoot)
+  {
+    nsCOMPtr<nsIContent> root = aSubtreeRoot;
+    mozilla::DeferredFinalize(AppendContentUnbinderPointer, DeferredFinalize, root.forget().take());
+  }
 };
 
-ContentUnbinder* ContentUnbinder::sContentUnbinder = nullptr;
-
-void
-FragmentOrElement::ClearContentUnbinder()
-{
-  ContentUnbinder::UnbindAll();
-}
-
 NS_IMPL_CYCLE_COLLECTION_CLASS(FragmentOrElement)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FragmentOrElement)
   nsINode::Unlink(tmp);
 
   // The XBL binding is removed by RemoveFromBindingManagerRunnable
   // which is dispatched in UnbindFromTree.
 
--- a/dom/base/FragmentOrElement.h
+++ b/dom/base/FragmentOrElement.h
@@ -210,17 +210,16 @@ public:
     return mRefCnt.IsPurple();
   }
 
   virtual void RemovePurple() override
   {
     mRefCnt.RemovePurple();
   }
 
-  static void ClearContentUnbinder();
   static bool CanSkip(nsINode* aNode, bool aRemovingAllowed);
   static bool CanSkipInCC(nsINode* aNode);
   static bool CanSkipThis(nsINode* aNode);
   static void RemoveBlackMarkedNode(nsINode* aNode);
   static void MarkNodeChildren(nsINode* aNode);
   static void InitCCCallbacks();
   static void MarkUserData(void* aObject, nsIAtom* aKey, void* aChild,
                            void *aData);
--- a/dom/base/nsCCUncollectableMarker.cpp
+++ b/dom/base/nsCCUncollectableMarker.cpp
@@ -312,18 +312,16 @@ MarkWindowList(nsISimpleEnumerator* aWin
   }
 }
 
 nsresult
 nsCCUncollectableMarker::Observe(nsISupports* aSubject, const char* aTopic,
                                  const char16_t* aData)
 {
   if (!strcmp(aTopic, "xpcom-shutdown")) {
-    Element::ClearContentUnbinder();
-
     nsCOMPtr<nsIObserverService> obs =
       mozilla::services::GetObserverService();
     if (!obs)
       return NS_ERROR_FAILURE;
 
     // No need for kungFuDeathGrip here, yay observerservice!
     obs->RemoveObserver(this, "xpcom-shutdown");
     obs->RemoveObserver(this, "cycle-collector-begin");
@@ -338,19 +336,16 @@ nsCCUncollectableMarker::Observe(nsISupp
                !strcmp(aTopic, "cycle-collector-forget-skippable"), "wrong topic");
 
   // JS cleanup can be slow. Do it only if there has been a GC.
   bool cleanupJS =
     nsJSContext::CleanupsSinceLastGC() == 0 &&
     !strcmp(aTopic, "cycle-collector-forget-skippable");
 
   bool prepareForCC = !strcmp(aTopic, "cycle-collector-begin");
-  if (prepareForCC) {
-    Element::ClearContentUnbinder();
-  }
 
   // Increase generation to effectively unmark all current objects
   if (!++sGeneration) {
     ++sGeneration;
   }
 
   nsFocusManager::MarkUncollectableForCCGeneration(sGeneration);
 
--- a/xpcom/base/CycleCollectedJSRuntime.h
+++ b/xpcom/base/CycleCollectedJSRuntime.h
@@ -203,32 +203,32 @@ private:
   static void OutOfMemoryCallback(JSContext* aContext, void* aData);
   static void LargeAllocationFailureCallback(void* aData);
   static bool ContextCallback(JSContext* aCx, unsigned aOperation,
                               void* aData);
 
   virtual void TraceNativeBlackRoots(JSTracer* aTracer) { };
   void TraceNativeGrayRoots(JSTracer* aTracer);
 
+public:
   enum DeferredFinalizeType {
     FinalizeIncrementally,
     FinalizeNow,
   };
 
   void FinalizeDeferredThings(DeferredFinalizeType aType);
 
-public:
   // Two conditions, JSOutOfMemory and JSLargeAllocationFailure, are noted in
   // crash reports. Here are the values that can appear in the reports:
   enum class OOMState : uint32_t {
     // The condition has never happened. No entry appears in the crash report.
     OK,
 
     // We are currently reporting the given condition.
-    // 
+    //
     // Suppose a crash report contains "JSLargeAllocationFailure:
     // Reporting". This means we crashed while executing memory-pressure
     // observers, trying to shake loose some memory. The large allocation in
     // question did not return null: it is still on the stack. Had we not
     // crashed, it would have been retried.
     Reporting,
 
     // The condition has been reported since the last GC.
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -3536,16 +3536,19 @@ nsCycleCollector::CleanupAfterCollection
 
   CC_TELEMETRY( , interval);
   CC_TELEMETRY(_VISITED_REF_COUNTED, mResults.mVisitedRefCounted);
   CC_TELEMETRY(_VISITED_GCED, mResults.mVisitedGCed);
   CC_TELEMETRY(_COLLECTED, mWhiteNodeCount);
   timeLog.Checkpoint("CleanupAfterCollection::telemetry");
 
   if (mJSRuntime) {
+    mJSRuntime->FinalizeDeferredThings(mResults.mAnyManual
+                                       ? CycleCollectedJSRuntime::FinalizeNow
+                                       : CycleCollectedJSRuntime::FinalizeIncrementally);
     mJSRuntime->EndCycleCollectionCallback(mResults);
     timeLog.Checkpoint("CleanupAfterCollection::EndCycleCollectionCallback()");
   }
   mIncrementalPhase = IdlePhase;
 }
 
 void
 nsCycleCollector::ShutdownCollect()