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"
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
{
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
) {
44 return mQueue
->SizeOfIncludingThis(aMallocSizeOf
);
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.
55 RefPtr
<ThreadEventQueue
> mOwner
;
58 ThreadEventQueue::ThreadEventQueue(UniquePtr
<EventQueue
> aQueue
,
60 : mBaseQueue(std::move(aQueue
)),
61 mLock("ThreadEventQueue"),
62 mEventsAvailable(mLock
, "EventsAvail"),
63 mIsMainThread(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
,
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.
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
) {
120 if (!aSink
->mQueue
) {
124 aSink
->mQueue
->PutEvent(event
.take(), aPriority
, lock
);
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.
139 obs
->OnDispatchedEvent();
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
152 MutexAutoLock
lock(mLock
);
155 const bool noNestedQueue
= mNestedQueues
.IsEmpty();
157 event
= mBaseQueue
->GetEvent(lock
, aLastEventDelay
);
159 // We always get events from the topmost queue when there are nested
162 mNestedQueues
.LastElement().mQueue
->GetEvent(lock
, aLastEventDelay
);
169 // No runnable available. Sleep waiting for one if if we're supposed to.
170 // Otherwise just go ahead and return null.
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
);
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;
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
230 : mNestedQueues
[mNestedQueues
.Length() - 2].mQueue
.get();
232 // Move events from the old queue to the new one.
233 nsCOMPtr
<nsIRunnable
> event
;
235 while ((event
= item
.mQueue
->GetEvent(lock
, &delay
))) {
236 // preserve the event delay so far
237 prevQueue
->PutEvent(event
.forget(), EventQueuePriority::Normal
, lock
,
241 mNestedQueues
.RemoveLastElement();
244 size_t ThreadEventQueue::SizeOfExcludingThis(
245 mozilla::MallocSizeOf aMallocSizeOf
) {
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
);
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
) {}