mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-04 02:57:38 +00:00
8032d4373e
Differential Revision: https://phabricator.services.mozilla.com/D74672
390 lines
13 KiB
C++
390 lines
13 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 "mozilla/ThreadEventQueue.h"
|
|
#include "mozilla/EventQueue.h"
|
|
|
|
#include "LeakRefPtr.h"
|
|
#include "nsComponentManagerUtils.h"
|
|
#include "nsIThreadInternal.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsThread.h"
|
|
#include "PrioritizedEventQueue.h"
|
|
#include "ThreadEventTarget.h"
|
|
#include "mozilla/TaskController.h"
|
|
|
|
using namespace mozilla;
|
|
|
|
template <class InnerQueueT>
|
|
class ThreadEventQueue<InnerQueueT>::NestedSink : public ThreadTargetSink {
|
|
public:
|
|
NestedSink(EventQueue* aQueue, ThreadEventQueue* aOwner)
|
|
: mQueue(aQueue), mOwner(aOwner) {}
|
|
|
|
bool PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
|
|
EventQueuePriority aPriority) final {
|
|
return mOwner->PutEventInternal(std::move(aEvent), aPriority, this);
|
|
}
|
|
|
|
void Disconnect(const MutexAutoLock& aProofOfLock) final { mQueue = nullptr; }
|
|
|
|
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
|
|
if (mQueue) {
|
|
return mQueue->SizeOfIncludingThis(aMallocSizeOf);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private:
|
|
friend class ThreadEventQueue;
|
|
|
|
// This is a non-owning reference. It must live at least until Disconnect is
|
|
// called to clear it out.
|
|
EventQueue* mQueue;
|
|
RefPtr<ThreadEventQueue> mOwner;
|
|
};
|
|
|
|
template <class InnerQueueT>
|
|
ThreadEventQueue<InnerQueueT>::ThreadEventQueue(UniquePtr<InnerQueueT> aQueue,
|
|
bool aIsMainThread)
|
|
: mBaseQueue(std::move(aQueue)),
|
|
mLock("ThreadEventQueue"),
|
|
mEventsAvailable(mLock, "EventsAvail") {
|
|
if (UseTaskController() && aIsMainThread) {
|
|
TaskController::Get()->SetConditionVariable(&mEventsAvailable);
|
|
}
|
|
static_assert(std::is_base_of<AbstractEventQueue, InnerQueueT>::value,
|
|
"InnerQueueT must be an AbstractEventQueue subclass");
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
ThreadEventQueue<InnerQueueT>::~ThreadEventQueue() {
|
|
MOZ_ASSERT(mNestedQueues.IsEmpty());
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
bool ThreadEventQueue<InnerQueueT>::PutEvent(
|
|
already_AddRefed<nsIRunnable>&& aEvent, EventQueuePriority aPriority) {
|
|
return PutEventInternal(std::move(aEvent), aPriority, nullptr);
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
bool ThreadEventQueue<InnerQueueT>::PutEventInternal(
|
|
already_AddRefed<nsIRunnable>&& aEvent, EventQueuePriority aPriority,
|
|
NestedSink* aSink) {
|
|
// We want to leak the reference when we fail to dispatch it, so that
|
|
// we won't release the event in a wrong thread.
|
|
LeakRefPtr<nsIRunnable> event(std::move(aEvent));
|
|
nsCOMPtr<nsIThreadObserver> obs;
|
|
|
|
{
|
|
// Check if the runnable wants to override the passed-in priority.
|
|
// Do this outside the lock, so runnables implemented in JS can QI
|
|
// (and possibly GC) outside of the lock.
|
|
if (InnerQueueT::SupportsPrioritization) {
|
|
auto* e = event.get(); // can't do_QueryInterface on LeakRefPtr.
|
|
if (nsCOMPtr<nsIRunnablePriority> runnablePrio = do_QueryInterface(e)) {
|
|
uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL;
|
|
runnablePrio->GetPriority(&prio);
|
|
if (prio == nsIRunnablePriority::PRIORITY_HIGH) {
|
|
aPriority = EventQueuePriority::High;
|
|
} else if (prio == nsIRunnablePriority::PRIORITY_INPUT_HIGH) {
|
|
aPriority = EventQueuePriority::InputHigh;
|
|
} else if (prio == nsIRunnablePriority::PRIORITY_MEDIUMHIGH) {
|
|
aPriority = EventQueuePriority::MediumHigh;
|
|
} else if (prio == nsIRunnablePriority::PRIORITY_DEFERRED_TIMERS) {
|
|
aPriority = EventQueuePriority::DeferredTimers;
|
|
} else if (prio == nsIRunnablePriority::PRIORITY_IDLE) {
|
|
aPriority = EventQueuePriority::Idle;
|
|
}
|
|
}
|
|
}
|
|
|
|
MutexAutoLock lock(mLock);
|
|
|
|
if (mEventsAreDoomed) {
|
|
return false;
|
|
}
|
|
|
|
if (aSink) {
|
|
if (!aSink->mQueue) {
|
|
return false;
|
|
}
|
|
|
|
aSink->mQueue->PutEvent(event.take(), aPriority, lock);
|
|
} else {
|
|
mBaseQueue->PutEvent(event.take(), aPriority, lock);
|
|
}
|
|
|
|
mEventsAvailable.Notify();
|
|
|
|
// Make sure to grab the observer before dropping the lock, otherwise the
|
|
// event that we just placed into the queue could run and eventually delete
|
|
// this nsThread before the calling thread is scheduled again. We would then
|
|
// crash while trying to access a dead nsThread.
|
|
obs = mObserver;
|
|
}
|
|
|
|
if (obs) {
|
|
obs->OnDispatchedEvent();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
already_AddRefed<nsIRunnable> ThreadEventQueue<InnerQueueT>::GetEvent(
|
|
bool aMayWait, EventQueuePriority* aPriority,
|
|
mozilla::TimeDuration* aLastEventDelay) {
|
|
nsCOMPtr<nsIRunnable> event;
|
|
bool eventIsIdleRunnable = false;
|
|
// This will be the IdlePeriodState for the queue the event, if any,
|
|
// came from. May be null all along.
|
|
IdlePeriodState* idleState = nullptr;
|
|
|
|
{
|
|
// Scope for lock. When we are about to return, we will exit this
|
|
// scope so we can do some work after releasing the lock but
|
|
// before returning.
|
|
MutexAutoLock lock(mLock);
|
|
|
|
for (;;) {
|
|
const bool noNestedQueue = mNestedQueues.IsEmpty();
|
|
if (noNestedQueue) {
|
|
idleState = mBaseQueue->GetIdlePeriodState();
|
|
event = mBaseQueue->GetEvent(aPriority, lock, aLastEventDelay,
|
|
&eventIsIdleRunnable);
|
|
} else {
|
|
// We always get events from the topmost queue when there are nested
|
|
// queues.
|
|
MOZ_ASSERT(!mNestedQueues.LastElement().mQueue->GetIdlePeriodState());
|
|
event = mNestedQueues.LastElement().mQueue->GetEvent(
|
|
aPriority, lock, aLastEventDelay, &eventIsIdleRunnable);
|
|
MOZ_ASSERT(!eventIsIdleRunnable);
|
|
}
|
|
|
|
if (event) {
|
|
break;
|
|
}
|
|
|
|
if (idleState) {
|
|
MOZ_ASSERT(noNestedQueue);
|
|
if (mBaseQueue->HasIdleRunnables(lock)) {
|
|
// We have idle runnables that we may not have gotten above because
|
|
// our idle state is not up to date. We need to update the idle state
|
|
// and try again. We need to temporarily release the lock while we do
|
|
// that.
|
|
MutexAutoUnlock unlock(mLock);
|
|
idleState->UpdateCachedIdleDeadline(unlock);
|
|
} else {
|
|
// We need to notify our idle state that we're out of tasks to run.
|
|
// This needs to be done while not holding the lock.
|
|
MutexAutoUnlock unlock(mLock);
|
|
idleState->RanOutOfTasks(unlock);
|
|
}
|
|
|
|
// When we unlocked, someone may have queued a new runnable on us. So
|
|
// we _must_ try to get a runnable again before we start sleeping, since
|
|
// that might be the runnable we were waiting for.
|
|
MOZ_ASSERT(
|
|
noNestedQueue == mNestedQueues.IsEmpty(),
|
|
"Who is pushing nested queues on us from some other thread?");
|
|
event = mBaseQueue->GetEvent(aPriority, lock, aLastEventDelay,
|
|
&eventIsIdleRunnable);
|
|
// Now clear the cached idle deadline, because it was specific to this
|
|
// GetEvent() call.
|
|
idleState->ClearCachedIdleDeadline();
|
|
|
|
if (event) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// No runnable available. Sleep waiting for one if if we're supposed to.
|
|
// Otherwise just go ahead and return null.
|
|
if (!aMayWait) {
|
|
break;
|
|
}
|
|
|
|
AUTO_PROFILER_LABEL("ThreadEventQueue::GetEvent::Wait", IDLE);
|
|
mEventsAvailable.Wait();
|
|
}
|
|
}
|
|
|
|
if (idleState) {
|
|
// The pending task guarantee is not needed anymore, since we just tried
|
|
// doing GetEvent().
|
|
idleState->ForgetPendingTaskGuarantee();
|
|
if (event && !eventIsIdleRunnable) {
|
|
// We don't have a MutexAutoUnlock to pass to the callee here. We _could_
|
|
// have one if we wanted to, simply by moving this into the same scope as
|
|
// our MutexAutoLock and adding a MutexAutoUnlock, but then we'd be doing
|
|
// an extra lock/unlock pair on mLock, which seems uncalled-for.
|
|
idleState->FlagNotIdle();
|
|
}
|
|
}
|
|
|
|
return event.forget();
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
void ThreadEventQueue<InnerQueueT>::DidRunEvent() {
|
|
MutexAutoLock lock(mLock);
|
|
if (mNestedQueues.IsEmpty()) {
|
|
mBaseQueue->DidRunEvent(lock);
|
|
// Don't do anything else here, because that call might have
|
|
// temporarily unlocked the lock.
|
|
} else {
|
|
mNestedQueues.LastElement().mQueue->DidRunEvent(lock);
|
|
}
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
bool ThreadEventQueue<InnerQueueT>::HasPendingEvent() {
|
|
MutexAutoLock lock(mLock);
|
|
|
|
// We always get events from the topmost queue when there are nested queues.
|
|
if (mNestedQueues.IsEmpty()) {
|
|
return mBaseQueue->HasReadyEvent(lock);
|
|
} else {
|
|
return mNestedQueues.LastElement().mQueue->HasReadyEvent(lock);
|
|
}
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
bool ThreadEventQueue<InnerQueueT>::HasPendingHighPriorityEvents() {
|
|
MutexAutoLock lock(mLock);
|
|
|
|
// We always get events from the topmost queue when there are nested queues.
|
|
if (mNestedQueues.IsEmpty()) {
|
|
return mBaseQueue->HasPendingHighPriorityEvents(lock);
|
|
} else {
|
|
return mNestedQueues.LastElement().mQueue->HasPendingHighPriorityEvents(
|
|
lock);
|
|
}
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
bool ThreadEventQueue<InnerQueueT>::ShutdownIfNoPendingEvents() {
|
|
MutexAutoLock lock(mLock);
|
|
if (mNestedQueues.IsEmpty() && mBaseQueue->IsEmpty(lock)) {
|
|
mEventsAreDoomed = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
void ThreadEventQueue<InnerQueueT>::EnableInputEventPrioritization() {
|
|
MutexAutoLock lock(mLock);
|
|
mBaseQueue->EnableInputEventPrioritization(lock);
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
void ThreadEventQueue<InnerQueueT>::FlushInputEventPrioritization() {
|
|
MutexAutoLock lock(mLock);
|
|
mBaseQueue->FlushInputEventPrioritization(lock);
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
void ThreadEventQueue<InnerQueueT>::SuspendInputEventPrioritization() {
|
|
MutexAutoLock lock(mLock);
|
|
mBaseQueue->SuspendInputEventPrioritization(lock);
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
void ThreadEventQueue<InnerQueueT>::ResumeInputEventPrioritization() {
|
|
MutexAutoLock lock(mLock);
|
|
mBaseQueue->ResumeInputEventPrioritization(lock);
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
already_AddRefed<nsISerialEventTarget>
|
|
ThreadEventQueue<InnerQueueT>::PushEventQueue() {
|
|
auto queue = MakeUnique<EventQueue>();
|
|
RefPtr<NestedSink> sink = new NestedSink(queue.get(), this);
|
|
RefPtr<ThreadEventTarget> eventTarget =
|
|
new ThreadEventTarget(sink, NS_IsMainThread());
|
|
|
|
MutexAutoLock lock(mLock);
|
|
|
|
mNestedQueues.AppendElement(NestedQueueItem(std::move(queue), eventTarget));
|
|
return eventTarget.forget();
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
void ThreadEventQueue<InnerQueueT>::PopEventQueue(nsIEventTarget* aTarget) {
|
|
MutexAutoLock lock(mLock);
|
|
|
|
MOZ_ASSERT(!mNestedQueues.IsEmpty());
|
|
|
|
NestedQueueItem& item = mNestedQueues.LastElement();
|
|
|
|
MOZ_ASSERT(aTarget == item.mEventTarget);
|
|
|
|
// Disconnect the event target that will be popped.
|
|
item.mEventTarget->Disconnect(lock);
|
|
|
|
AbstractEventQueue* prevQueue =
|
|
mNestedQueues.Length() == 1
|
|
? static_cast<AbstractEventQueue*>(mBaseQueue.get())
|
|
: static_cast<AbstractEventQueue*>(
|
|
mNestedQueues[mNestedQueues.Length() - 2].mQueue.get());
|
|
|
|
// Move events from the old queue to the new one.
|
|
nsCOMPtr<nsIRunnable> event;
|
|
EventQueuePriority prio;
|
|
TimeDuration delay;
|
|
while ((event = item.mQueue->GetEvent(&prio, lock, &delay))) {
|
|
// preserve the event delay so far
|
|
prevQueue->PutEvent(event.forget(), prio, lock, &delay);
|
|
}
|
|
|
|
mNestedQueues.RemoveLastElement();
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
size_t ThreadEventQueue<InnerQueueT>::SizeOfExcludingThis(
|
|
mozilla::MallocSizeOf aMallocSizeOf) const {
|
|
size_t n = 0;
|
|
|
|
n += mBaseQueue->SizeOfIncludingThis(aMallocSizeOf);
|
|
|
|
n += mNestedQueues.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
|
for (auto& queue : mNestedQueues) {
|
|
n += queue.mEventTarget->SizeOfIncludingThis(aMallocSizeOf);
|
|
}
|
|
|
|
return SynchronizedEventQueue::SizeOfExcludingThis(aMallocSizeOf) + n;
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
already_AddRefed<nsIThreadObserver>
|
|
ThreadEventQueue<InnerQueueT>::GetObserver() {
|
|
MutexAutoLock lock(mLock);
|
|
return do_AddRef(mObserver);
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
already_AddRefed<nsIThreadObserver>
|
|
ThreadEventQueue<InnerQueueT>::GetObserverOnThread() {
|
|
return do_AddRef(mObserver);
|
|
}
|
|
|
|
template <class InnerQueueT>
|
|
void ThreadEventQueue<InnerQueueT>::SetObserver(nsIThreadObserver* aObserver) {
|
|
MutexAutoLock lock(mLock);
|
|
mObserver = aObserver;
|
|
if (UseTaskController() && NS_IsMainThread()) {
|
|
TaskController::Get()->SetThreadObserver(aObserver);
|
|
}
|
|
}
|
|
|
|
namespace mozilla {
|
|
template class ThreadEventQueue<EventQueue>;
|
|
template class ThreadEventQueue<PrioritizedEventQueue>;
|
|
} // namespace mozilla
|