Backed out changeset 798bab343a0a (bug 1589561) for turning bug 1586420 into permafail. CLOSED TREE
authorCosmin Sabou <csabou@mozilla.com>
Fri, 18 Oct 2019 23:04:26 +0300
changeset 559620 21b88cef47e1e4aafb8450953739d3c15cffb766
parent 559619 5887f4095a87aca9cf4d9bdf81edcc7258b58cd2
child 559621 91e184a01fa74c39d7be931700d3a932f63288a2
push id12177
push usercsabou@mozilla.com
push dateMon, 21 Oct 2019 14:52:16 +0000
treeherdermozilla-beta@1918a9cd33bc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1589561, 1586420
milestone71.0a1
backs out798bab343a0aa9e7940e5a449e6555096ecdc4ac
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
Backed out changeset 798bab343a0a (bug 1589561) for turning bug 1586420 into permafail. CLOSED TREE
ipc/glue/IdleSchedulerChild.cpp
ipc/glue/IdleSchedulerChild.h
xpcom/threads/IdlePeriodState.cpp
xpcom/threads/IdlePeriodState.h
xpcom/threads/PrioritizedEventQueue.cpp
xpcom/threads/PrioritizedEventQueue.h
xpcom/threads/moz.build
--- a/ipc/glue/IdleSchedulerChild.cpp
+++ b/ipc/glue/IdleSchedulerChild.cpp
@@ -2,54 +2,54 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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/. */
 
 #include "mozilla/ipc/IdleSchedulerChild.h"
 #include "mozilla/ipc/IdleSchedulerParent.h"
 #include "mozilla/Atomics.h"
