зеркало из https://github.com/mozilla/gecko-dev.git
286 строки
7.9 KiB
C++
286 строки
7.9 KiB
C++
/* -*- 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;
|
|
|
|
LinkedList<SchedulerGroup>* LabeledEventQueue::sSchedulerGroups;
|
|
size_t LabeledEventQueue::sLabeledEventQueueCount;
|
|
SchedulerGroup* LabeledEventQueue::sCurrentSchedulerGroup;
|
|
|
|
LabeledEventQueue::LabeledEventQueue()
|
|
{
|
|
// 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<SchedulerGroup>();
|
|
}
|
|
}
|
|
|
|
LabeledEventQueue::~LabeledEventQueue()
|
|
{
|
|
if (--sLabeledEventQueueCount == 0) {
|
|
delete sSchedulerGroups;
|
|
sSchedulerGroups = nullptr;
|
|
}
|
|
}
|
|
|
|
static SchedulerGroup*
|
|
GetSchedulerGroup(nsIRunnable* aEvent)
|
|
{
|
|
RefPtr<SchedulerGroup::Runnable> 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<nsILabelableRunnable> labelable = do_QueryInterface(aEvent);
|
|
if (!labelable) {
|
|
return false;
|
|
}
|
|
|
|
return labelable->IsReadyToRun();
|
|
}
|
|
|
|
void
|
|
LabeledEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
|
|
EventPriority aPriority,
|
|
const MutexAutoLock& aProofOfLock)
|
|
{
|
|
nsCOMPtr<nsIRunnable> 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));
|
|
|
|
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<nsIRunnable>
|
|
LabeledEventQueue::GetEvent(EventPriority* aPriority,
|
|
const MutexAutoLock& aProofOfLock)
|
|
{
|
|
if (mEpochs.IsEmpty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
Epoch epoch = mEpochs.FirstElement();
|
|
if (!epoch.IsLabeled()) {
|
|
QueueEntry entry = mUnlabeled.FirstElement();
|
|
if (!IsReadyToRun(entry.mRunnable, nullptr)) {
|
|
return nullptr;
|
|
}
|
|
|
|
PopEpoch();
|
|
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--;
|
|
|
|
RunnableEpochQueue* queue = mLabeled.Get(group);
|
|
if (!queue) {
|
|
// This can happen if |group| is in a different LabeledEventQueue than |this|.
|
|
group = NextSchedulerGroup(group);
|
|
continue;
|
|
}
|
|
MOZ_ASSERT(!queue->IsEmpty());
|
|
|
|
QueueEntry entry = queue->FirstElement();
|
|
if (entry.mEpochNumber == epoch.mEpochNumber &&
|
|
IsReadyToRun(entry.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);
|
|
}
|
|
queue->Pop();
|
|
if (queue->IsEmpty()) {
|
|
mLabeled.Remove(group);
|
|
}
|
|
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();
|
|
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.
|
|
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());
|
|
|
|
QueueEntry entry = queue->FirstElement();
|
|
if (entry.mEpochNumber != currentEpoch) {
|
|
continue;
|
|
}
|
|
|
|
if (IsReadyToRun(entry.mRunnable, key)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|