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 "nsIThreadInternal.h"
13 #include "nsThreadUtils.h"
15 #include "ThreadEventTarget.h"
16 #include "mozilla/ProfilerLabels.h"
17 #include "mozilla/TaskController.h"
18 #include "mozilla/StaticPrefs_threads.h"
20 using namespace mozilla
;
22 class ThreadEventQueue::NestedSink
: public ThreadTargetSink
{
24 NestedSink(EventQueue
* aQueue
, ThreadEventQueue
* aOwner
)
25 : mQueue(aQueue
), mOwner(aOwner
) {}
27 bool PutEvent(already_AddRefed
<nsIRunnable
>&& aEvent
,
28 EventQueuePriority aPriority
) final
{
29 return mOwner
->PutEventInternal(std::move(aEvent
), aPriority
, this);
32 void Disconnect(const MutexAutoLock
& aProofOfLock
) final
{ mQueue
= nullptr; }
34 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf
) {
36 return mQueue
->SizeOfIncludingThis(aMallocSizeOf
);
42 friend class ThreadEventQueue
;
44 // This is a non-owning reference. It must live at least until Disconnect is
45 // called to clear it out.
47 RefPtr
<ThreadEventQueue
> mOwner
;
50 ThreadEventQueue::ThreadEventQueue(UniquePtr
<EventQueue
> aQueue
,
52 : mBaseQueue(std::move(aQueue
)),
53 mLock("ThreadEventQueue"),
54 mEventsAvailable(mLock
, "EventsAvail"),
55 mIsMainThread(aIsMainThread
) {
57 TaskController::Get()->SetConditionVariable(&mEventsAvailable
);
61 ThreadEventQueue::~ThreadEventQueue() { MOZ_ASSERT(mNestedQueues
.IsEmpty()); }
63 bool ThreadEventQueue::PutEvent(already_AddRefed
<nsIRunnable
>&& aEvent
,
64 EventQueuePriority aPriority
) {
65 return PutEventInternal(std::move(aEvent
), aPriority
, nullptr);
68 bool ThreadEventQueue::PutEventInternal(already_AddRefed
<nsIRunnable
>&& aEvent
,
69 EventQueuePriority aPriority
,
71 // We want to leak the reference when we fail to dispatch it, so that
72 // we won't release the event in a wrong thread.
73 LeakRefPtr
<nsIRunnable
> event(std::move(aEvent
));
74 nsCOMPtr
<nsIThreadObserver
> obs
;
77 // Check if the runnable wants to override the passed-in priority.
78 // Do this outside the lock, so runnables implemented in JS can QI
79 // (and possibly GC) outside of the lock.
81 auto* e
= event
.get(); // can't do_QueryInterface on LeakRefPtr.
82 if (nsCOMPtr
<nsIRunnablePriority
> runnablePrio
= do_QueryInterface(e
)) {
83 uint32_t prio
= nsIRunnablePriority::PRIORITY_NORMAL
;
84 runnablePrio
->GetPriority(&prio
);
85 if (prio
== nsIRunnablePriority::PRIORITY_CONTROL
) {
86 aPriority
= EventQueuePriority::Control
;
87 } else if (prio
== nsIRunnablePriority::PRIORITY_RENDER_BLOCKING
) {
88 aPriority
= EventQueuePriority::RenderBlocking
;
89 } else if (prio
== nsIRunnablePriority::PRIORITY_VSYNC
) {
90 aPriority
= EventQueuePriority::Vsync
;
91 } else if (prio
== nsIRunnablePriority::PRIORITY_INPUT_HIGH
) {
92 aPriority
= EventQueuePriority::InputHigh
;
93 } else if (prio
== nsIRunnablePriority::PRIORITY_MEDIUMHIGH
) {
94 aPriority
= EventQueuePriority::MediumHigh
;
95 } else if (prio
== nsIRunnablePriority::PRIORITY_DEFERRED_TIMERS
) {
96 aPriority
= EventQueuePriority::DeferredTimers
;
97 } else if (prio
== nsIRunnablePriority::PRIORITY_IDLE
) {
98 aPriority
= EventQueuePriority::Idle
;
102 if (aPriority
== EventQueuePriority::Control
&&
103 !StaticPrefs::threads_control_event_queue_enabled()) {
104 aPriority
= EventQueuePriority::MediumHigh
;
108 MutexAutoLock
lock(mLock
);
110 if (mEventsAreDoomed
) {
115 if (!aSink
->mQueue
) {
119 aSink
->mQueue
->PutEvent(event
.take(), aPriority
, lock
);
121 mBaseQueue
->PutEvent(event
.take(), aPriority
, lock
);
124 mEventsAvailable
.Notify();
126 // Make sure to grab the observer before dropping the lock, otherwise the
127 // event that we just placed into the queue could run and eventually delete
128 // this nsThread before the calling thread is scheduled again. We would then
129 // crash while trying to access a dead nsThread.
134 obs
->OnDispatchedEvent();
140 already_AddRefed
<nsIRunnable
> ThreadEventQueue::GetEvent(
141 bool aMayWait
, mozilla::TimeDuration
* aLastEventDelay
) {
142 nsCOMPtr
<nsIRunnable
> event
;
144 // Scope for lock. When we are about to return, we will exit this
145 // scope so we can do some work after releasing the lock but
147 MutexAutoLock
lock(mLock
);
150 const bool noNestedQueue
= mNestedQueues
.IsEmpty();
152 event
= mBaseQueue
->GetEvent(lock
, aLastEventDelay
);
154 // We always get events from the topmost queue when there are nested
157 mNestedQueues
.LastElement().mQueue
->GetEvent(lock
, aLastEventDelay
);
164 // No runnable available. Sleep waiting for one if if we're supposed to.
165 // Otherwise just go ahead and return null.
170 AUTO_PROFILER_LABEL("ThreadEventQueue::GetEvent::Wait", IDLE
);
171 mEventsAvailable
.Wait();
175 return event
.forget();
178 bool ThreadEventQueue::HasPendingEvent() {
179 MutexAutoLock
lock(mLock
);
181 // We always get events from the topmost queue when there are nested queues.
182 if (mNestedQueues
.IsEmpty()) {
183 return mBaseQueue
->HasReadyEvent(lock
);
185 return mNestedQueues
.LastElement().mQueue
->HasReadyEvent(lock
);
189 bool ThreadEventQueue::ShutdownIfNoPendingEvents() {
190 MutexAutoLock
lock(mLock
);
191 if (mNestedQueues
.IsEmpty() && mBaseQueue
->IsEmpty(lock
)) {
192 mEventsAreDoomed
= true;
198 already_AddRefed
<nsISerialEventTarget
> ThreadEventQueue::PushEventQueue() {
199 auto queue
= MakeUnique
<EventQueue
>();
200 RefPtr
<NestedSink
> sink
= new NestedSink(queue
.get(), this);
201 RefPtr
<ThreadEventTarget
> eventTarget
=
202 new ThreadEventTarget(sink
, NS_IsMainThread());
204 MutexAutoLock
lock(mLock
);
206 mNestedQueues
.AppendElement(NestedQueueItem(std::move(queue
), eventTarget
));
207 return eventTarget
.forget();
210 void ThreadEventQueue::PopEventQueue(nsIEventTarget
* aTarget
) {
211 MutexAutoLock
lock(mLock
);
213 MOZ_ASSERT(!mNestedQueues
.IsEmpty());
215 NestedQueueItem
& item
= mNestedQueues
.LastElement();
217 MOZ_ASSERT(aTarget
== item
.mEventTarget
);
219 // Disconnect the event target that will be popped.
220 item
.mEventTarget
->Disconnect(lock
);
222 EventQueue
* prevQueue
=
223 mNestedQueues
.Length() == 1
225 : mNestedQueues
[mNestedQueues
.Length() - 2].mQueue
.get();
227 // Move events from the old queue to the new one.
228 nsCOMPtr
<nsIRunnable
> event
;
230 while ((event
= item
.mQueue
->GetEvent(lock
, &delay
))) {
231 // preserve the event delay so far
232 prevQueue
->PutEvent(event
.forget(), EventQueuePriority::Normal
, lock
,
236 mNestedQueues
.RemoveLastElement();
239 size_t ThreadEventQueue::SizeOfExcludingThis(
240 mozilla::MallocSizeOf aMallocSizeOf
) {
243 n
+= mBaseQueue
->SizeOfIncludingThis(aMallocSizeOf
);
246 MutexAutoLock
lock(mLock
);
247 n
+= mNestedQueues
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
248 for (auto& queue
: mNestedQueues
) {
249 n
+= queue
.mEventTarget
->SizeOfIncludingThis(aMallocSizeOf
);
253 return SynchronizedEventQueue::SizeOfExcludingThis(aMallocSizeOf
) + n
;
256 already_AddRefed
<nsIThreadObserver
> ThreadEventQueue::GetObserver() {
257 MutexAutoLock
lock(mLock
);
258 return do_AddRef(mObserver
);
261 already_AddRefed
<nsIThreadObserver
> ThreadEventQueue::GetObserverOnThread() {
262 // only written on this thread
263 return do_AddRef(mObserver
);
266 void ThreadEventQueue::SetObserver(nsIThreadObserver
* aObserver
) {
267 // Always called from the thread - single writer
268 MutexAutoLock
lock(mLock
);
269 nsCOMPtr observer
= aObserver
;
270 mObserver
.swap(observer
);
271 if (NS_IsMainThread()) {
272 TaskController::Get()->SetThreadObserver(aObserver
);
276 ThreadEventQueue::NestedQueueItem::NestedQueueItem(
277 UniquePtr
<EventQueue
> aQueue
, ThreadEventTarget
* aEventTarget
)
278 : mQueue(std::move(aQueue
)), mEventTarget(aEventTarget
) {}