Bug 1917371 - Consolidate unit_base_updater and unit_service_updater tests, r=bytesiz...
[gecko.git] / xpcom / threads / ThreadEventQueue.cpp
blob9cdb6479ae3879fb705bfe7c76bfeb380c5c885c
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/ThreadEventQueue.h"
8 #include "mozilla/EventQueue.h"
10 #include "LeakRefPtr.h"
11 #include "nsComponentManagerUtils.h"
12 #include "nsITargetShutdownTask.h"
13 #include "nsIThreadInternal.h"
14 #include "nsThreadUtils.h"
15 #include "nsThread.h"
16 #include "ThreadEventTarget.h"
17 #include "mozilla/ProfilerLabels.h"
18 #include "mozilla/TaskController.h"
19 #include "mozilla/StaticPrefs_threads.h"
21 using namespace mozilla;
23 class ThreadEventQueue::NestedSink : public ThreadTargetSink {
24 public:
25 NestedSink(EventQueue* aQueue, ThreadEventQueue* aOwner)
26 : mQueue(aQueue), mOwner(aOwner) {}
28 bool PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
29 EventQueuePriority aPriority) final {
30 return mOwner->PutEventInternal(std::move(aEvent), aPriority, this);
33 void Disconnect(const MutexAutoLock& aProofOfLock) final { mQueue = nullptr; }
35 nsresult RegisterShutdownTask(nsITargetShutdownTask* aTask) final {
36 return NS_ERROR_NOT_IMPLEMENTED;
38 nsresult UnregisterShutdownTask(nsITargetShutdownTask* aTask) final {
39 return NS_ERROR_NOT_IMPLEMENTED;
42 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
43 if (mQueue) {
44 return mQueue->SizeOfIncludingThis(aMallocSizeOf);
46 return 0;
49 private:
50 friend class ThreadEventQueue;
52 // This is a non-owning reference. It must live at least until Disconnect is
53 // called to clear it out.
54 EventQueue* mQueue;
55 RefPtr<ThreadEventQueue> mOwner;
58 ThreadEventQueue::ThreadEventQueue(UniquePtr<EventQueue> aQueue,
59 bool aIsMainThread)
60 : mBaseQueue(std::move(aQueue)),
61 mLock("ThreadEventQueue"),
62 mEventsAvailable(mLock, "EventsAvail"),
63 mIsMainThread(aIsMainThread) {
64 if (aIsMainThread) {
65 TaskController::Get()->SetConditionVariable(&mEventsAvailable);
69 ThreadEventQueue::~ThreadEventQueue() { MOZ_ASSERT(mNestedQueues.IsEmpty()); }
71 bool ThreadEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
72 EventQueuePriority aPriority) {
73 return PutEventInternal(std::move(aEvent), aPriority, nullptr);
76 bool ThreadEventQueue::PutEventInternal(already_AddRefed<nsIRunnable>&& aEvent,
77 EventQueuePriority aPriority,
78 NestedSink* aSink) {
79 // We want to leak the reference when we fail to dispatch it, so that
80 // we won't release the event in a wrong thread.
81 LeakRefPtr<nsIRunnable> event(std::move(aEvent));
82 nsCOMPtr<nsIThreadObserver> obs;
85 // Check if the runnable wants to override the passed-in priority.
86 // Do this outside the lock, so runnables implemented in JS can QI
87 // (and possibly GC) outside of the lock.
88 if (mIsMainThread) {
89 auto* e = event.get(); // can't do_QueryInterface on LeakRefPtr.
90 if (nsCOMPtr<nsIRunnablePriority> runnablePrio = do_QueryInterface(e)) {
91 uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL;
92 runnablePrio->GetPriority(&prio);
93 if (prio == nsIRunnablePriority::PRIORITY_CONTROL) {
94 aPriority = EventQueuePriority::Control;
95 } else if (prio == nsIRunnablePriority::PRIORITY_RENDER_BLOCKING) {
96 aPriority = EventQueuePriority::RenderBlocking;
97 } else if (prio == nsIRunnablePriority::PRIORITY_VSYNC) {
98 aPriority = EventQueuePriority::Vsync;
99 } else if (prio == nsIRunnablePriority::PRIORITY_INPUT_HIGH) {
100 aPriority = EventQueuePriority::InputHigh;
101 } else if (prio == nsIRunnablePriority::PRIORITY_MEDIUMHIGH) {
102 aPriority = EventQueuePriority::MediumHigh;
103 } else if (prio == nsIRunnablePriority::PRIORITY_DEFERRED_TIMERS) {
104 aPriority = EventQueuePriority::DeferredTimers;
105 } else if (prio == nsIRunnablePriority::PRIORITY_IDLE) {
106 aPriority = EventQueuePriority::Idle;
107 } else if (prio == nsIRunnablePriority::PRIORITY_LOW) {
108 aPriority = EventQueuePriority::Low;
113 MutexAutoLock lock(mLock);
115 if (mEventsAreDoomed) {
116 return false;
119 if (aSink) {
120 if (!aSink->mQueue) {
121 return false;
124 aSink->mQueue->PutEvent(event.take(), aPriority, lock);
125 } else {
126 mBaseQueue->PutEvent(event.take(), aPriority, lock);
129 mEventsAvailable.Notify();
131 // Make sure to grab the observer before dropping the lock, otherwise the
132 // event that we just placed into the queue could run and eventually delete
133 // this nsThread before the calling thread is scheduled again. We would then
134 // crash while trying to access a dead nsThread.
135 obs = mObserver;
138 if (obs) {
139 obs->OnDispatchedEvent();
142 return true;
145 already_AddRefed<nsIRunnable> ThreadEventQueue::GetEvent(
146 bool aMayWait, mozilla::TimeDuration* aLastEventDelay) {
147 nsCOMPtr<nsIRunnable> event;
149 // Scope for lock. When we are about to return, we will exit this
150 // scope so we can do some work after releasing the lock but
151 // before returning.
152 MutexAutoLock lock(mLock);
154 for (;;) {
155 const bool noNestedQueue = mNestedQueues.IsEmpty();
156 if (noNestedQueue) {
157 event = mBaseQueue->GetEvent(lock, aLastEventDelay);
158 } else {
159 // We always get events from the topmost queue when there are nested
160 // queues.
161 event =
162 mNestedQueues.LastElement().mQueue->GetEvent(lock, aLastEventDelay);
165 if (event) {
166 break;
169 // No runnable available. Sleep waiting for one if if we're supposed to.
170 // Otherwise just go ahead and return null.
171 if (!aMayWait) {
172 break;
175 AUTO_PROFILER_LABEL("ThreadEventQueue::GetEvent::Wait", IDLE);
176 mEventsAvailable.Wait();
180 return event.forget();
183 bool ThreadEventQueue::HasPendingEvent() {
184 MutexAutoLock lock(mLock);
186 // We always get events from the topmost queue when there are nested queues.
187 if (mNestedQueues.IsEmpty()) {
188 return mBaseQueue->HasReadyEvent(lock);
189 } else {
190 return mNestedQueues.LastElement().mQueue->HasReadyEvent(lock);
194 bool ThreadEventQueue::ShutdownIfNoPendingEvents() {
195 MutexAutoLock lock(mLock);
196 if (mNestedQueues.IsEmpty() && mBaseQueue->IsEmpty(lock)) {
197 mEventsAreDoomed = true;
198 return true;
200 return false;
203 already_AddRefed<nsISerialEventTarget> ThreadEventQueue::PushEventQueue() {
204 auto queue = MakeUnique<EventQueue>();
205 RefPtr<NestedSink> sink = new NestedSink(queue.get(), this);
206 RefPtr<ThreadEventTarget> eventTarget =
207 new ThreadEventTarget(sink, NS_IsMainThread(), false);
209 MutexAutoLock lock(mLock);
211 mNestedQueues.AppendElement(NestedQueueItem(std::move(queue), eventTarget));
212 return eventTarget.forget();
215 void ThreadEventQueue::PopEventQueue(nsIEventTarget* aTarget) {
216 MutexAutoLock lock(mLock);
218 MOZ_ASSERT(!mNestedQueues.IsEmpty());
220 NestedQueueItem& item = mNestedQueues.LastElement();
222 MOZ_ASSERT(aTarget == item.mEventTarget);
224 // Disconnect the event target that will be popped.
225 item.mEventTarget->Disconnect(lock);
227 EventQueue* prevQueue =
228 mNestedQueues.Length() == 1
229 ? mBaseQueue.get()
230 : mNestedQueues[mNestedQueues.Length() - 2].mQueue.get();
232 // Move events from the old queue to the new one.
233 nsCOMPtr<nsIRunnable> event;
234 TimeDuration delay;
235 while ((event = item.mQueue->GetEvent(lock, &delay))) {
236 // preserve the event delay so far
237 prevQueue->PutEvent(event.forget(), EventQueuePriority::Normal, lock,
238 &delay);
241 mNestedQueues.RemoveLastElement();
244 size_t ThreadEventQueue::SizeOfExcludingThis(
245 mozilla::MallocSizeOf aMallocSizeOf) {
246 size_t n = 0;
249 MutexAutoLock lock(mLock);
250 n += mBaseQueue->SizeOfIncludingThis(aMallocSizeOf);
251 n += mNestedQueues.ShallowSizeOfExcludingThis(aMallocSizeOf);
252 for (auto& queue : mNestedQueues) {
253 n += queue.mEventTarget->SizeOfIncludingThis(aMallocSizeOf);
257 return SynchronizedEventQueue::SizeOfExcludingThis(aMallocSizeOf) + n;
260 already_AddRefed<nsIThreadObserver> ThreadEventQueue::GetObserver() {
261 MutexAutoLock lock(mLock);
262 return do_AddRef(mObserver);
265 already_AddRefed<nsIThreadObserver> ThreadEventQueue::GetObserverOnThread()
266 MOZ_NO_THREAD_SAFETY_ANALYSIS {
267 // only written on this thread
268 return do_AddRef(mObserver);
271 void ThreadEventQueue::SetObserver(nsIThreadObserver* aObserver) {
272 // Always called from the thread - single writer.
273 nsCOMPtr<nsIThreadObserver> observer = aObserver;
275 MutexAutoLock lock(mLock);
276 mObserver.swap(observer);
278 if (NS_IsMainThread()) {
279 TaskController::Get()->SetThreadObserver(aObserver);
283 nsresult ThreadEventQueue::RegisterShutdownTask(nsITargetShutdownTask* aTask) {
284 NS_ENSURE_ARG(aTask);
285 MutexAutoLock lock(mLock);
286 if (mEventsAreDoomed || mShutdownTasksRun) {
287 return NS_ERROR_UNEXPECTED;
289 MOZ_ASSERT(!mShutdownTasks.Contains(aTask));
290 mShutdownTasks.AppendElement(aTask);
291 return NS_OK;
294 nsresult ThreadEventQueue::UnregisterShutdownTask(
295 nsITargetShutdownTask* aTask) {
296 NS_ENSURE_ARG(aTask);
297 MutexAutoLock lock(mLock);
298 if (mEventsAreDoomed || mShutdownTasksRun) {
299 return NS_ERROR_UNEXPECTED;
301 return mShutdownTasks.RemoveElement(aTask) ? NS_OK : NS_ERROR_UNEXPECTED;
304 void ThreadEventQueue::RunShutdownTasks() {
305 nsTArray<nsCOMPtr<nsITargetShutdownTask>> shutdownTasks;
307 MutexAutoLock lock(mLock);
308 shutdownTasks = std::move(mShutdownTasks);
309 mShutdownTasks.Clear();
310 mShutdownTasksRun = true;
312 for (auto& task : shutdownTasks) {
313 task->TargetShutdown();
317 ThreadEventQueue::NestedQueueItem::NestedQueueItem(
318 UniquePtr<EventQueue> aQueue, ThreadEventTarget* aEventTarget)
319 : mQueue(std::move(aQueue)), mEventTarget(aEventTarget) {}