diff --git a/xpcom/threads/AbstractEventQueue.h b/xpcom/threads/AbstractEventQueue.h --- a/xpcom/threads/AbstractEventQueue.h +++ b/xpcom/threads/AbstractEventQueue.h @@ -1,74 +1,76 @@ /* -*- 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_AbstractEventQueue_h #define mozilla_AbstractEventQueue_h #include "mozilla/AlreadyAddRefed.h" #include "mozilla/Mutex.h" class nsIRunnable; namespace mozilla { enum class EventPriority { High, Input, Normal, - Idle + Idle, + + Count }; // AbstractEventQueue is an abstract base class for all our unsynchronized event // queue implementations: // - EventQueue: A queue of runnables. Used for non-main threads. // - PrioritizedEventQueue: Contains a queue for each priority level. // Has heuristics to decide which queue to pop from. Events are // pushed into the queue corresponding to their priority. // Used for the main thread. // // Since AbstractEventQueue implementations are unsynchronized, they should be // wrapped in an outer SynchronizedEventQueue implementation (like // ThreadEventQueue). class AbstractEventQueue { public: // Add an event to the end of the queue. Implementors are free to use // aPriority however they wish. They may ignore it if the runnable has its own // intrinsic priority (via nsIRunnablePriority). virtual void PutEvent(already_AddRefed&& aEvent, EventPriority aPriority, const MutexAutoLock& aProofOfLock) = 0; // Get an event from the front of the queue. aPriority is an out param. If the // implementation supports priorities, then this should be the same priority // that the event was pushed with. aPriority may be null. This should return // null if the queue is non-empty but the event in front is not ready to run. virtual already_AddRefed GetEvent(EventPriority* aPriority, const MutexAutoLock& aProofOfLock) = 0; // Returns true if the queue is empty. Implies !HasReadyEvent(). virtual bool IsEmpty(const MutexAutoLock& aProofOfLock) = 0; // Returns true if the queue is non-empty and if the event in front is ready // to run. Implies !IsEmpty(). This should return true iff GetEvent returns a // non-null value. virtual bool HasReadyEvent(const MutexAutoLock& aProofOfLock) = 0; // Returns the number of events in the queue. virtual size_t Count(const MutexAutoLock& aProofOfLock) const = 0; virtual void EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock) = 0; virtual void FlushInputEventPrioritization(const MutexAutoLock& aProofOfLock) = 0; virtual void SuspendInputEventPrioritization(const MutexAutoLock& aProofOfLock) = 0; virtual void ResumeInputEventPrioritization(const MutexAutoLock& aProofOfLock) = 0; virtual ~AbstractEventQueue() {} }; } // namespace mozilla #endif // mozilla_AbstractEventQueue_h diff --git a/xpcom/threads/EventQueue.cpp b/xpcom/threads/EventQueue.cpp --- a/xpcom/threads/EventQueue.cpp +++ b/xpcom/threads/EventQueue.cpp @@ -1,68 +1,68 @@ /* -*- 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/EventQueue.h" #include "nsIRunnable.h" using namespace mozilla; -EventQueue::EventQueue() +EventQueue::EventQueue(EventPriority aPriority) { } void EventQueue::PutEvent(already_AddRefed&& aEvent, EventPriority aPriority, const MutexAutoLock& aProofOfLock) { nsCOMPtr event(aEvent); mQueue.Push(Move(event)); } already_AddRefed EventQueue::GetEvent(EventPriority* aPriority, const MutexAutoLock& aProofOfLock) { if (mQueue.IsEmpty()) { return nullptr; } if (aPriority) { *aPriority = EventPriority::Normal; } nsCOMPtr result = mQueue.Pop(); return result.forget(); } bool EventQueue::IsEmpty(const MutexAutoLock& aProofOfLock) { return mQueue.IsEmpty(); } bool EventQueue::HasReadyEvent(const MutexAutoLock& aProofOfLock) { return !IsEmpty(aProofOfLock); } already_AddRefed EventQueue::PeekEvent(const MutexAutoLock& aProofOfLock) { if (mQueue.IsEmpty()) { return nullptr; } nsCOMPtr result = mQueue.FirstElement(); return result.forget(); } size_t EventQueue::Count(const MutexAutoLock& aProofOfLock) const { return mQueue.Count(); } diff --git a/xpcom/threads/EventQueue.h b/xpcom/threads/EventQueue.h --- a/xpcom/threads/EventQueue.h +++ b/xpcom/threads/EventQueue.h @@ -1,46 +1,47 @@ /* -*- 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_EventQueue_h #define mozilla_EventQueue_h #include "mozilla/AbstractEventQueue.h" #include "mozilla/Queue.h" #include "nsCOMPtr.h" class nsIRunnable; namespace mozilla { class EventQueue final : public AbstractEventQueue { public: - EventQueue(); + EventQueue() {} + explicit EventQueue(EventPriority aPriority); void PutEvent(already_AddRefed&& aEvent, EventPriority aPriority, const MutexAutoLock& aProofOfLock) final; already_AddRefed GetEvent(EventPriority* aPriority, const MutexAutoLock& aProofOfLock) final; bool IsEmpty(const MutexAutoLock& aProofOfLock) final; bool HasReadyEvent(const MutexAutoLock& aProofOfLock) final; size_t Count(const MutexAutoLock& aProofOfLock) const final; already_AddRefed PeekEvent(const MutexAutoLock& aProofOfLock); void EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock) final {} void FlushInputEventPrioritization(const MutexAutoLock& aProofOfLock) final {} void SuspendInputEventPrioritization(const MutexAutoLock& aProofOfLock) final {} void ResumeInputEventPrioritization(const MutexAutoLock& aProofOfLock) final {} private: mozilla::Queue> mQueue; }; } // namespace mozilla #endif // mozilla_EventQueue_h diff --git a/xpcom/threads/LabeledEventQueue.cpp b/xpcom/threads/LabeledEventQueue.cpp --- a/xpcom/threads/LabeledEventQueue.cpp +++ b/xpcom/threads/LabeledEventQueue.cpp @@ -1,287 +1,289 @@ /* -*- 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 "LabeledEventQueue.h" #include "mozilla/dom/TabChild.h" #include "mozilla/dom/TabGroup.h" #include "mozilla/Scheduler.h" #include "mozilla/SchedulerGroup.h" #include "nsQueryObject.h" using namespace mozilla::dom; +using EpochQueueEntry = SchedulerGroup::EpochQueueEntry; + LinkedList* LabeledEventQueue::sSchedulerGroups; size_t LabeledEventQueue::sLabeledEventQueueCount; SchedulerGroup* LabeledEventQueue::sCurrentSchedulerGroup; -LabeledEventQueue::LabeledEventQueue() +LabeledEventQueue::LabeledEventQueue(EventPriority aPriority) + : mPriority(aPriority) { // LabeledEventQueue should only be used by one consumer since it uses a // single static sSchedulerGroups field. It's hard to assert this, though, so // we assert NS_IsMainThread(), which is a reasonable proxy. MOZ_ASSERT(NS_IsMainThread()); if (sLabeledEventQueueCount++ == 0) { sSchedulerGroups = new LinkedList(); } } LabeledEventQueue::~LabeledEventQueue() { if (--sLabeledEventQueueCount == 0) { delete sSchedulerGroups; sSchedulerGroups = nullptr; } } static SchedulerGroup* GetSchedulerGroup(nsIRunnable* aEvent) { RefPtr groupRunnable = do_QueryObject(aEvent); if (!groupRunnable) { // It's not labeled. return nullptr; } return groupRunnable->Group(); } static bool IsReadyToRun(nsIRunnable* aEvent, SchedulerGroup* aEventGroup) { if (!Scheduler::AnyEventRunning()) { return true; } if (Scheduler::UnlabeledEventRunning()) { return false; } if (aEventGroup) { return !aEventGroup->IsRunning(); } nsCOMPtr labelable = do_QueryInterface(aEvent); if (!labelable) { return false; } return labelable->IsReadyToRun(); } void LabeledEventQueue::PutEvent(already_AddRefed&& aEvent, EventPriority aPriority, const MutexAutoLock& aProofOfLock) { + MOZ_ASSERT(aPriority == mPriority); + nsCOMPtr event(aEvent); MOZ_ASSERT(event.get()); SchedulerGroup* group = GetSchedulerGroup(event); bool isLabeled = !!group; // Create a new epoch if necessary. Epoch* epoch; if (mEpochs.IsEmpty()) { epoch = &mEpochs.Push(Epoch::First(isLabeled)); } else { Epoch& lastEpoch = mEpochs.LastElement(); if (lastEpoch.IsLabeled() != isLabeled) { epoch = &mEpochs.Push(lastEpoch.NextEpoch(isLabeled)); } else { epoch = &lastEpoch; } } mNumEvents++; epoch->mNumEvents++; - RunnableEpochQueue* queue = isLabeled ? mLabeled.LookupOrAdd(group) : &mUnlabeled; - queue->Push(QueueEntry(event.forget(), epoch->mEpochNumber)); + RunnableEpochQueue& queue = isLabeled ? group->GetQueue(aPriority) : mUnlabeled; + queue.Push(EpochQueueEntry(event.forget(), epoch->mEpochNumber)); if (group && group->EnqueueEvent() == SchedulerGroup::NewlyQueued) { // This group didn't have any events before. Add it to the // sSchedulerGroups list. MOZ_ASSERT(!group->isInList()); sSchedulerGroups->insertBack(group); if (!sCurrentSchedulerGroup) { sCurrentSchedulerGroup = group; } } } void LabeledEventQueue::PopEpoch() { Epoch& epoch = mEpochs.FirstElement(); MOZ_ASSERT(epoch.mNumEvents > 0); if (epoch.mNumEvents == 1) { mEpochs.Pop(); } else { epoch.mNumEvents--; } mNumEvents--; } // Returns the next SchedulerGroup after |aGroup| in sSchedulerGroups. Wraps // around to the beginning of the list when we hit the end. /* static */ SchedulerGroup* LabeledEventQueue::NextSchedulerGroup(SchedulerGroup* aGroup) { SchedulerGroup* result = aGroup->getNext(); if (!result) { result = sSchedulerGroups->getFirst(); } return result; } already_AddRefed LabeledEventQueue::GetEvent(EventPriority* aPriority, const MutexAutoLock& aProofOfLock) { if (mEpochs.IsEmpty()) { return nullptr; } Epoch epoch = mEpochs.FirstElement(); if (!epoch.IsLabeled()) { - QueueEntry& first = mUnlabeled.FirstElement(); + EpochQueueEntry& first = mUnlabeled.FirstElement(); if (!IsReadyToRun(first.mRunnable, nullptr)) { return nullptr; } PopEpoch(); - QueueEntry entry = mUnlabeled.Pop(); + EpochQueueEntry entry = mUnlabeled.Pop(); MOZ_ASSERT(entry.mEpochNumber == epoch.mEpochNumber); MOZ_ASSERT(entry.mRunnable.get()); return entry.mRunnable.forget(); } if (!sCurrentSchedulerGroup) { return nullptr; } // Move active tabs to the front of the queue. The mAvoidActiveTabCount field // prevents us from preferentially processing events from active tabs twice in // a row. This scheme is designed to prevent starvation. if (TabChild::HasActiveTabs() && mAvoidActiveTabCount <= 0) { for (auto iter = TabChild::GetActiveTabs().ConstIter(); !iter.Done(); iter.Next()) { SchedulerGroup* group = iter.Get()->GetKey()->TabGroup(); if (!group->isInList() || group == sCurrentSchedulerGroup) { continue; } // For each active tab we move to the front of the queue, we have to // process two SchedulerGroups (the active tab and another one, presumably // a background group) before we prioritize active tabs again. mAvoidActiveTabCount += 2; // We move |group| right before sCurrentSchedulerGroup and then set // sCurrentSchedulerGroup to group. MOZ_ASSERT(group != sCurrentSchedulerGroup); group->removeFrom(*sSchedulerGroups); sCurrentSchedulerGroup->setPrevious(group); sCurrentSchedulerGroup = group; } } // Iterate over each SchedulerGroup once, starting at sCurrentSchedulerGroup. SchedulerGroup* firstGroup = sCurrentSchedulerGroup; SchedulerGroup* group = firstGroup; do { mAvoidActiveTabCount--; - auto queueEntry = mLabeled.Lookup(group); - if (!queueEntry) { + RunnableEpochQueue& queue = group->GetQueue(mPriority); + + if (queue.IsEmpty()) { // This can happen if |group| is in a different LabeledEventQueue than |this|. group = NextSchedulerGroup(group); continue; } - RunnableEpochQueue* queue = queueEntry.Data(); - MOZ_ASSERT(!queue->IsEmpty()); - - QueueEntry& first = queue->FirstElement(); + EpochQueueEntry& first = queue.FirstElement(); if (first.mEpochNumber == epoch.mEpochNumber && IsReadyToRun(first.mRunnable, group)) { sCurrentSchedulerGroup = NextSchedulerGroup(group); PopEpoch(); if (group->DequeueEvent() == SchedulerGroup::NoLongerQueued) { // Now we can take group out of sSchedulerGroups. if (sCurrentSchedulerGroup == group) { // Since we changed sCurrentSchedulerGroup above, we'll only get here // if |group| was the only element in sSchedulerGroups. In that case // set sCurrentSchedulerGroup to null. MOZ_ASSERT(group->getNext() == nullptr); MOZ_ASSERT(group->getPrevious() == nullptr); sCurrentSchedulerGroup = nullptr; } group->removeFrom(*sSchedulerGroups); } - QueueEntry entry = queue->Pop(); - if (queue->IsEmpty()) { - queueEntry.Remove(); - } + EpochQueueEntry entry = queue.Pop(); return entry.mRunnable.forget(); } group = NextSchedulerGroup(group); } while (group != firstGroup); return nullptr; } bool LabeledEventQueue::IsEmpty(const MutexAutoLock& aProofOfLock) { return mEpochs.IsEmpty(); } size_t LabeledEventQueue::Count(const MutexAutoLock& aProofOfLock) const { return mNumEvents; } bool LabeledEventQueue::HasReadyEvent(const MutexAutoLock& aProofOfLock) { if (mEpochs.IsEmpty()) { return false; } Epoch& frontEpoch = mEpochs.FirstElement(); if (!frontEpoch.IsLabeled()) { - QueueEntry& entry = mUnlabeled.FirstElement(); + EpochQueueEntry& entry = mUnlabeled.FirstElement(); return IsReadyToRun(entry.mRunnable, nullptr); } - // Go through the labeled queues and look for one whose head is from the - // current epoch and is allowed to run. + // Go through the scheduler groups and look for one that has events + // for the priority of this labeled queue that is in the current + // epoch and is allowed to run. uintptr_t currentEpoch = frontEpoch.mEpochNumber; - for (auto iter = mLabeled.Iter(); !iter.Done(); iter.Next()) { - SchedulerGroup* key = iter.Key(); - RunnableEpochQueue* queue = iter.Data(); - MOZ_ASSERT(!queue->IsEmpty()); + for (SchedulerGroup* group : *sSchedulerGroups) { + RunnableEpochQueue& queue = group->GetQueue(mPriority); + if (queue.IsEmpty()) { + continue; + } - QueueEntry& entry = queue->FirstElement(); + EpochQueueEntry& entry = queue.FirstElement(); if (entry.mEpochNumber != currentEpoch) { continue; } - if (IsReadyToRun(entry.mRunnable, key)) { + if (IsReadyToRun(entry.mRunnable, group)) { return true; } } return false; } diff --git a/xpcom/threads/LabeledEventQueue.h b/xpcom/threads/LabeledEventQueue.h --- a/xpcom/threads/LabeledEventQueue.h +++ b/xpcom/threads/LabeledEventQueue.h @@ -1,161 +1,150 @@ /* -*- 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_LabeledEventQueue_h #define mozilla_LabeledEventQueue_h #include #include "mozilla/AbstractEventQueue.h" #include "mozilla/LinkedList.h" +#include "mozilla/SchedulerGroup.h" #include "mozilla/Queue.h" #include "nsClassHashtable.h" #include "nsHashKeys.h" namespace mozilla { class SchedulerGroup; // LabeledEventQueue is actually a set of queues. There is one queue for each // SchedulerGroup, as well as one queue for unlabeled events (those with no // associated SchedulerGroup). When an event is added to a LabeledEventQueue, we // query its SchedulerGroup and then add it to the appropriate queue. When an // event is fetched, we heuristically pick a SchedulerGroup and return an event // from its queue. Ideally the heuristic should give precedence to // SchedulerGroups corresponding to the foreground tabs. The correctness of this // data structure relies on the invariant that events from different // SchedulerGroups cannot affect each other. class LabeledEventQueue final : public AbstractEventQueue { public: - LabeledEventQueue(); + explicit LabeledEventQueue(EventPriority aPriority); ~LabeledEventQueue(); void PutEvent(already_AddRefed&& aEvent, EventPriority aPriority, const MutexAutoLock& aProofOfLock) final; already_AddRefed GetEvent(EventPriority* aPriority, const MutexAutoLock& aProofOfLock) final; bool IsEmpty(const MutexAutoLock& aProofOfLock) final; size_t Count(const MutexAutoLock& aProofOfLock) const final; bool HasReadyEvent(const MutexAutoLock& aProofOfLock) final; void EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock) final {} void FlushInputEventPrioritization(const MutexAutoLock& aProofOfLock) final {} void SuspendInputEventPrioritization(const MutexAutoLock& aProofOfLock) final {} void ResumeInputEventPrioritization(const MutexAutoLock& aProofOfLock) final {} private: // The basic problem here is to keep track of the ordering relationships // between events. As long as there are only labeled events, there can be one // queue per SchedulerGroup. However, if an unlabeled event is pushed, we must // remember that it should run after all the labeled events currently in the // queue. To do this, the queues are arranged in "epochs". Each time the tail // of the queue transitions from labeled to unlabeled (or from unlabeled to // labeled) a new epoch starts. Events within different epochs are ordered // according to which epoch happened first. Within a labeled epoch, there is // one queue per SchedulerGroup. So events from different SchedulerGroups // within the same epoch are unordered with respect to each other. Within an // unlabeled epoch, there is a single queue that orders all the unlabeled // events. // // The data structures we use are: // 1. A queue of epochs. For each epoch, we store its epoch number. This number // is odd for labeled epochs and even for unlabeled epochs. We also store the // number of events in the epoch. // 2. A single queue for all unlabeled events. For each event in the queue, we // store the runnable as well as the epoch number. // 3. For labeled events, one queue for each SchedulerGroup. Each element in // these queues also keeps track of the epoch it belongs to. // // To push an event, we see if we can remain in the same epoch or if we have // to start a new one. If we have to start a new one, we push onto the epoch // queue. Then, based on whether the event is labeled or not, we push the // runnable and the epoch number into the appopriate queue. // // To pop an event, we look at the epoch at the front of the epoch queue. If // it is unlabeled, then we pop the first event in the unlabeled queue. If it // is labeled, we can pop from any of the SchedulerGroup queues. Then we // decrement the number of events in the current epoch. If this number reaches // zero, we pop from the epoch queue. - struct QueueEntry - { - nsCOMPtr mRunnable; - uintptr_t mEpochNumber; - - QueueEntry(already_AddRefed aRunnable, uintptr_t aEpoch) - : mRunnable(aRunnable) - , mEpochNumber(aEpoch) - {} - }; - struct Epoch { static Epoch First(bool aIsLabeled) { // Odd numbers are labeled, even are unlabeled. uintptr_t number = aIsLabeled ? 1 : 0; return Epoch(number, aIsLabeled); } static bool EpochNumberIsLabeled(uintptr_t aEpochNumber) { // Odd numbers are labeled, even are unlabeled. return (aEpochNumber & 1) ? true : false; } uintptr_t mEpochNumber; size_t mNumEvents; Epoch(uintptr_t aEpochNumber, bool aIsLabeled) : mEpochNumber(aEpochNumber) , mNumEvents(0) { MOZ_ASSERT(aIsLabeled == EpochNumberIsLabeled(aEpochNumber)); } bool IsLabeled() const { return EpochNumberIsLabeled(mEpochNumber); } Epoch NextEpoch(bool aIsLabeled) const { MOZ_ASSERT(aIsLabeled == !IsLabeled()); return Epoch(mEpochNumber + 1, aIsLabeled); } }; void PopEpoch(); static SchedulerGroup* NextSchedulerGroup(SchedulerGroup* aGroup); - using RunnableEpochQueue = Queue; - using LabeledMap = nsClassHashtable, RunnableEpochQueue>; + using RunnableEpochQueue = SchedulerGroup::RunnableEpochQueue; using EpochQueue = Queue; // List of SchedulerGroups that might have events. This is static, so it // covers all LabeledEventQueues. If a SchedulerGroup is in this list, it may // not have an event in *this* LabeledEventQueue (although it will have an // event in *some* LabeledEventQueue). sCurrentSchedulerGroup cycles through // the elements of sSchedulerGroups in order. static LinkedList* sSchedulerGroups; static size_t sLabeledEventQueueCount; static SchedulerGroup* sCurrentSchedulerGroup; - LabeledMap mLabeled; RunnableEpochQueue mUnlabeled; EpochQueue mEpochs; size_t mNumEvents = 0; // Number of SchedulerGroups that must be processed before we prioritize an // active tab. This field is designed to guarantee a 1:1 interleaving between // foreground and background SchedulerGroups. For details, see its usage in // LabeledEventQueue.cpp. int64_t mAvoidActiveTabCount = 0; + EventPriority mPriority; }; } // namespace mozilla #endif // mozilla_LabeledEventQueue_h diff --git a/xpcom/threads/MainThreadQueue.h b/xpcom/threads/MainThreadQueue.h --- a/xpcom/threads/MainThreadQueue.h +++ b/xpcom/threads/MainThreadQueue.h @@ -1,53 +1,53 @@ /* -*- 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_MainThreadQueue_h #define mozilla_MainThreadQueue_h #include "mozilla/Attributes.h" #include "mozilla/EventQueue.h" #include "mozilla/RefPtr.h" #include "mozilla/UniquePtr.h" #include "nsThread.h" #include "PrioritizedEventQueue.h" namespace mozilla { template inline already_AddRefed CreateMainThread(nsIIdlePeriod* aIdlePeriod, SynchronizedQueueT** aSynchronizedQueue = nullptr) { using MainThreadQueueT = PrioritizedEventQueue; auto queue = MakeUnique( - MakeUnique(), - MakeUnique(), - MakeUnique(), - MakeUnique(), + MakeUnique(EventPriority::High), + MakeUnique(EventPriority::Input), + MakeUnique(EventPriority::Normal), + MakeUnique(EventPriority::Idle), do_AddRef(aIdlePeriod)); MainThreadQueueT* prioritized = queue.get(); RefPtr synchronizedQueue = new SynchronizedQueueT(Move(queue)); prioritized->SetMutexRef(synchronizedQueue->MutexRef()); // Setup "main" thread RefPtr mainThread = new nsThread(WrapNotNull(synchronizedQueue), nsThread::MAIN_THREAD, 0); #ifndef RELEASE_OR_BETA prioritized->SetNextIdleDeadlineRef(mainThread->NextIdleDeadlineRef()); #endif if (aSynchronizedQueue) { synchronizedQueue.forget(aSynchronizedQueue); } return mainThread.forget(); } } // namespace mozilla #endif // mozilla_MainThreadQueue_h diff --git a/xpcom/threads/PrioritizedEventQueue.cpp b/xpcom/threads/PrioritizedEventQueue.cpp --- a/xpcom/threads/PrioritizedEventQueue.cpp +++ b/xpcom/threads/PrioritizedEventQueue.cpp @@ -1,351 +1,354 @@ /* -*- 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 "PrioritizedEventQueue.h" #include "mozilla/EventQueue.h" #include "mozilla/ScopeExit.h" #include "nsThreadManager.h" #include "nsXPCOMPrivate.h" // for gXPCOMThreadsShutDown #include "InputEventStatistics.h" using namespace mozilla; template PrioritizedEventQueue::PrioritizedEventQueue(UniquePtr aHighQueue, UniquePtr aInputQueue, UniquePtr aNormalQueue, UniquePtr aIdleQueue, already_AddRefed aIdlePeriod) : mHighQueue(Move(aHighQueue)) , mInputQueue(Move(aInputQueue)) , mNormalQueue(Move(aNormalQueue)) , mIdleQueue(Move(aIdleQueue)) , mIdlePeriod(aIdlePeriod) { static_assert(IsBaseOf::value, "InnerQueueT must be an AbstractEventQueue subclass"); } template void PrioritizedEventQueue::PutEvent(already_AddRefed&& aEvent, EventPriority aPriority, const MutexAutoLock& aProofOfLock) { // Double check the priority with a QI. RefPtr event(aEvent); EventPriority priority = aPriority; if (nsCOMPtr runnablePrio = do_QueryInterface(event)) { uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL; runnablePrio->GetPriority(&prio); if (prio == nsIRunnablePriority::PRIORITY_HIGH) { priority = EventPriority::High; } else if (prio == nsIRunnablePriority::PRIORITY_INPUT) { priority = EventPriority::Input; } } if (priority == EventPriority::Input && mInputQueueState == STATE_DISABLED) { priority = EventPriority::Normal; } switch (priority) { case EventPriority::High: mHighQueue->PutEvent(event.forget(), priority, aProofOfLock); break; case EventPriority::Input: mInputQueue->PutEvent(event.forget(), priority, aProofOfLock); break; case EventPriority::Normal: mNormalQueue->PutEvent(event.forget(), priority, aProofOfLock); break; case EventPriority::Idle: mIdleQueue->PutEvent(event.forget(), priority, aProofOfLock); break; + case EventPriority::Count: + MOZ_CRASH("EventPriority::Count isn't a valid priority"); + break; } } template TimeStamp PrioritizedEventQueue::GetIdleDeadline() { // 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()) { return TimeStamp::Now(); } 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; } template EventPriority PrioritizedEventQueue::SelectQueue(bool aUpdateState, const MutexAutoLock& aProofOfLock) { bool highPending = !mHighQueue->IsEmpty(aProofOfLock); bool normalPending = !mNormalQueue->IsEmpty(aProofOfLock); size_t inputCount = mInputQueue->Count(aProofOfLock); if (aUpdateState && mInputQueueState == STATE_ENABLED && mInputHandlingStartTime.IsNull() && inputCount > 0) { mInputHandlingStartTime = InputEventStatistics::Get() .GetInputHandlingStartTime(inputCount); } // We check the different queues in the following order. The conditions we use // are meant to avoid starvation and to ensure that we don't process an event // at the wrong time. // // HIGH: if mProcessHighPriorityQueue // INPUT: if inputCount > 0 && TimeStamp::Now() > mInputHandlingStartTime // NORMAL: if normalPending // // If we still don't have an event, then we take events from the queues // in the following order: // // HIGH // INPUT // IDLE: if GetIdleDeadline() // // If we don't get an event in this pass, then we return null since no events // are ready. // This variable determines which queue we will take an event from. EventPriority queue; if (mProcessHighPriorityQueue) { queue = EventPriority::High; } else if (inputCount > 0 && (mInputQueueState == STATE_FLUSHING || (mInputQueueState == STATE_ENABLED && !mInputHandlingStartTime.IsNull() && TimeStamp::Now() > mInputHandlingStartTime))) { queue = EventPriority::Input; } else if (normalPending) { MOZ_ASSERT(mInputQueueState != STATE_FLUSHING, "Shouldn't consume normal event when flusing input events"); queue = EventPriority::Normal; } else if (highPending) { queue = EventPriority::High; } else if (inputCount > 0 && mInputQueueState != STATE_SUSPEND) { MOZ_ASSERT(mInputQueueState != STATE_DISABLED, "Shouldn't consume input events when the input queue is disabled"); queue = EventPriority::Input; } else { // We may not actually return an idle event in this case. queue = EventPriority::Idle; } MOZ_ASSERT_IF(queue == EventPriority::Input, mInputQueueState != STATE_DISABLED && mInputQueueState != STATE_SUSPEND); if (aUpdateState) { mProcessHighPriorityQueue = highPending; } return queue; } template already_AddRefed PrioritizedEventQueue::GetEvent(EventPriority* aPriority, const MutexAutoLock& aProofOfLock) { MakeScopeExit([&] { mHasPendingEventsPromisedIdleEvent = false; }); #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 EventPriority queue = SelectQueue(true, aProofOfLock); if (aPriority) { *aPriority = queue; } if (queue == EventPriority::High) { nsCOMPtr event = mHighQueue->GetEvent(aPriority, aProofOfLock); MOZ_ASSERT(event); mInputHandlingStartTime = TimeStamp(); mProcessHighPriorityQueue = false; return event.forget(); } if (queue == EventPriority::Input) { nsCOMPtr event = mInputQueue->GetEvent(aPriority, aProofOfLock); MOZ_ASSERT(event); return event.forget(); } if (queue == EventPriority::Normal) { nsCOMPtr event = mNormalQueue->GetEvent(aPriority, aProofOfLock); return event.forget(); } // If we get here, then all queues except idle are empty. MOZ_ASSERT(queue == EventPriority::Idle); if (mIdleQueue->IsEmpty(aProofOfLock)) { MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent); return nullptr; } TimeStamp idleDeadline = GetIdleDeadline(); if (!idleDeadline) { return nullptr; } nsCOMPtr event = mIdleQueue->GetEvent(aPriority, aProofOfLock); if (event) { nsCOMPtr idleEvent = do_QueryInterface(event); if (idleEvent) { idleEvent->SetDeadline(idleDeadline); } #ifndef RELEASE_OR_BETA // Store the next idle deadline to be able to determine budget use // in ProcessNextEvent. *mNextIdleDeadline = idleDeadline; #endif } return event.forget(); } template 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) && mNormalQueue->IsEmpty(aProofOfLock) && mIdleQueue->IsEmpty(aProofOfLock); } template bool PrioritizedEventQueue::HasReadyEvent(const MutexAutoLock& aProofOfLock) { mHasPendingEventsPromisedIdleEvent = false; EventPriority queue = SelectQueue(false, aProofOfLock); if (queue == EventPriority::High) { return mHighQueue->HasReadyEvent(aProofOfLock); } else if (queue == EventPriority::Input) { return mInputQueue->HasReadyEvent(aProofOfLock); } else if (queue == EventPriority::Normal) { return mNormalQueue->HasReadyEvent(aProofOfLock); } MOZ_ASSERT(queue == EventPriority::Idle); // If we get here, then both the high and normal queues are empty. if (mIdleQueue->IsEmpty(aProofOfLock)) { return false; } TimeStamp idleDeadline = GetIdleDeadline(); if (idleDeadline && mIdleQueue->HasReadyEvent(aProofOfLock)) { mHasPendingEventsPromisedIdleEvent = true; return true; } return false; } template size_t PrioritizedEventQueue::Count(const MutexAutoLock& aProofOfLock) const { MOZ_CRASH("unimplemented"); } template void PrioritizedEventQueue::EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock) { MOZ_ASSERT(mInputQueueState == STATE_DISABLED); mInputQueueState = STATE_ENABLED; mInputHandlingStartTime = TimeStamp(); } template void PrioritizedEventQueue:: FlushInputEventPrioritization(const MutexAutoLock& aProofOfLock) { MOZ_ASSERT(mInputQueueState == STATE_ENABLED || mInputQueueState == STATE_SUSPEND); mInputQueueState = mInputQueueState == STATE_ENABLED ? STATE_FLUSHING : STATE_SUSPEND; } template void PrioritizedEventQueue:: SuspendInputEventPrioritization(const MutexAutoLock& aProofOfLock) { MOZ_ASSERT(mInputQueueState == STATE_ENABLED || mInputQueueState == STATE_FLUSHING); mInputQueueState = STATE_SUSPEND; } template void PrioritizedEventQueue:: ResumeInputEventPrioritization(const MutexAutoLock& aProofOfLock) { MOZ_ASSERT(mInputQueueState == STATE_SUSPEND); mInputQueueState = STATE_ENABLED; } namespace mozilla { template class PrioritizedEventQueue; template class PrioritizedEventQueue; } diff --git a/xpcom/threads/SchedulerGroup.h b/xpcom/threads/SchedulerGroup.h --- a/xpcom/threads/SchedulerGroup.h +++ b/xpcom/threads/SchedulerGroup.h @@ -1,212 +1,233 @@ /* -*- 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_SchedulerGroup_h #define mozilla_SchedulerGroup_h #include "mozilla/AlreadyAddRefed.h" #include "mozilla/LinkedList.h" +#include "mozilla/Queue.h" #include "mozilla/TaskCategory.h" #include "mozilla/ThreadLocal.h" #include "mozilla/TimeStamp.h" #include "nsCOMPtr.h" #include "nsILabelableRunnable.h" #include "nsISupportsImpl.h" #include "nsThreadUtils.h" class nsIEventTarget; class nsIRunnable; class nsISerialEventTarget; namespace mozilla { class AbstractThread; namespace dom { class TabGroup; } #define NS_SCHEDULERGROUPRUNNABLE_IID \ { 0xd31b7420, 0x872b, 0x4cfb, \ { 0xa9, 0xc6, 0xae, 0x4c, 0x0f, 0x06, 0x36, 0x74 } } // The "main thread" in Gecko will soon be a set of cooperatively scheduled // "fibers". Global state in Gecko will be partitioned into a series of "groups" // (with roughly one group per tab). Runnables will be annotated with the set of // groups that they touch. Two runnables may run concurrently on different // fibers as long as they touch different groups. // // A SchedulerGroup is an abstract class to represent a "group". Essentially the // only functionality offered by a SchedulerGroup is the ability to dispatch // runnables to the group. TabGroup, DocGroup, and SystemGroup are the concrete // implementations of SchedulerGroup. class SchedulerGroup : public LinkedListElement { public: SchedulerGroup(); NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING // This method returns true if all members of the "group" are in a // "background" state. virtual bool IsBackground() const { return false; } // This function returns true if it's currently safe to run code associated // with this SchedulerGroup. It will return true either if we're inside an // unlabeled runnable or if we're inside a runnable labeled with this // SchedulerGroup. bool IsSafeToRun() const { return !sTlsValidatingAccess.get() || mIsRunning; } // This function returns true if it's currently safe to run unlabeled code // with no known SchedulerGroup. It will only return true if we're inside an // unlabeled runnable. static bool IsSafeToRunUnlabeled() { return !sTlsValidatingAccess.get(); } // Ensure that it's valid to access the TabGroup at this time. void ValidateAccess() const { MOZ_ASSERT(IsSafeToRun()); } enum EnqueueStatus { NewlyQueued, AlreadyQueued, }; // Records that this SchedulerGroup had an event enqueued in some // queue. Returns whether the SchedulerGroup was already in a queue before // EnqueueEvent() was called. EnqueueStatus EnqueueEvent() { mEventCount++; return mEventCount == 1 ? NewlyQueued : AlreadyQueued; } enum DequeueStatus { StillQueued, NoLongerQueued, }; // Records that this SchedulerGroup had an event dequeued from some // queue. Returns whether the SchedulerGroup is still in a queue after // DequeueEvent() returns. DequeueStatus DequeueEvent() { mEventCount--; return mEventCount == 0 ? NoLongerQueued : StillQueued; } class Runnable final : public mozilla::Runnable , public nsIRunnablePriority , public nsILabelableRunnable { public: Runnable(already_AddRefed&& aRunnable, SchedulerGroup* aGroup); bool GetAffectedSchedulerGroups(SchedulerGroupSet& aGroups) override; SchedulerGroup* Group() const { return mGroup; } NS_IMETHOD GetName(nsACString& aName) override; bool IsBackground() const { return mGroup->IsBackground(); } NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIRUNNABLE NS_DECL_NSIRUNNABLEPRIORITY NS_DECLARE_STATIC_IID_ACCESSOR(NS_SCHEDULERGROUPRUNNABLE_IID); private: friend class SchedulerGroup; ~Runnable() = default; nsCOMPtr mRunnable; RefPtr mGroup; }; friend class Runnable; bool* GetValidAccessPtr() { return &mIsRunning; } virtual nsresult Dispatch(TaskCategory aCategory, already_AddRefed&& aRunnable); virtual nsISerialEventTarget* EventTargetFor(TaskCategory aCategory) const; // Must always be called on the main thread. The returned AbstractThread can // always be used off the main thread. AbstractThread* AbstractMainThreadFor(TaskCategory aCategory); // This method performs a safe cast. It returns null if |this| is not of the // requested type. virtual dom::TabGroup* AsTabGroup() { return nullptr; } static nsresult UnlabeledDispatch(TaskCategory aCategory, already_AddRefed&& aRunnable); static void MarkVsyncReceived(); static void MarkVsyncRan(); void SetIsRunning(bool aIsRunning) { mIsRunning = aIsRunning; } bool IsRunning() const { return mIsRunning; } enum ValidationType { StartValidation, EndValidation, }; static void SetValidatingAccess(ValidationType aType); + struct EpochQueueEntry + { + nsCOMPtr mRunnable; + uintptr_t mEpochNumber; + + EpochQueueEntry(already_AddRefed aRunnable, uintptr_t aEpoch) + : mRunnable(aRunnable) + , mEpochNumber(aEpoch) + { + } + }; + + using RunnableEpochQueue = Queue; + + RunnableEpochQueue& GetQueue(EventPriority aPriority) + { + return mEventQueues[size_t(aPriority)]; + } + protected: static nsresult InternalUnlabeledDispatch(TaskCategory aCategory, already_AddRefed&& aRunnable); // Implementations are guaranteed that this method is called on the main // thread. virtual AbstractThread* AbstractMainThreadForImpl(TaskCategory aCategory); // Helper method to create an event target specific to a particular TaskCategory. virtual already_AddRefed CreateEventTargetFor(TaskCategory aCategory); // Given an event target returned by |dispatcher->CreateEventTargetFor|, this // function returns |dispatcher|. static SchedulerGroup* FromEventTarget(nsIEventTarget* aEventTarget); nsresult LabeledDispatch(TaskCategory aCategory, already_AddRefed&& aRunnable); void CreateEventTargets(bool aNeedValidation); // Shuts down this dispatcher. If aXPCOMShutdown is true, invalidates this // dispatcher. void Shutdown(bool aXPCOMShutdown); static MOZ_THREAD_LOCAL(bool) sTlsValidatingAccess; bool mIsRunning; // Number of events that are currently enqueued for this SchedulerGroup // (across all queues). size_t mEventCount = 0; nsCOMPtr mEventTargets[size_t(TaskCategory::Count)]; RefPtr mAbstractThreads[size_t(TaskCategory::Count)]; + RunnableEpochQueue mEventQueues[size_t(EventPriority::Count)]; }; NS_DEFINE_STATIC_IID_ACCESSOR(SchedulerGroup::Runnable, NS_SCHEDULERGROUPRUNNABLE_IID); } // namespace mozilla #endif // mozilla_SchedulerGroup_h