/* -*- 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 "ThreadEventTarget.h" #include "mozilla/ProfilerLabels.h" #include "mozilla/TaskController.h" #include "mozilla/StaticPrefs_threads.h" using namespace mozilla; class ThreadEventQueue::NestedSink : public ThreadTargetSink { public: NestedSink(EventQueue* aQueue, ThreadEventQueue* aOwner) : mQueue(aQueue), mOwner(aOwner) {} bool PutEvent(already_AddRefed&& 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 mOwner; }; ThreadEventQueue::ThreadEventQueue(UniquePtr aQueue, bool aIsMainThread) : mBaseQueue(std::move(aQueue)), mLock("ThreadEventQueue"), mEventsAvailable(mLock, "EventsAvail"), mIsMainThread(aIsMainThread) { if (aIsMainThread) { TaskController::Get()->SetConditionVariable(&mEventsAvailable); } } ThreadEventQueue::~ThreadEventQueue() { MOZ_ASSERT(mNestedQueues.IsEmpty()); } bool ThreadEventQueue::PutEvent(already_AddRefed&& aEvent, EventQueuePriority aPriority) { return PutEventInternal(std::move(aEvent), aPriority, nullptr); } bool ThreadEventQueue::PutEventInternal(already_AddRefed&& 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 event(std::move(aEvent)); nsCOMPtr 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 (mIsMainThread) { auto* e = event.get(); // can't do_QueryInterface on LeakRefPtr. if (nsCOMPtr runnablePrio = do_QueryInterface(e)) { uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL; runnablePrio->GetPriority(&prio); if (prio == nsIRunnablePriority::PRIORITY_CONTROL) { aPriority = EventQueuePriority::Control; } else if (prio == nsIRunnablePriority::PRIORITY_RENDER_BLOCKING) { aPriority = EventQueuePriority::RenderBlocking; } else if (prio == nsIRunnablePriority::PRIORITY_VSYNC) { aPriority = EventQueuePriority::Vsync; } 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; } } if (aPriority == EventQueuePriority::Control && !StaticPrefs::threads_control_event_queue_enabled()) { aPriority = EventQueuePriority::MediumHigh; } } 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; } already_AddRefed ThreadEventQueue::GetEvent( bool aMayWait, mozilla::TimeDuration* aLastEventDelay) { nsCOMPtr event; { // 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) { event = mBaseQueue->GetEvent(lock, aLastEventDelay); } else { // We always get events from the topmost queue when there are nested // queues. event = mNestedQueues.LastElement().mQueue->GetEvent(lock, aLastEventDelay); } 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(); } } return event.forget(); } bool ThreadEventQueue::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); } } bool ThreadEventQueue::ShutdownIfNoPendingEvents() { MutexAutoLock lock(mLock); if (mNestedQueues.IsEmpty() && mBaseQueue->IsEmpty(lock)) { mEventsAreDoomed = true; return true; } return false; } already_AddRefed ThreadEventQueue::PushEventQueue() { auto queue = MakeUnique(); RefPtr sink = new NestedSink(queue.get(), this); RefPtr eventTarget = new ThreadEventTarget(sink, NS_IsMainThread()); MutexAutoLock lock(mLock); mNestedQueues.AppendElement(NestedQueueItem(std::move(queue), eventTarget)); return eventTarget.forget(); } void ThreadEventQueue::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); EventQueue* prevQueue = mNestedQueues.Length() == 1 ? mBaseQueue.get() : mNestedQueues[mNestedQueues.Length() - 2].mQueue.get(); // Move events from the old queue to the new one. nsCOMPtr event; TimeDuration delay; while ((event = item.mQueue->GetEvent(lock, &delay))) { // preserve the event delay so far prevQueue->PutEvent(event.forget(), EventQueuePriority::Normal, lock, &delay); } mNestedQueues.RemoveLastElement(); } size_t ThreadEventQueue::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; } already_AddRefed ThreadEventQueue::GetObserver() { MutexAutoLock lock(mLock); return do_AddRef(mObserver); } already_AddRefed ThreadEventQueue::GetObserverOnThread() { return do_AddRef(mObserver); } void ThreadEventQueue::SetObserver(nsIThreadObserver* aObserver) { MutexAutoLock lock(mLock); mObserver = aObserver; if (NS_IsMainThread()) { TaskController::Get()->SetThreadObserver(aObserver); } } ThreadEventQueue::NestedQueueItem::NestedQueueItem( UniquePtr aQueue, ThreadEventTarget* aEventTarget) : mQueue(std::move(aQueue)), mEventTarget(aEventTarget) {}