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/TaskQueue.h"
9 #include "mozilla/DelayedRunnable.h"
10 #include "mozilla/ProfilerRunnable.h"
11 #include "nsIEventTarget.h"
12 #include "nsITargetShutdownTask.h"
13 #include "nsThreadUtils.h"
14 #include "nsQueryObject.h"
18 // Handle for a TaskQueue being tracked by a TaskQueueTracker. When created,
19 // it is registered with the TaskQueueTracker, and when destroyed it is
20 // unregistered. Holds a threadsafe weak reference to the TaskQueue.
21 class TaskQueueTrackerEntry final
22 : private LinkedListElement
<TaskQueueTrackerEntry
> {
24 TaskQueueTrackerEntry(TaskQueueTracker
* aTracker
,
25 const RefPtr
<TaskQueue
>& aQueue
)
26 : mTracker(aTracker
), mQueue(aQueue
) {
27 MutexAutoLock
lock(mTracker
->mMutex
);
28 mTracker
->mEntries
.insertFront(this);
30 ~TaskQueueTrackerEntry() {
31 MutexAutoLock
lock(mTracker
->mMutex
);
32 removeFrom(mTracker
->mEntries
);
35 TaskQueueTrackerEntry(const TaskQueueTrackerEntry
&) = delete;
36 TaskQueueTrackerEntry(TaskQueueTrackerEntry
&&) = delete;
37 TaskQueueTrackerEntry
& operator=(const TaskQueueTrackerEntry
&) = delete;
38 TaskQueueTrackerEntry
& operator=(TaskQueueTrackerEntry
&&) = delete;
40 RefPtr
<TaskQueue
> GetQueue() const { return RefPtr
<TaskQueue
>(mQueue
); }
43 friend class LinkedList
<TaskQueueTrackerEntry
>;
44 friend class LinkedListElement
<TaskQueueTrackerEntry
>;
46 const RefPtr
<TaskQueueTracker
> mTracker
;
47 const ThreadSafeWeakPtr
<TaskQueue
> mQueue
;
50 RefPtr
<TaskQueue
> TaskQueue::Create(already_AddRefed
<nsIEventTarget
> aTarget
,
52 bool aSupportsTailDispatch
) {
53 nsCOMPtr
<nsIEventTarget
> target(std::move(aTarget
));
54 RefPtr
<TaskQueue
> queue
=
55 new TaskQueue(do_AddRef(target
), aName
, aSupportsTailDispatch
);
57 // If |target| is a TaskQueueTracker, register this TaskQueue with it. It will
58 // be unregistered when the TaskQueue is destroyed or shut down.
59 if (RefPtr
<TaskQueueTracker
> tracker
= do_QueryObject(target
)) {
60 MonitorAutoLock
lock(queue
->mQueueMonitor
);
61 queue
->mTrackerEntry
= MakeUnique
<TaskQueueTrackerEntry
>(tracker
, queue
);
67 TaskQueue::TaskQueue(already_AddRefed
<nsIEventTarget
> aTarget
,
68 const char* aName
, bool aSupportsTailDispatch
)
69 : AbstractThread(aSupportsTailDispatch
),
71 mQueueMonitor("TaskQueue::Queue"),
72 mTailDispatcher(nullptr),
77 TaskQueue::~TaskQueue() {
78 // We should never free the TaskQueue if it was destroyed abnormally, meaning
79 // that all cleanup tasks should be complete if we do.
80 MOZ_ASSERT(mShutdownTasks
.IsEmpty());
83 NS_IMPL_ADDREF_INHERITED(TaskQueue
, SupportsThreadSafeWeakPtr
<TaskQueue
>)
84 NS_IMPL_RELEASE_INHERITED(TaskQueue
, SupportsThreadSafeWeakPtr
<TaskQueue
>)
85 NS_IMPL_QUERY_INTERFACE(TaskQueue
, nsIDirectTaskDispatcher
,
86 nsISerialEventTarget
, nsIEventTarget
)
88 TaskDispatcher
& TaskQueue::TailDispatcher() {
89 MOZ_ASSERT(IsCurrentThreadIn());
90 MOZ_ASSERT(mTailDispatcher
);
91 return *mTailDispatcher
;
94 // Note aRunnable is passed by ref to support conditional ownership transfer.
95 // See Dispatch() in TaskQueue.h for more details.
96 nsresult
TaskQueue::DispatchLocked(nsCOMPtr
<nsIRunnable
>& aRunnable
,
97 uint32_t aFlags
, DispatchReason aReason
) {
98 mQueueMonitor
.AssertCurrentThreadOwns();
100 // Continue to allow dispatches after shutdown until the last message has been
101 // processed, at which point no more messages will be accepted.
102 if (mIsShutdown
&& !mIsRunning
) {
103 return NS_ERROR_UNEXPECTED
;
106 AbstractThread
* currentThread
;
107 if (aReason
!= TailDispatch
&& (currentThread
= GetCurrent()) &&
108 RequiresTailDispatch(currentThread
) &&
109 currentThread
->IsTailDispatcherAvailable()) {
110 MOZ_ASSERT(aFlags
== NS_DISPATCH_NORMAL
,
111 "Tail dispatch doesn't support flags");
112 return currentThread
->TailDispatcher().AddTask(this, aRunnable
.forget());
115 LogRunnable::LogDispatch(aRunnable
);
116 mTasks
.Push({std::move(aRunnable
), aFlags
});
121 RefPtr
<nsIRunnable
> runner(new Runner(this));
122 nsresult rv
= mTarget
->Dispatch(runner
.forget(), aFlags
);
124 NS_WARNING("Failed to dispatch runnable to run TaskQueue");
132 nsresult
TaskQueue::RegisterShutdownTask(nsITargetShutdownTask
* aTask
) {
133 NS_ENSURE_ARG(aTask
);
135 MonitorAutoLock
mon(mQueueMonitor
);
137 return NS_ERROR_UNEXPECTED
;
140 MOZ_ASSERT(!mShutdownTasks
.Contains(aTask
));
141 mShutdownTasks
.AppendElement(aTask
);
145 nsresult
TaskQueue::UnregisterShutdownTask(nsITargetShutdownTask
* aTask
) {
146 NS_ENSURE_ARG(aTask
);
148 MonitorAutoLock
mon(mQueueMonitor
);
150 return NS_ERROR_UNEXPECTED
;
153 return mShutdownTasks
.RemoveElement(aTask
) ? NS_OK
: NS_ERROR_UNEXPECTED
;
156 void TaskQueue::AwaitIdle() {
157 MonitorAutoLock
mon(mQueueMonitor
);
161 void TaskQueue::AwaitIdleLocked() {
162 // Make sure there are no tasks for this queue waiting in the caller's tail
164 MOZ_ASSERT_IF(AbstractThread::GetCurrent(),
165 !AbstractThread::GetCurrent()->HasTailTasksFor(this));
167 mQueueMonitor
.AssertCurrentThreadOwns();
168 MOZ_ASSERT(mIsRunning
|| mTasks
.IsEmpty());
170 mQueueMonitor
.Wait();
174 void TaskQueue::AwaitShutdownAndIdle() {
175 MOZ_ASSERT(!IsCurrentThreadIn());
176 // Make sure there are no tasks for this queue waiting in the caller's tail
178 MOZ_ASSERT_IF(AbstractThread::GetCurrent(),
179 !AbstractThread::GetCurrent()->HasTailTasksFor(this));
181 MonitorAutoLock
mon(mQueueMonitor
);
182 while (!mIsShutdown
) {
183 mQueueMonitor
.Wait();
187 RefPtr
<ShutdownPromise
> TaskQueue::BeginShutdown() {
188 // Dispatch any tasks for this queue waiting in the caller's tail dispatcher,
189 // since this is the last opportunity to do so.
190 if (AbstractThread
* currentThread
= AbstractThread::GetCurrent()) {
191 currentThread
->TailDispatchTasksFor(this);
194 MonitorAutoLock
mon(mQueueMonitor
);
195 // Dispatch any cleanup tasks to the queue before we put it into full
197 for (auto& task
: mShutdownTasks
) {
198 nsCOMPtr runnable
{task
->AsRunnable()};
200 DispatchLocked(runnable
, NS_DISPATCH_NORMAL
, TailDispatch
));
202 mShutdownTasks
.Clear();
205 RefPtr
<ShutdownPromise
> p
= mShutdownPromise
.Ensure(__func__
);
206 MaybeResolveShutdown();
211 void TaskQueue::MaybeResolveShutdown() {
212 mQueueMonitor
.AssertCurrentThreadOwns();
213 if (mIsShutdown
&& !mIsRunning
) {
214 mShutdownPromise
.ResolveIfExists(true, __func__
);
215 // Disconnect from our target as we won't try to dispatch any more events.
216 mTrackerEntry
= nullptr;
221 bool TaskQueue::IsEmpty() {
222 MonitorAutoLock
mon(mQueueMonitor
);
223 return mTasks
.IsEmpty();
226 bool TaskQueue::IsCurrentThreadIn() const {
227 bool in
= mRunningThread
== PR_GetCurrentThread();
231 nsresult
TaskQueue::Runner::Run() {
234 MonitorAutoLock
mon(mQueue
->mQueueMonitor
);
235 MOZ_ASSERT(mQueue
->mIsRunning
);
236 if (mQueue
->mTasks
.IsEmpty()) {
237 mQueue
->mIsRunning
= false;
238 mQueue
->MaybeResolveShutdown();
242 event
= std::move(mQueue
->mTasks
.FirstElement());
243 mQueue
->mTasks
.Pop();
245 MOZ_ASSERT(event
.event
);
247 // Note that dropping the queue monitor before running the task, and
248 // taking the monitor again after the task has run ensures we have memory
249 // fences enforced. This means that if the object we're calling wasn't
250 // designed to be threadsafe, it will be, provided we're only calling it
251 // in this task queue.
253 AutoTaskGuard
g(mQueue
);
254 SerialEventTargetGuard
tg(mQueue
);
256 LogRunnable::Run
log(event
.event
);
258 AUTO_PROFILE_FOLLOWING_RUNNABLE(event
.event
);
261 // Drop the reference to event. The event will hold a reference to the
262 // object it's calling, and we don't want to keep it alive, it may be
263 // making assumptions what holds references to it. This is especially
264 // the case if the object is waiting for us to shutdown, so that it
265 // can shutdown (like in the MediaDecoderStateMachine's SHUTDOWN case).
266 event
.event
= nullptr;
271 MonitorAutoLock
mon(mQueue
->mQueueMonitor
);
272 if (mQueue
->mTasks
.IsEmpty()) {
273 // No more events to run. Exit the task runner.
274 mQueue
->mIsRunning
= false;
275 mQueue
->MaybeResolveShutdown();
281 // There's at least one more event that we can run. Dispatch this Runner
282 // to the target again to ensure it runs again. Note that we don't just
283 // run in a loop here so that we don't hog the target. This means we may
284 // run on another thread next time, but we rely on the memory fences from
285 // mQueueMonitor for thread safety of non-threadsafe tasks.
288 MonitorAutoLock
mon(mQueue
->mQueueMonitor
);
289 rv
= mQueue
->mTarget
->Dispatch(
290 this, mQueue
->mTasks
.FirstElement().flags
| NS_DISPATCH_AT_END
);
293 // Failed to dispatch, shutdown!
294 MonitorAutoLock
mon(mQueue
->mQueueMonitor
);
295 mQueue
->mIsRunning
= false;
296 mQueue
->mIsShutdown
= true;
297 mQueue
->MaybeResolveShutdown();
304 //-----------------------------------------------------------------------------
305 // nsIDirectTaskDispatcher
306 //-----------------------------------------------------------------------------
309 TaskQueue::DispatchDirectTask(already_AddRefed
<nsIRunnable
> aEvent
) {
310 if (!IsCurrentThreadIn()) {
311 return NS_ERROR_FAILURE
;
313 mDirectTasks
.AddTask(std::move(aEvent
));
317 NS_IMETHODIMP
TaskQueue::DrainDirectTasks() {
318 if (!IsCurrentThreadIn()) {
319 return NS_ERROR_FAILURE
;
321 mDirectTasks
.DrainTasks();
325 NS_IMETHODIMP
TaskQueue::HaveDirectTasks(bool* aValue
) {
326 if (!IsCurrentThreadIn()) {
327 return NS_ERROR_FAILURE
;
330 *aValue
= mDirectTasks
.HaveTasks();
334 nsTArray
<RefPtr
<TaskQueue
>> TaskQueueTracker::GetAllTrackedTaskQueues() {
335 MutexAutoLock
lock(mMutex
);
336 nsTArray
<RefPtr
<TaskQueue
>> queues
;
337 for (auto* entry
: mEntries
) {
338 if (auto queue
= entry
->GetQueue()) {
339 queues
.AppendElement(queue
);
345 TaskQueueTracker::~TaskQueueTracker() = default;
347 } // namespace mozilla