summaryrefslogtreecommitdiffstats
path: root/xpcom/threads/ThreadEventQueue.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--xpcom/threads/ThreadEventQueue.cpp325
1 files changed, 325 insertions, 0 deletions
diff --git a/xpcom/threads/ThreadEventQueue.cpp b/xpcom/threads/ThreadEventQueue.cpp
new file mode 100644
index 0000000000..70a4668b6c
--- /dev/null
+++ b/xpcom/threads/ThreadEventQueue.cpp
@@ -0,0 +1,325 @@
+/* -*- 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 "nsITargetShutdownTask.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<nsIRunnable>&& aEvent,
+ EventQueuePriority aPriority) final {
+ return mOwner->PutEventInternal(std::move(aEvent), aPriority, this);
+ }
+
+ void Disconnect(const MutexAutoLock& aProofOfLock) final { mQueue = nullptr; }
+
+ nsresult RegisterShutdownTask(nsITargetShutdownTask* aTask) final {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult UnregisterShutdownTask(nsITargetShutdownTask* aTask) final {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
+ 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;
+};
+
+ThreadEventQueue::ThreadEventQueue(UniquePtr<EventQueue> 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<nsIRunnable>&& aEvent,
+ EventQueuePriority aPriority) {
+ return PutEventInternal(std::move(aEvent), aPriority, nullptr);
+}
+
+bool ThreadEventQueue::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 (mIsMainThread) {
+ 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_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;
+ } else if (prio == nsIRunnablePriority::PRIORITY_LOW) {
+ aPriority = EventQueuePriority::Low;
+ }
+ }
+
+ 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<nsIRunnable> ThreadEventQueue::GetEvent(
+ bool aMayWait, mozilla::TimeDuration* aLastEventDelay) {
+ nsCOMPtr<nsIRunnable> 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<nsISerialEventTarget> ThreadEventQueue::PushEventQueue() {
+ auto queue = MakeUnique<EventQueue>();
+ RefPtr<NestedSink> sink = new NestedSink(queue.get(), this);
+ RefPtr<ThreadEventTarget> eventTarget =
+ new ThreadEventTarget(sink, NS_IsMainThread(), false);
+
+ 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<nsIRunnable> 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) {
+ size_t n = 0;
+
+ n += mBaseQueue->SizeOfIncludingThis(aMallocSizeOf);
+
+ {
+ MutexAutoLock lock(mLock);
+ n += mNestedQueues.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto& queue : mNestedQueues) {
+ n += queue.mEventTarget->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+
+ return SynchronizedEventQueue::SizeOfExcludingThis(aMallocSizeOf) + n;
+}
+
+already_AddRefed<nsIThreadObserver> ThreadEventQueue::GetObserver() {
+ MutexAutoLock lock(mLock);
+ return do_AddRef(mObserver);
+}
+
+already_AddRefed<nsIThreadObserver> ThreadEventQueue::GetObserverOnThread()
+ MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ // only written on this thread
+ return do_AddRef(mObserver);
+}
+
+void ThreadEventQueue::SetObserver(nsIThreadObserver* aObserver) {
+ // Always called from the thread - single writer.
+ nsCOMPtr<nsIThreadObserver> observer = aObserver;
+ {
+ MutexAutoLock lock(mLock);
+ mObserver.swap(observer);
+ }
+ if (NS_IsMainThread()) {
+ TaskController::Get()->SetThreadObserver(aObserver);
+ }
+}
+
+nsresult ThreadEventQueue::RegisterShutdownTask(nsITargetShutdownTask* aTask) {
+ NS_ENSURE_ARG(aTask);
+ MutexAutoLock lock(mLock);
+ if (mEventsAreDoomed || mShutdownTasksRun) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ MOZ_ASSERT(!mShutdownTasks.Contains(aTask));
+ mShutdownTasks.AppendElement(aTask);
+ return NS_OK;
+}
+
+nsresult ThreadEventQueue::UnregisterShutdownTask(
+ nsITargetShutdownTask* aTask) {
+ NS_ENSURE_ARG(aTask);
+ MutexAutoLock lock(mLock);
+ if (mEventsAreDoomed || mShutdownTasksRun) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return mShutdownTasks.RemoveElement(aTask) ? NS_OK : NS_ERROR_UNEXPECTED;
+}
+
+void ThreadEventQueue::RunShutdownTasks() {
+ nsTArray<nsCOMPtr<nsITargetShutdownTask>> shutdownTasks;
+ {
+ MutexAutoLock lock(mLock);
+ shutdownTasks = std::move(mShutdownTasks);
+ mShutdownTasks.Clear();
+ mShutdownTasksRun = true;
+ }
+ for (auto& task : shutdownTasks) {
+ task->TargetShutdown();
+ }
+}
+
+ThreadEventQueue::NestedQueueItem::NestedQueueItem(
+ UniquePtr<EventQueue> aQueue, ThreadEventTarget* aEventTarget)
+ : mQueue(std::move(aQueue)), mEventTarget(aEventTarget) {}