-#include "mozilla/IdlePeriodState.h"
+#include "mozilla/PrioritizedEventQueue.h"
 #include "BackgroundChild.h"
 
 namespace mozilla {
 namespace ipc {
 
 static IdleSchedulerChild* sMainThreadIdleScheduler = nullptr;
 
 IdleSchedulerChild::~IdleSchedulerChild() {
   if (sMainThreadIdleScheduler == this) {
     sMainThreadIdleScheduler = nullptr;
   }
-  MOZ_ASSERT(!mIdlePeriodState);
+  MOZ_ASSERT(!mEventQueue);
 }
 
-void IdleSchedulerChild::Init(IdlePeriodState* aIdlePeriodState) {
-  mIdlePeriodState = aIdlePeriodState;
+void IdleSchedulerChild::Init(PrioritizedEventQueue* aEventQueue) {
+  mEventQueue = aEventQueue;
 
   RefPtr<IdleSchedulerChild> scheduler = this;
   auto resolve =
       [&](Tuple<mozilla::Maybe<SharedMemoryHandle>, uint32_t>&& aResult) {
         if (Get<0>(aResult)) {
           mActiveCounter.SetHandle(*Get<0>(aResult), false);
           mActiveCounter.Map(sizeof(int32_t));
           mChildId = Get<1>(aResult);
-          if (mChildId && mIdlePeriodState && mIdlePeriodState->IsActive()) {
+          if (mChildId && mEventQueue && mEventQueue->IsActive()) {
             SetActive();
           }
         }
       };
 
   auto reject = [&](ResponseRejectReason) {};
   SendInitForIdleUse(std::move(resolve), std::move(reject));
 }
 
 IPCResult IdleSchedulerChild::RecvIdleTime(uint64_t aId, TimeDuration aBudget) {
-  if (mIdlePeriodState) {
-    mIdlePeriodState->SetIdleToken(aId, aBudget);
+  if (mEventQueue) {
+    mEventQueue->SetIdleToken(aId, aBudget);
   }
   return IPC_OK();
 }
 
 void IdleSchedulerChild::SetActive() {
   if (mChildId && CanSend() && mActiveCounter.memory()) {
     ++(static_cast<Atomic<int32_t>*>(
         mActiveCounter.memory())[NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER]);
--- a/ipc/glue/IdleSchedulerChild.h
+++ b/ipc/glue/IdleSchedulerChild.h
@@ -9,50 +9,50 @@
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ipc/PIdleSchedulerChild.h"
 
 class nsIIdlePeriod;
 
 namespace mozilla {
-class IdlePeriodState;
+class PrioritizedEventQueue;
 
 namespace ipc {
 
 class BackgroundChildImpl;
 
 class IdleSchedulerChild final : public PIdleSchedulerChild {
  public:
   IdleSchedulerChild() = default;
 
   NS_INLINE_DECL_REFCOUNTING(IdleSchedulerChild)
 
   IPCResult RecvIdleTime(uint64_t aId, TimeDuration aBudget);
 
-  void Init(IdlePeriodState* aIdlePeriodState);
+  void Init(PrioritizedEventQueue* aEventQueue);
 
-  void Disconnect() { mIdlePeriodState = nullptr; }
+  void Disconnect() { mEventQueue = nullptr; }
 
   // See similar methods on PrioritizedEventQueue.
   void SetActive();
   // Returns true if activity state dropped below cpu count.
   bool SetPaused();
 
   static IdleSchedulerChild* GetMainThreadIdleScheduler();
 
  private:
   ~IdleSchedulerChild();
 
   friend class BackgroundChildImpl;
 
   // See IdleScheduleParent::sActiveChildCounter
   base::SharedMemory mActiveCounter;
 
-  IdlePeriodState* mIdlePeriodState = nullptr;
+  PrioritizedEventQueue* mEventQueue = nullptr;
 
   uint32_t mChildId = 0;
 };
 
 }  // namespace ipc
 }  // namespace mozilla
 
 #endif  // mozilla_ipc_IdleSchedulerChild_h__
deleted file mode 100644
--- a/xpcom/threads/IdlePeriodState.cpp
+++ /dev/null
@@ -1,252 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-#include "mozilla/IdlePeriodState.h"
-#include "mozilla/StaticPrefs_idle_period.h"
-#include "mozilla/ipc/IdleSchedulerChild.h"
-#include "nsIIdlePeriod.h"
-#include "nsThreadManager.h"
-#include "nsThreadUtils.h"
-#include "nsXPCOM.h"
-#include "nsXULAppAPI.h"
-
-static uint64_t sIdleRequestCounter = 0;
-
-namespace mozilla {
-
-IdlePeriodState::IdlePeriodState(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod)
-    : mIdlePeriod(aIdlePeriod) {
-  MOZ_ASSERT(NS_IsMainThread(),
-             "Why are we touching idle state off the main thread?");
-}
-
-IdlePeriodState::~IdlePeriodState() {
-  MOZ_ASSERT(NS_IsMainThread(),
-             "Why are we touching idle state off the main thread?");
-  if (mIdleScheduler) {
-    mIdleScheduler->Disconnect();
-  }
-}
-
-size_t IdlePeriodState::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
-  size_t n = 0;
-  if (mIdlePeriod) {
-    n += aMallocSizeOf(mIdlePeriod);
-  }
-
-  return n;
-}
-
-void IdlePeriodState::FlagNotIdle(const MutexAutoUnlock& aProofOfUnlock) {
-  MOZ_ASSERT(NS_IsMainThread(),
-             "Why are we touching idle state off the main thread?");
-
-  EnsureIsActive();
-  if (mIdleToken && mIdleToken < TimeStamp::Now()) {
-    ClearIdleToken(aProofOfUnlock);
-  }
-}
-
-void IdlePeriodState::RanOutOfTasks(const MutexAutoUnlock& aProofOfUnlock) {
-  MOZ_ASSERT(NS_IsMainThread(),
-             "Why are we touching idle state off the main thread?");
-  MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent);
-  EnsureIsPaused(aProofOfUnlock);
-  ClearIdleToken(aProofOfUnlock);
-}
-
-TimeStamp IdlePeriodState::GetIdleDeadlineInternal(
-    bool aIsPeek, const MutexAutoUnlock& aProofOfUnlock) {
-  MOZ_ASSERT(NS_IsMainThread(),
-             "Why are we touching idle state off the main thread?");
-
-  bool shuttingDown;
-  TimeStamp localIdleDeadline =
-      GetLocalIdleDeadline(shuttingDown, aProofOfUnlock);
-  if (!localIdleDeadline) {
-    if (!aIsPeek) {
-      EnsureIsPaused(aProofOfUnlock);
-      ClearIdleToken(aProofOfUnlock);
-    }
-    return TimeStamp();
-  }
-
-  TimeStamp idleDeadline =
-      mHasPendingEventsPromisedIdleEvent || shuttingDown
-          ? localIdleDeadline
-          : GetIdleToken(localIdleDeadline, aProofOfUnlock);
-  if (!idleDeadline) {
-    if (!aIsPeek) {
-      EnsureIsPaused(aProofOfUnlock);
-
-      // Don't call ClearIdleToken() here, since we may have a pending
-      // request already.
-      RequestIdleToken(localIdleDeadline, aProofOfUnlock);
-    }
-    return TimeStamp();
-  }
-
-  if (!aIsPeek) {
-    EnsureIsActive();
-  }
-  return idleDeadline;
-}
-
-TimeStamp IdlePeriodState::GetLocalIdleDeadline(
-    bool& aShuttingDown, const MutexAutoUnlock& aProofOfUnlock) {
-  MOZ_ASSERT(NS_IsMainThread(),
-             "Why are we touching idle state off the main thread?");
-  // If we are shutting down, we won't honor the idle period, and we will
-  // always process idle runnables.  This will ensure that the idle queue
-  // gets exhausted at shutdown time to prevent intermittently leaking
-  // some runnables inside that queue and even worse potentially leaving
-  // some important cleanup work unfinished.
-  if (gXPCOMThreadsShutDown ||
-      nsThreadManager::get().GetCurrentThread()->ShuttingDown()) {
-    aShuttingDown = true;
-    return TimeStamp::Now();
-  }
-
-  aShuttingDown = false;
-  TimeStamp idleDeadline;
-  // This GetIdlePeriodHint call is why we need aProofOfUnlock, since getting
-  // the idle period might need to lock the timer thread.  Can we get right of
-  // this somehow?
-  Unused << aProofOfUnlock;
-  mIdlePeriod->GetIdlePeriodHint(&idleDeadline);
-
-  // If HasPendingEvents() has been called and it has returned true because of
-  // pending idle events, there is a risk that we may decide here that we aren't
-  // idle and return null, in which case HasPendingEvents() has effectively
-  // lied.  Since we can't go back and fix the past, we have to adjust what we
-  // do here and forcefully pick the idle queue task here.  Note that this means
-  // that we are choosing to run a task from the idle queue when we would
-  // normally decide that we aren't in an idle period, but this can only happen
-  // if we fall out of the idle period in between the call to HasPendingEvents()
-  // and here, which should hopefully be quite rare.  We are effectively
-  // choosing to prioritize the sanity of our API semantics over the optimal
-  // scheduling.
-  if (!mHasPendingEventsPromisedIdleEvent &&
-      (!idleDeadline || idleDeadline < TimeStamp::Now())) {
-    return TimeStamp();
-  }
-  if (mHasPendingEventsPromisedIdleEvent && !idleDeadline) {
-    // If HasPendingEvents() has been called and it has returned true, but we're
-    // no longer in the idle period, we must return a valid timestamp to pretend
-    // that we are still in the idle period.
-    return TimeStamp::Now();
-  }
-  return idleDeadline;
-}
-
-TimeStamp IdlePeriodState::GetIdleToken(TimeStamp aLocalIdlePeriodHint,
-                                        const MutexAutoUnlock& aProofOfUnlock) {
-  MOZ_ASSERT(NS_IsMainThread(),
-             "Why are we touching idle state off the main thread?");
-
-  if (XRE_IsParentProcess()) {
-    return aLocalIdlePeriodHint;
-  }
-  if (mIdleToken) {
-    TimeStamp now = TimeStamp::Now();
-    if (mIdleToken < now) {
-      ClearIdleToken(aProofOfUnlock);
-      return mIdleToken;
-    }
-    return mIdleToken < aLocalIdlePeriodHint ? mIdleToken
-                                             : aLocalIdlePeriodHint;
-  }
-  return TimeStamp();
-}
-
-void IdlePeriodState::RequestIdleToken(TimeStamp aLocalIdlePeriodHint,
-                                       const MutexAutoUnlock& aProofOfUnlock) {
-  MOZ_ASSERT(NS_IsMainThread(),
-             "Why are we touching idle state off the main thread?");
-  MOZ_ASSERT(!mActive);
-
-  if (!mIdleSchedulerInitialized) {
-    mIdleSchedulerInitialized = true;
-    if (StaticPrefs::idle_period_cross_process_scheduling() &&
-        XRE_IsContentProcess() &&
-        // Disable when recording/replaying, as IdleSchedulerChild uses mutable
-        // shared memory which needs special handling.
-        !recordreplay::IsRecordingOrReplaying()) {
-      // For now cross-process idle scheduler is supported only on the main
-      // threads of the child processes.
-      mIdleScheduler = ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
-      if (mIdleScheduler) {
-        mIdleScheduler->Init(this);
-      }
-    }
-  }
-
-  if (mIdleScheduler && !mIdleRequestId) {
-    TimeStamp now = TimeStamp::Now();
-    if (aLocalIdlePeriodHint <= now) {
-      return;
-    }
-
-    mIdleRequestId = ++sIdleRequestCounter;
-    // This is presumably the bit where we need the "not holding mutexes"
-    // guarantee.  Can we avoid that somehow?
-    Unused << aProofOfUnlock;
-    mIdleScheduler->SendRequestIdleTime(mIdleRequestId,
-                                        aLocalIdlePeriodHint - now);
-  }
-}
-
-void IdlePeriodState::SetIdleToken(uint64_t aId, TimeDuration aDuration) {
-  MOZ_ASSERT(NS_IsMainThread(),
-             "Why are we touching idle state off the main thread?");
-  if (mIdleRequestId == aId) {
-    mIdleToken = TimeStamp::Now() + aDuration;
-  }
-}
-
-void IdlePeriodState::SetActive() {
-  MOZ_ASSERT(NS_IsMainThread(),
-             "Why are we touching idle state off the main thread?");
-  MOZ_ASSERT(!mActive);
-  if (mIdleScheduler) {
-    mIdleScheduler->SetActive();
-  }
-  mActive = true;
-}
-
-void IdlePeriodState::SetPaused(const MutexAutoUnlock& aProofOfUnlock) {
-  MOZ_ASSERT(NS_IsMainThread(),
-             "Why are we touching idle state off the main thread?");
-  MOZ_ASSERT(mActive);
-  if (mIdleScheduler && mIdleScheduler->SetPaused()) {
-    // We may have gotten a free cpu core for running idle tasks.
-    // We don't try to catch the case when there are prioritized processes
-    // running.
-    // This SendSchedule call is why we need to aProofOfUnlock, because IPC can
-    // do weird things with mutexes.
-    Unused << aProofOfUnlock;
-    mIdleScheduler->SendSchedule();
-  }
-  mActive = false;
-}
-
-void IdlePeriodState::ClearIdleToken(const MutexAutoUnlock& aProofOfUnlock) {
-  MOZ_ASSERT(NS_IsMainThread(),
-             "Why are we touching idle state off the main thread?");
-
-  if (mIdleRequestId) {
-    if (mIdleScheduler) {
-      // This SendIdleTimeUsed call is why we need a proof of unlock.  Can we
-      // eliminate that requirement somehow?
-      Unused << aProofOfUnlock;
-      mIdleScheduler->SendIdleTimeUsed(mIdleRequestId);
-    }
-    mIdleRequestId = 0;
-    mIdleToken = TimeStamp();
-  }
-}
-
-}  // namespace mozilla
deleted file mode 100644
--- a/xpcom/threads/IdlePeriodState.h
+++ /dev/null
@@ -1,190 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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 mozilla_IdlePeriodState_h
-#define mozilla_IdlePeriodState_h
-
-/**
- * A class for tracking the state of our idle period.  This includes keeping
- * track of both the state of our process-local idle period estimate and, for
- * content processes, managing communication with the parent process for
- * cross-pprocess idle detection.
- */
-
-#include "mozilla/MemoryReporting.h"
-#include "mozilla/Mutex.h"
-#include "mozilla/RefPtr.h"
-#include "mozilla/TimeStamp.h"
-#include "nsCOMPtr.h"
-
-#include <stdint.h>
-
-class nsIIdlePeriod;
-
-namespace mozilla {
-namespace ipc {
-class IdleSchedulerChild;
-}  // namespace ipc
-
-class IdlePeriodState {
- public:
-  explicit IdlePeriodState(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod);
-
-  ~IdlePeriodState();
-
-  // Integration with memory reporting.
-  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
-
-  // Notification that whoever we are tracking idle state for has found a
-  // non-idle task to process.
-  //
-  // Do not call this while holding any mutexes, because it may lock mutexes
-  // itself.
-  void FlagNotIdle(const MutexAutoUnlock& aProofOfUnlock);
-
-  // Notification that whoever we are tracking idle state for has no more
-  // tasks (idle or not) to process.
-  //
-  // Do not call this while holding any mutexes, because it may lock mutexes
-  // itself.
-  void RanOutOfTasks(const MutexAutoUnlock& aProofOfUnlock);
-
-  // Notification that whoever we are tracking idle state has idle tasks that
-  // they are considering ready to run and that we should keep claiming they are
-  // ready to run until they call ForgetPendingTaskGuarantee().
-  void EnforcePendingTaskGuarantee() {
-    mHasPendingEventsPromisedIdleEvent = true;
-  }
-
-  // Notification that whoever we are tracking idle state for is done with our
-  // "we have an idle event ready to run" guarantee.  When this happens, we can
-  // reset mHasPendingEventsPromisedIdleEvent to false, because we have
-  // fulfilled our contract.
-  void ForgetPendingTaskGuarantee() {
-    mHasPendingEventsPromisedIdleEvent = false;
-  }
-
-  // Get our current idle deadline so we can run an idle task with that
-  // deadline.  This can return a null timestamp (which means we are not idle
-  // right now), and it can also queue up queries to our parent process, if
-  // we're a content process, to find out whether we're idle.  This should only
-  // be called when there is an actual idle task that might run.
-  //
-  // Do not call this while holding any mutexes, because it may lock mutexes
-  // itself.
-  TimeStamp GetDeadlineForIdleTask(const MutexAutoUnlock& aProofOfUnlock) {
-    return GetIdleDeadlineInternal(false, aProofOfUnlock);
-  }
-
-  // Peek our current idle deadline.  This can return a null timestamp (which
-  // means we are not idle right now).  This method does not have any
-  // side-effects on our state, apart from guaranteeing that if it returns
-  // non-null then GetDeadlineForIdleTask will return non-null until
-  // ForgetPendingTaskGuarantee() is called.
-  //
-  // Do not call this while holding any mutexes, because it may lock mutexes
-  // itself.
-  TimeStamp PeekIdleDeadline(const MutexAutoUnlock& aProofOfUnlock) {
-    return GetIdleDeadlineInternal(true, aProofOfUnlock);
-  }
-
-  void SetIdleToken(uint64_t aId, TimeDuration aDuration);
-
-  bool IsActive() { return mActive; }
-
- protected:
-  void EnsureIsActive() {
-    if (!mActive) {
-      SetActive();
-    }
-  }
-
-  void EnsureIsPaused(const MutexAutoUnlock& aProofOfUnlock) {
-    if (mActive) {
-      SetPaused(aProofOfUnlock);
-    }
-  }
-
-  // Returns a null TimeStamp if we're not in the idle period.
-  TimeStamp GetLocalIdleDeadline(bool& aShuttingDown,
-                                 const MutexAutoUnlock& aProofOfUnlock);
-
-  // Gets the idle token, which is the end time of the idle period.
-  //
-  // Do not call this while holding any mutexes, because it may lock mutexes
-  // itself.
-  TimeStamp GetIdleToken(TimeStamp aLocalIdlePeriodHint,
-                         const MutexAutoUnlock& aProofOfUnlock);
-
-  // In case of child processes, requests idle time from the cross-process
-  // idle scheduler.
-  //
-  // Do not call this while holding any mutexes, because it may lock mutexes
-  // itself.
-  void RequestIdleToken(TimeStamp aLocalIdlePeriodHint,
-                        const MutexAutoUnlock& aProofOfUnlock);
-
-  // Mark that we don't have idle time to use, nor are expecting to get an idle
-  // token from the idle scheduler.
-  void ClearIdleToken(const MutexAutoUnlock& aProofOfUnlock);
-
-  // SetActive should be called when the event queue is running any type of
-  // tasks.
-  void SetActive();
-  // SetPaused should be called once the event queue doesn't have more
-  // tasks to process, or is waiting for the idle token.
-  //
-  // Do not call this while holding any mutexes, because it may lock mutexes
-  // itself.
-  void SetPaused(const MutexAutoUnlock& aProofOfUnlock);
-
-  // Get or peek our idle deadline.  When peeking, we generally don't change any
-  // of our internal state.  When getting, we may request an idle token as
-  // needed.
-  //
-  // Do not call this while holding any mutexes, because it may lock mutexes
-  // itself.
-  TimeStamp GetIdleDeadlineInternal(bool aIsPeek,
-                                    const MutexAutoUnlock& aProofOfUnlock);
-
-  // Set to true if we have claimed we have a ready-to-run idle task when asked.
-  // In that case, we will ensure that we allow at least one task to run when
-  // someone tries to run a task, even if we have run out of idle period at that
-  // point.  This ensures that we never fail to produce a task to run if we
-  // claim we have a task ready to run.
-  bool mHasPendingEventsPromisedIdleEvent = false;
-
-  // mIdlePeriod keeps track of the current idle period. Calling
-  // mIdlePeriod->GetIdlePeriodHint() will give an estimate of when
-  // the current idle period will end.
-  nsCOMPtr<nsIIdlePeriod> mIdlePeriod;
-
-  // If non-null, this timestamp represents the end time of the idle period.  An
-  // idle period starts when we get the idle token from the parent process and
-  // ends when either there are no more things we want to run at idle priority
-  // or mIdleToken < TimeStamp::Now(), so we have reached our idle deadline.
-  TimeStamp mIdleToken;
-
-  // The id of the last idle request to the cross-process idle scheduler.
-  uint64_t mIdleRequestId = 0;
-
-  // If we're in a content process, we use mIdleScheduler to communicate with
-  // the parent process for purposes of cross-process idle tracking.
-  RefPtr<ipc::IdleSchedulerChild> mIdleScheduler;
-
-  // mIdleSchedulerInitialized is true if our mIdleScheduler has been
-  // initialized.  It may be null even after initialiazation, in various
-  // situations.
-  bool mIdleSchedulerInitialized = false;
-
-  // mActive is true when the PrioritizedEventQueue or TaskController we are
-  // associated with is running tasks.
-  bool mActive = true;
-};
-
-}  // namespace mozilla
-
-#endif  // mozilla_IdlePeriodState_h
--- a/xpcom/threads/PrioritizedEventQueue.cpp
+++ b/xpcom/threads/PrioritizedEventQueue.cpp
@@ -2,36 +2,43 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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/. */
 
 #include "PrioritizedEventQueue.h"
 #include "mozilla/EventQueue.h"
 #include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_idle_period.h"
 #include "mozilla/StaticPrefs_threads.h"
 #include "mozilla/ipc/IdleSchedulerChild.h"
 #include "nsThreadManager.h"
 #include "nsXPCOMPrivate.h"  // for gXPCOMThreadsShutDown
 #include "InputEventStatistics.h"
 
 using namespace mozilla;
 
+static uint64_t sIdleRequestCounter = 0;
+
 PrioritizedEventQueue::PrioritizedEventQueue(
-    already_AddRefed<nsIIdlePeriod>&& aIdlePeriod)
+    already_AddRefed<nsIIdlePeriod> aIdlePeriod)
     : mHighQueue(MakeUnique<EventQueue>(EventQueuePriority::High)),
       mInputQueue(MakeUnique<EventQueue>(EventQueuePriority::Input)),
       mMediumHighQueue(MakeUnique<EventQueue>(EventQueuePriority::MediumHigh)),
       mNormalQueue(MakeUnique<EventQueue>(EventQueuePriority::Normal)),
       mDeferredTimersQueue(
           MakeUnique<EventQueue>(EventQueuePriority::DeferredTimers)),
       mIdleQueue(MakeUnique<EventQueue>(EventQueuePriority::Idle)),
-      mIdlePeriodState(std::move(aIdlePeriod)) {}
+      mIdlePeriod(aIdlePeriod) {}
 
-PrioritizedEventQueue::~PrioritizedEventQueue() = default;
+PrioritizedEventQueue::~PrioritizedEventQueue() {
+  if (mIdleScheduler) {
+    mIdleScheduler->Disconnect();
+  }
+}
 
 void PrioritizedEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
                                      EventQueuePriority aPriority,
                                      const MutexAutoLock& aProofOfLock) {
   // Double check the priority with a QI.
   RefPtr<nsIRunnable> event(aEvent);
   EventQueuePriority priority = aPriority;
 
@@ -63,16 +70,63 @@ void PrioritizedEventQueue::PutEvent(alr
       mIdleQueue->PutEvent(event.forget(), priority, aProofOfLock);
       break;
     case EventQueuePriority::Count:
       MOZ_CRASH("EventQueuePriority::Count isn't a valid priority");
       break;
   }
 }
 
+TimeStamp PrioritizedEventQueue::GetLocalIdleDeadline(bool& aShuttingDown) {
+  // If we are shutting down, we won't honor the idle period, and we will
+  // always process idle runnables.  This will ensure that the idle queue
+  // gets exhausted at shutdown time to prevent intermittently leaking
+  // some runnables inside that queue and even worse potentially leaving
+  // some important cleanup work unfinished.
+  if (gXPCOMThreadsShutDown ||
+      nsThreadManager::get().GetCurrentThread()->ShuttingDown()) {
+    aShuttingDown = true;
+    return TimeStamp::Now();
+  }
+
+  aShuttingDown = false;
+  TimeStamp idleDeadline;
+  {
+    // Releasing the lock temporarily since getting the idle period
+    // might need to lock the timer thread. Unlocking here might make
+    // us receive an event on the main queue, but we've committed to
+    // run an idle event anyhow.
+    MutexAutoUnlock unlock(*mMutex);
+    mIdlePeriod->GetIdlePeriodHint(&idleDeadline);
+  }
+
+  // If HasPendingEvents() has been called and it has returned true because of
+  // pending idle events, there is a risk that we may decide here that we aren't
+  // idle and return null, in which case HasPendingEvents() has effectively
+  // lied.  Since we can't go back and fix the past, we have to adjust what we
+  // do here and forcefully pick the idle queue task here.  Note that this means
+  // that we are choosing to run a task from the idle queue when we would
+  // normally decide that we aren't in an idle period, but this can only happen
+  // if we fall out of the idle period in between the call to HasPendingEvents()
+  // and here, which should hopefully be quite rare.  We are effectively
+  // choosing to prioritize the sanity of our API semantics over the optimal
+  // scheduling.
+  if (!mHasPendingEventsPromisedIdleEvent &&
+      (!idleDeadline || idleDeadline < TimeStamp::Now())) {
+    return TimeStamp();
+  }
+  if (mHasPendingEventsPromisedIdleEvent && !idleDeadline) {
+    // If HasPendingEvents() has been called and it has returned true, but we're
+    // no longer in the idle period, we must return a valid timestamp to pretend
+    // that we are still in the idle period.
+    return TimeStamp::Now();
+  }
+  return idleDeadline;
+}
+
 EventQueuePriority PrioritizedEventQueue::SelectQueue(
     bool aUpdateState, const MutexAutoLock& aProofOfLock) {
   size_t inputCount = mInputQueue->Count(aProofOfLock);
 
   if (aUpdateState && mInputQueueState == STATE_ENABLED &&
       mInputHandlingStartTime.IsNull() && inputCount > 0) {
     mInputHandlingStartTime =
         InputEventStatistics::Get().GetInputHandlingStartTime(inputCount);
@@ -149,21 +203,23 @@ already_AddRefed<nsIRunnable> Prioritize
 #ifndef RELEASE_OR_BETA
   // Clear mNextIdleDeadline so that it is possible to determine that
   // we're running an idle runnable in ProcessNextEvent.
   *mNextIdleDeadline = TimeStamp();
 #endif
 
   EventQueuePriority queue = SelectQueue(true, aProofOfLock);
   auto guard = MakeScopeExit([&] {
-    mIdlePeriodState.ForgetPendingTaskGuarantee();
+    mHasPendingEventsPromisedIdleEvent = false;
     if (queue != EventQueuePriority::Idle &&
         queue != EventQueuePriority::DeferredTimers) {
-      MutexAutoUnlock unlock(*mMutex);
-      mIdlePeriodState.FlagNotIdle(unlock);
+      EnsureIsActive();
+      if (mIdleToken && mIdleToken < TimeStamp::Now()) {
+        ClearIdleToken();
+      }
     }
   });
 
   if (aPriority) {
     *aPriority = queue;
   }
 
   if (queue == EventQueuePriority::High) {
@@ -194,29 +250,40 @@ already_AddRefed<nsIRunnable> Prioritize
   }
 
   // If we get here, then all queues except deferredtimers and idle are empty.
   MOZ_ASSERT(queue == EventQueuePriority::Idle ||
              queue == EventQueuePriority::DeferredTimers);
 
   if (mIdleQueue->IsEmpty(aProofOfLock) &&
       mDeferredTimersQueue->IsEmpty(aProofOfLock)) {
-    MutexAutoUnlock unlock(*mMutex);
-    mIdlePeriodState.RanOutOfTasks(unlock);
+    MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent);
+    EnsureIsPaused();
+    ClearIdleToken();
     return nullptr;
   }
 
-  TimeStamp idleDeadline;
-  {
-    // Scope for MutexAutoUnlock
-    MutexAutoUnlock unlock(*mMutex);
-    idleDeadline = mIdlePeriodState.GetDeadlineForIdleTask(unlock);
+  bool shuttingDown;
+  TimeStamp localIdleDeadline = GetLocalIdleDeadline(shuttingDown);
+  if (!localIdleDeadline) {
+    EnsureIsPaused();
+    ClearIdleToken();
+    return nullptr;
   }
 
+  TimeStamp idleDeadline = mHasPendingEventsPromisedIdleEvent || shuttingDown
+                               ? localIdleDeadline
+                               : GetIdleToken(localIdleDeadline);
   if (!idleDeadline) {
+    EnsureIsPaused();
+
+    // Don't call ClearIdleToken() here, since we may have a pending
+    // request already.
+    MutexAutoUnlock unlock(*mMutex);
+    RequestIdleToken(localIdleDeadline);
     return nullptr;
   }
 
   nsCOMPtr<nsIRunnable> event =
       mDeferredTimersQueue->GetEvent(aPriority, aProofOfLock);
   if (!event) {
     event = mIdleQueue->GetEvent(aPriority, aProofOfLock);
   }
@@ -228,40 +295,42 @@ already_AddRefed<nsIRunnable> Prioritize
 
 #ifndef RELEASE_OR_BETA
     // Store the next idle deadline to be able to determine budget use
     // in ProcessNextEvent.
     *mNextIdleDeadline = idleDeadline;
 #endif
   }
 
+  EnsureIsActive();
   return event.forget();
 }
 
 void PrioritizedEventQueue::DidRunEvent(const MutexAutoLock& aProofOfLock) {
   if (IsEmpty(aProofOfLock)) {
-    // Certainly no more idle tasks.
-    MutexAutoUnlock unlock(*mMutex);
-    mIdlePeriodState.RanOutOfTasks(unlock);
+    if (IsActive()) {
+      SetPaused();
+    }
+    ClearIdleToken();
   }
 }
 
 bool PrioritizedEventQueue::IsEmpty(const MutexAutoLock& aProofOfLock) {
   // Just check IsEmpty() on the sub-queues. Don't bother checking the idle
   // deadline since that only determines whether an idle event is ready or not.
   return mHighQueue->IsEmpty(aProofOfLock) &&
          mInputQueue->IsEmpty(aProofOfLock) &&
          mMediumHighQueue->IsEmpty(aProofOfLock) &&
          mNormalQueue->IsEmpty(aProofOfLock) &&
          mDeferredTimersQueue->IsEmpty(aProofOfLock) &&
          mIdleQueue->IsEmpty(aProofOfLock);
 }
 
 bool PrioritizedEventQueue::HasReadyEvent(const MutexAutoLock& aProofOfLock) {
-  mIdlePeriodState.ForgetPendingTaskGuarantee();
+  mHasPendingEventsPromisedIdleEvent = false;
 
   EventQueuePriority queue = SelectQueue(false, aProofOfLock);
 
   if (queue == EventQueuePriority::High) {
     return mHighQueue->HasReadyEvent(aProofOfLock);
   } else if (queue == EventQueuePriority::Input) {
     return mInputQueue->HasReadyEvent(aProofOfLock);
   } else if (queue == EventQueuePriority::MediumHigh) {
@@ -275,25 +344,27 @@ bool PrioritizedEventQueue::HasReadyEven
 
   // If we get here, then both the high and normal queues are empty.
 
   if (mDeferredTimersQueue->IsEmpty(aProofOfLock) &&
       mIdleQueue->IsEmpty(aProofOfLock)) {
     return false;
   }
 
-  TimeStamp idleDeadline;
-  {
-    MutexAutoUnlock unlock(*mMutex);
-    idleDeadline = mIdlePeriodState.PeekIdleDeadline(unlock);
-  }
-  if (idleDeadline && (mDeferredTimersQueue->HasReadyEvent(aProofOfLock) ||
-                       mIdleQueue->HasReadyEvent(aProofOfLock))) {
-    mIdlePeriodState.EnforcePendingTaskGuarantee();
-    return true;
+  bool shuttingDown;
+  TimeStamp localIdleDeadline = GetLocalIdleDeadline(shuttingDown);
+  if (localIdleDeadline) {
+    TimeStamp idleDeadline = mHasPendingEventsPromisedIdleEvent || shuttingDown
+                                 ? localIdleDeadline
+                                 : GetIdleToken(localIdleDeadline);
+    if (idleDeadline && (mDeferredTimersQueue->HasReadyEvent(aProofOfLock) ||
+                         mIdleQueue->HasReadyEvent(aProofOfLock))) {
+      mHasPendingEventsPromisedIdleEvent = true;
+      return true;
+    }
   }
 
   return false;
 }
 
 bool PrioritizedEventQueue::HasPendingHighPriorityEvents(
     const MutexAutoLock& aProofOfLock) {
   return !mHighQueue->IsEmpty(aProofOfLock);
@@ -325,8 +396,93 @@ void PrioritizedEventQueue::SuspendInput
   mInputQueueState = STATE_SUSPEND;
 }
 
 void PrioritizedEventQueue::ResumeInputEventPrioritization(
     const MutexAutoLock& aProofOfLock) {
   MOZ_ASSERT(mInputQueueState == STATE_SUSPEND);
   mInputQueueState = STATE_ENABLED;
 }
+
+mozilla::TimeStamp PrioritizedEventQueue::GetIdleToken(
+    TimeStamp aLocalIdlePeriodHint) {
+  if (XRE_IsParentProcess()) {
+    return aLocalIdlePeriodHint;
+  }
+  if (mIdleToken) {
+    TimeStamp now = TimeStamp::Now();
+    if (mIdleToken < now) {
+      ClearIdleToken();
+      return mIdleToken;
+    }
+    return mIdleToken < aLocalIdlePeriodHint ? mIdleToken
+                                             : aLocalIdlePeriodHint;
+  }
+  return TimeStamp();
+}
+
+void PrioritizedEventQueue::RequestIdleToken(TimeStamp aLocalIdlePeriodHint) {
+  MOZ_ASSERT(!mActive);
+
+  if (!mIdleSchedulerInitialized) {
+    mIdleSchedulerInitialized = true;
+    if (StaticPrefs::idle_period_cross_process_scheduling() &&
+        XRE_IsContentProcess() && NS_IsMainThread() &&
+        // Disable when recording/replaying, as IdleSchedulerChild uses mutable
+        // shared memory which needs special handling.
+        !recordreplay::IsRecordingOrReplaying()) {
+      // For now cross-process idle scheduler is supported only on the main
+      // threads of the child processes.
+      mIdleScheduler = ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
+      if (mIdleScheduler) {
+        mIdleScheduler->Init(this);
+      }
+    }
+  }
+
+  if (mIdleScheduler && !mIdleRequestId) {
+    TimeStamp now = TimeStamp::Now();
+    if (aLocalIdlePeriodHint <= now) {
+      return;
+    }
+
+    mIdleRequestId = ++sIdleRequestCounter;
+    mIdleScheduler->SendRequestIdleTime(mIdleRequestId,
+                                        aLocalIdlePeriodHint - now);
+  }
+}
+
+void PrioritizedEventQueue::SetIdleToken(uint64_t aId, TimeDuration aDuration) {
+  if (mIdleRequestId == aId) {
+    mIdleToken = TimeStamp::Now() + aDuration;
+  }
+}
+
+void PrioritizedEventQueue::SetActive() {
+  MOZ_ASSERT(!mActive);
+  if (mIdleScheduler) {
+    mIdleScheduler->SetActive();
+  }
+  mActive = true;
+}
+
+void PrioritizedEventQueue::SetPaused() {
+  MOZ_ASSERT(mActive);
+  if (mIdleScheduler && mIdleScheduler->SetPaused()) {
+    MutexAutoUnlock unlock(*mMutex);
+    // We may have gotten a free cpu core for running idle tasks.
+    // We don't try to catch the case when there are prioritized processes
+    // running.
+    mIdleScheduler->SendSchedule();
+  }
+  mActive = false;
+}
+
+void PrioritizedEventQueue::ClearIdleToken() {
+  if (mIdleRequestId) {
+    if (mIdleScheduler) {
+      MutexAutoUnlock unlock(*mMutex);
+      mIdleScheduler->SendIdleTimeUsed(mIdleRequestId);
+    }
+    mIdleRequestId = 0;
+    mIdleToken = TimeStamp();
+  }
+}
--- a/xpcom/threads/PrioritizedEventQueue.h
+++ b/xpcom/threads/PrioritizedEventQueue.h
@@ -4,23 +4,22 @@
  * 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 mozilla_PrioritizedEventQueue_h
 #define mozilla_PrioritizedEventQueue_h
 
 #include "mozilla/AbstractEventQueue.h"
 #include "mozilla/EventQueue.h"
-#include "mozilla/IdlePeriodState.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/UniquePtr.h"
 #include "nsCOMPtr.h"
+#include "nsIIdlePeriod.h"
 
-class nsIIdlePeriod;
 class nsIRunnable;
 
 namespace mozilla {
 namespace ipc {
 class IdleSchedulerChild;
 }
 
 // This AbstractEventQueue implementation has one queue for each
@@ -39,17 +38,17 @@ class IdleSchedulerChild;
 //   and normal-priority events available, we interleave popping from the
 //   normal and high queues.
 // - We do not select events from the idle queue if the current idle period
 //   is almost over.
 class PrioritizedEventQueue final : public AbstractEventQueue {
  public:
   static const bool SupportsPrioritization = true;
 
-  explicit PrioritizedEventQueue(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod);
+  explicit PrioritizedEventQueue(already_AddRefed<nsIIdlePeriod> aIdlePeriod);
 
   virtual ~PrioritizedEventQueue();
 
   void PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
                 EventQueuePriority aPriority,
                 const MutexAutoLock& aProofOfLock) final;
   already_AddRefed<nsIRunnable> GetEvent(
       EventQueuePriority* aPriority, const MutexAutoLock& aProofOfLock) final;
@@ -86,25 +85,68 @@ class PrioritizedEventQueue final : publ
 
     n += mHighQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mInputQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mMediumHighQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mNormalQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mDeferredTimersQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mIdleQueue->SizeOfIncludingThis(aMallocSizeOf);
 
-    n += mIdlePeriodState.SizeOfExcludingThis(aMallocSizeOf);
+    if (mIdlePeriod) {
+      n += aMallocSizeOf(mIdlePeriod);
+    }
 
     return n;
   }
 
+  void SetIdleToken(uint64_t aId, TimeDuration aDuration);
+
+  bool IsActive() { return mActive; }
+
+  void EnsureIsActive() {
+    if (!mActive) {
+      SetActive();
+    }
+  }
+
+  void EnsureIsPaused() {
+    if (mActive) {
+      SetPaused();
+    }
+  }
+
  private:
   EventQueuePriority SelectQueue(bool aUpdateState,
                                  const MutexAutoLock& aProofOfLock);
 
+  // Returns a null TimeStamp if we're not in the idle period.
+  mozilla::TimeStamp GetLocalIdleDeadline(bool& aShuttingDown);
+
+  // SetActive should be called when the event queue is running any type of
+  // tasks.
+  void SetActive();
+  // SetPaused should be called once the event queue doesn't have more
+  // tasks to process, or is waiting for the idle token.
+  void SetPaused();
+
+  // Gets the idle token, which is the end time of the idle period.
+  TimeStamp GetIdleToken(TimeStamp aLocalIdlePeriodHint);
+
+  // In case of child processes, requests idle time from the cross-process
+  // idle scheduler.
+  void RequestIdleToken(TimeStamp aLocalIdlePeriodHint);
+
+  // Returns true if the event queue either is waiting for an idle token
+  // from the idle scheduler or has one.
+  bool HasIdleRequest() { return mIdleRequestId != 0; }
+
+  // Mark that the event queue doesn't have idle time to use, nor is expecting
+  // to get idle token from the idle scheduler.
+  void ClearIdleToken();
+
   UniquePtr<EventQueue> mHighQueue;
   UniquePtr<EventQueue> mInputQueue;
   UniquePtr<EventQueue> mMediumHighQueue;
   UniquePtr<EventQueue> mNormalQueue;
   UniquePtr<EventQueue> mDeferredTimersQueue;
   UniquePtr<EventQueue> mIdleQueue;
 
   // We need to drop the queue mutex when checking the idle deadline, so we keep
@@ -118,25 +160,48 @@ class PrioritizedEventQueue final : publ
 #endif
 
   // Try to process one high priority runnable after each normal
   // priority runnable. This gives the processing model HTML spec has for
   // 'Update the rendering' in the case only vsync messages are in the
   // secondary queue and prevents starving the normal queue.
   bool mProcessHighPriorityQueue = false;
 
+  // mIdlePeriod keeps track of the current idle period. If at any
+  // time the main event queue is empty, calling
+  // mIdlePeriod->GetIdlePeriodHint() will give an estimate of when
+  // the current idle period will end.
+  nsCOMPtr<nsIIdlePeriod> mIdlePeriod;
+
+  // Set to true if HasPendingEvents() has been called and returned true because
+  // of a pending idle event.  This is used to remember to return that idle
+  // event from GetIdleEvent() to ensure that HasPendingEvents() never lies.
+  bool mHasPendingEventsPromisedIdleEvent = false;
+
   TimeStamp mInputHandlingStartTime;
 
   enum InputEventQueueState {
     STATE_DISABLED,
     STATE_FLUSHING,
     STATE_SUSPEND,
     STATE_ENABLED
   };
   InputEventQueueState mInputQueueState = STATE_DISABLED;
 
-  // Tracking of our idle state of various sorts.
-  IdlePeriodState mIdlePeriodState;
+  // If non-null, tells the end time of the idle period.
+  // Idle period starts when we get idle token from the parent process and
+  // ends when either there are no runnables in the event queues or
+  // mIdleToken < TimeStamp::Now()
+  TimeStamp mIdleToken;
+
+  // The id of the last idle request to the cross-process idle scheduler.
+  uint64_t mIdleRequestId = 0;
+
+  RefPtr<ipc::IdleSchedulerChild> mIdleScheduler;
+  bool mIdleSchedulerInitialized = false;
+
+  // mActive tells whether the event queue is running tasks.
+  bool mActive = true;
 };
 
 }  // namespace mozilla
 
 #endif  // mozilla_PrioritizedEventQueue_h
--- a/xpcom/threads/moz.build
+++ b/xpcom/threads/moz.build
@@ -43,17 +43,16 @@ EXPORTS.mozilla += [
     'AbstractEventQueue.h',
     'AbstractThread.h',
     'BlockingResourceBase.h',
     'CondVar.h',
     'CPUUsageWatcher.h',
     'DataMutex.h',
     'DeadlockDetector.h',
     'EventQueue.h',
-    'IdlePeriodState.h',
     'IdleTaskRunner.h',
     'LazyIdleThread.h',
     'MainThreadIdlePeriod.h',
     'Monitor.h',
     'MozPromise.h',
     'MozPromiseInlines.h',
     'Mutex.h',
     'PerformanceCounter.h',
@@ -82,17 +81,16 @@ SOURCES += [
     'ThreadDelay.cpp',
 ]
 
 UNIFIED_SOURCES += [
     'AbstractThread.cpp',
     'BlockingResourceBase.cpp',
     'CPUUsageWatcher.cpp',
     'EventQueue.cpp',
-    'IdlePeriodState.cpp',
     'InputEventStatistics.cpp',
     'LazyIdleThread.cpp',
     'MainThreadIdlePeriod.cpp',
     'nsEnvironment.cpp',
     'nsMemoryPressure.cpp',
     'nsProcessCommon.cpp',
     'nsProxyRelease.cpp',
     'nsThread.cpp',