Bug 1300659 P5 Suspend a window if its main thread TaskQueue falls to far behind. r=smaug
authorBen Kelly <ben@wanderview.com>
Mon, 07 Nov 2016 12:30:18 -0800
changeset 351620 24d97dc514d4b8fe608e9aee7527615c1e613606
parent 351619 2ff33f3c59d5782f460d03d28f91a0a2dce0b8f0
child 351621 8c2ad6366c6be9fce02428ca22b973be8d9d295b
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1300659
milestone52.0a1
Bug 1300659 P5 Suspend a window if its main thread TaskQueue falls to far behind. r=smaug
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -3636,16 +3636,57 @@ nsGlobalWindow::DefineArgumentsProperty(
 
   nsIScriptContext *ctx = GetOuterWindowInternal()->mContext;
   NS_ENSURE_TRUE(aArguments && ctx, NS_ERROR_NOT_INITIALIZED);
 
   JS::Rooted<JSObject*> obj(RootingCx(), GetWrapperPreserveColor());
   return ctx->SetProperty(obj, "arguments", aArguments);
 }
 
+void
+nsGlobalWindow::MaybeApplyBackPressure()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // If we are already suspended, then we don't need to apply back
+  // pressure for ThrottledEventQueue reasons.  This also avoids repeatedly
+  // calling SuspendTimeout() if this routine is executed many times
+  // before dropping below the backpressure threshold.
+  if (IsSuspended()) {
+    return;
+  }
+
+  RefPtr<ThrottledEventQueue> taskQueue = TabGroup()->GetThrottledEventQueue();
+  if (!taskQueue) {
+    return;
+  }
+
+  // Only stop the window if it has greatly fallen behind the main thread.
+  // This is a somewhat arbitrary threshold chosen such that it should
+  // rarely fire under normaly circumstances.  Its low enough, though,
+  // that we should avoid hitting an OOM from the backed up runnables in
+  // the queue.
+  static const uint32_t kThrottledEventQueueBackPressure = 5000;
+  if (taskQueue->Length() < kThrottledEventQueueBackPressure) {
+    return;
+  }
+
+  // First attempt to queue a runnable to resume running timeouts.  We do
+  // this first in order to verify we can dispatch successfully.
+  nsCOMPtr<nsIRunnable> r = NewRunnableMethod(this, &nsGlobalWindow::Resume);
+  nsresult rv = taskQueue->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  // Since the resume is dispatched we can go ahead and suspend the window
+  // now.  Once the task queue drains the resume will automatically get
+  // executed balancing this suspend.
+  // TODO: Consider suppressing event handling as well.
+  Suspend();
+}
+
 //*****************************************************************************
 // nsGlobalWindow::nsIScriptObjectPrincipal
 //*****************************************************************************
 
 nsIPrincipal*
 nsGlobalWindow::GetPrincipal()
 {
   if (mDoc) {
@@ -13066,16 +13107,18 @@ nsGlobalWindow::RunTimeout(Timeout* aTim
   }
 
   // Take the dummy timeout off the head of the list
   dummy_timeout->remove();
   timeoutExtraRef = nullptr;
   MOZ_ASSERT(dummy_timeout->HasRefCntOne(), "dummy_timeout may leak");
 
   mTimeoutInsertionPoint = last_insertion_point;
+
+  MaybeApplyBackPressure();
 }
 
 void
 nsGlobalWindow::ClearTimeoutOrInterval(int32_t aTimerId, Timeout::Reason aReason)
 {
   MOZ_RELEASE_ASSERT(IsInnerWindow());
 
   uint32_t timerId = (uint32_t)aTimerId;
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -1703,16 +1703,22 @@ private:
   // IsSecureContext() for the inner window that corresponds to aDocument.
   bool ComputeIsSecureContext(nsIDocument* aDocument);
 
   // nsPIDOMWindow<T> should be able to see these helper methods.
   friend class nsPIDOMWindow<mozIDOMWindowProxy>;
   friend class nsPIDOMWindow<mozIDOMWindow>;
   friend class nsPIDOMWindow<nsISupports>;
 
+  // Apply back pressure to the window if the TabGroup ThrottledEventQueue
+  // exists and has too many runnables waiting to run.  For example, suspend
+  // timers until we have a chance to catch up, etc.
+  void
+  MaybeApplyBackPressure();
+
   mozilla::dom::TabGroup* TabGroupInner();
   mozilla::dom::TabGroup* TabGroupOuter();
 
 protected:
   // These members are only used on outer window objects. Make sure
   // you never set any of these on an inner object!
   bool                          mFullScreen : 1;
   bool                          mFullscreenMode : 1;