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/ProfilerRunnable.h"
10 #include "nsIEventTarget.h"
11 #include "nsITargetShutdownTask.h"
12 #include "nsThreadUtils.h"
13 #include "nsQueryObject.h"
17 // Handle for a TaskQueue being tracked by a TaskQueueTracker. When created,
18 // it is registered with the TaskQueueTracker, and when destroyed it is
19 // unregistered. Holds a threadsafe weak reference to the TaskQueue.
20 class TaskQueueTrackerEntry final
21 : private LinkedListElement
<TaskQueueTrackerEntry
> {
23 TaskQueueTrackerEntry(TaskQueueTracker
* aTracker
,
24 const RefPtr
<TaskQueue
>& aQueue
)
25 : mTracker(aTracker
), mQueue(aQueue
) {
26 MutexAutoLock
lock(mTracker
->mMutex
);
27 mTracker
->mEntries
.insertFront(this);
29 ~TaskQueueTrackerEntry() {
30 MutexAutoLock
lock(mTracker
->mMutex
);
31 removeFrom(mTracker
->mEntries
);
34 TaskQueueTrackerEntry(const TaskQueueTrackerEntry
&) = delete;
35 TaskQueueTrackerEntry(TaskQueueTrackerEntry
&&) = delete;
36 TaskQueueTrackerEntry
& operator=(const TaskQueueTrackerEntry
&) = delete;
37 TaskQueueTrackerEntry
& operator=(TaskQueueTrackerEntry
&&) = delete;
39 RefPtr
<TaskQueue
> GetQueue() const { return RefPtr
<TaskQueue
>(mQueue
); }
42 friend class LinkedList
<TaskQueueTrackerEntry
>;
43 friend class LinkedListElement
<TaskQueueTrackerEntry
>;
45 const RefPtr
<TaskQueueTracker
> mTracker
;
46 const ThreadSafeWeakPtr
<TaskQueue
> mQueue
;
49 RefPtr
<TaskQueue
> TaskQueue::Create(already_AddRefed
<nsIEventTarget
> aTarget
,
51 bool aSupportsTailDispatch
) {
52 nsCOMPtr
<nsIEventTarget
> target(std::move(aTarget
));
53 RefPtr
<TaskQueue
> queue
=
54 new TaskQueue(do_AddRef(target
), aName
, aSupportsTailDispatch
);
56 // If |target| is a TaskQueueTracker, register this TaskQueue with it. It will
57 // be unregistered when the TaskQueue is destroyed or shut down.
58 if (RefPtr
<TaskQueueTracker
> tracker
= do_QueryObject(target
)) {
59 MonitorAutoLock
lock(queue
->mQueueMonitor
);
60 queue
->mTrackerEntry
= MakeUnique
<TaskQueueTrackerEntry
>(tracker
, queue
);
66 TaskQueue::TaskQueue(already_AddRefed
<nsIEventTarget
> aTarget
,
67 const char* aName
, bool aSupportsTailDispatch
)
68 : AbstractThread(aSupportsTailDispatch
),
70 mQueueMonitor("TaskQueue::Queue"),
71 mTailDispatcher(nullptr),
76 TaskQueue::~TaskQueue() {
77 // We should never free the TaskQueue if it was destroyed abnormally, meaning
78 // that all cleanup tasks should be complete if we do.
79 MOZ_ASSERT(mShutdownTasks
.IsEmpty());
82 NS_IMPL_ADDREF_INHERITED(TaskQueue
, SupportsThreadSafeWeakPtr
<TaskQueue
>)
83 NS_IMPL_RELEASE_INHERITED(TaskQueue
, SupportsThreadSafeWeakPtr
<TaskQueue
>)
84 NS_IMPL_QUERY_INTERFACE(TaskQueue
, nsIDirectTaskDispatcher
,
85 nsISerialEventTarget
, nsIEventTarget
)
87 TaskDispatcher
& TaskQueue::TailDispatcher() {
88 MOZ_ASSERT(IsCurrentThreadIn());
89 MOZ_ASSERT(mTailDispatcher
);
90 return *mTailDispatcher
;
93 // Note aRunnable is passed by ref to support conditional ownership transfer.
94 // See Dispatch() in TaskQueue.h for more details.
95 nsresult
TaskQueue::DispatchLocked(nsCOMPtr
<nsIRunnable
>& aRunnable
,
96 uint32_t aFlags
, DispatchReason aReason
) {
97 mQueueMonitor
.AssertCurrentThreadOwns();
99 // Continue to allow dispatches after shutdown until the last message has been
100 // processed, at which point no more messages will be accepted.
101 if (mIsShutdown
&& !mIsRunning
) {
102 return NS_ERROR_UNEXPECTED
;
105 AbstractThread
* currentThread
;
106 if (aReason
!= TailDispatch
&& (currentThread
= GetCurrent()) &&
107 RequiresTailDispatch(currentThread
) &&
108 currentThread
->IsTailDispatcherAvailable()) {
109 MOZ_ASSERT(aFlags
== NS_DISPATCH_NORMAL
,
110 "Tail dispatch doesn't support flags");
111 return currentThread
->TailDispatcher().AddTask(this, aRunnable
.forget());
114 LogRunnable::LogDispatch(aRunnable
);
115 mTasks
.Push({std::move(aRunnable
), aFlags
});
120 RefPtr
<nsIRunnable
> runner(new Runner(this));
121 nsresult rv
= mTarget
->Dispatch(runner
.forget(), aFlags
);
123 NS_WARNING("Failed to dispatch runnable to run TaskQueue");
131 nsresult
TaskQueue::RegisterShutdownTask(nsITargetShutdownTask
* aTask
) {
132 NS_ENSURE_ARG(aTask
);
134 MonitorAutoLock
mon(mQueueMonitor
);
136 return NS_ERROR_UNEXPECTED
;
139 MOZ_ASSERT(!mShutdownTasks
.Contains(aTask
));
140 mShutdownTasks
.AppendElement(aTask
);
144 nsresult
TaskQueue::UnregisterShutdownTask(nsITargetShutdownTask
* aTask
) {
145 NS_ENSURE_ARG(aTask
);
147 MonitorAutoLock
mon(mQueueMonitor
);
149 return NS_ERROR_UNEXPECTED
;
152 return mShutdownTasks
.RemoveElement(aTask
) ? NS_OK
: NS_ERROR_UNEXPECTED
;
155 void TaskQueue::AwaitIdle() {
156 MonitorAutoLock
mon(mQueueMonitor
);
160 void TaskQueue::AwaitIdleLocked() {
161 // Make sure there are no tasks for this queue waiting in the caller's tail
163 MOZ_ASSERT_IF(AbstractThread::GetCurrent(),
164 !AbstractThread::GetCurrent()->HasTailTasksFor(this));
166 mQueueMonitor
.AssertCurrentThreadOwns();
167 MOZ_ASSERT(mIsRunning
|| mTasks
.IsEmpty());
169 mQueueMonitor
.Wait();
173 void TaskQueue::AwaitShutdownAndIdle() {
174 MOZ_ASSERT(!IsCurrentThreadIn());
175 // Make sure there are no tasks for this queue waiting in the caller's tail
177 MOZ_ASSERT_IF(AbstractThread::GetCurrent(),
178 !AbstractThread::GetCurrent()->HasTailTasksFor(this));
180 MonitorAutoLock
mon(mQueueMonitor
);
181 while (!mIsShutdown
) {
182 mQueueMonitor
.Wait();
186 RefPtr
<ShutdownPromise
> TaskQueue::BeginShutdown() {
187 // Dispatch any tasks for this queue waiting in the caller's tail dispatcher,
188 // since this is the last opportunity to do so.
189 if (AbstractThread
* currentThread
= AbstractThread::GetCurrent()) {
190 currentThread
->TailDispatchTasksFor(this);
193 MonitorAutoLock
mon(mQueueMonitor
);
194 // Dispatch any cleanup tasks to the queue before we put it into full
196 for (auto& task
: mShutdownTasks
) {
197 nsCOMPtr runnable
{task
->AsRunnable()};
199 DispatchLocked(runnable
, NS_DISPATCH_NORMAL
, TailDispatch
));
201 mShutdownTasks
.Clear();
204 RefPtr
<ShutdownPromise
> p
= mShutdownPromise
.Ensure(__func__
);
205 MaybeResolveShutdown();
210 void TaskQueue::MaybeResolveShutdown() {
211 mQueueMonitor
.AssertCurrentThreadOwns();
212 if (mIsShutdown
&& !mIsRunning
) {
213 mShutdownPromise
.ResolveIfExists(true, __func__
);
214 // Disconnect from our target as we won't try to dispatch any more events.
215 mTrackerEntry
= nullptr;
220 bool TaskQueue::IsEmpty() {
221 MonitorAutoLock
mon(mQueueMonitor
);
222 return mTasks
.IsEmpty();
225 bool TaskQueue::IsCurrentThreadIn() const {
226 bool in
= mRunningThread
== PR_GetCurrentThread();
230 nsresult
TaskQueue::Runner::Run() {
233 MonitorAutoLock
mon(mQueue
->mQueueMonitor
);
234 MOZ_ASSERT(mQueue
->mIsRunning
);
235 if (mQueue
->mTasks
.IsEmpty()) {
236 mQueue
->mIsRunning
= false;
237 mQueue
->MaybeResolveShutdown();
241 event
= mQueue
->mTasks
.Pop();
243 MOZ_ASSERT(event
.event
);
245 // Note that dropping the queue monitor before running the task, and
246 // taking the monitor again after the task has run ensures we have memory
247 // fences enforced. This means that if the object we're calling wasn't
248 // designed to be threadsafe, it will be, provided we're only calling it
249 // in this task queue.
251 AutoTaskGuard
g(mQueue
);
252 SerialEventTargetGuard
tg(mQueue
);
254 LogRunnable::Run
log(event
.event
);
256 AUTO_PROFILE_FOLLOWING_RUNNABLE(event
.event
);
259 // Drop the reference to event. The event will hold a reference to the
260 // object it's calling, and we don't want to keep it alive, it may be
261 // making assumptions what holds references to it. This is especially
262 // the case if the object is waiting for us to shutdown, so that it
263 // can shutdown (like in the MediaDecoderStateMachine's SHUTDOWN case).
264 event
.event
= nullptr;
269 MonitorAutoLock
mon(mQueue
->mQueueMonitor
);
270 if (mQueue
->mTasks
.IsEmpty()) {
271 // No more events to run. Exit the task runner.
272 mQueue
->mIsRunning
= false;
273 mQueue
->MaybeResolveShutdown();
279 // There's at least one more event that we can run. Dispatch this Runner
280 // to the target again to ensure it runs again. Note that we don't just
281 // run in a loop here so that we don't hog the target. This means we may
282 // run on another thread next time, but we rely on the memory fences from
283 // mQueueMonitor for thread safety of non-threadsafe tasks.
286 MonitorAutoLock
mon(mQueue
->mQueueMonitor
);
287 rv
= mQueue
->mTarget
->Dispatch(
288 this, mQueue
->mTasks
.FirstElement().flags
| NS_DISPATCH_AT_END
);
291 // Failed to dispatch, shutdown!
292 MonitorAutoLock
mon(mQueue
->mQueueMonitor
);
293 mQueue
->mIsRunning
= false;
294 mQueue
->mIsShutdown
= true;
295 mQueue
->MaybeResolveShutdown();
302 //-----------------------------------------------------------------------------
303 // nsIDirectTaskDispatcher
304 //-----------------------------------------------------------------------------
307 TaskQueue::DispatchDirectTask(already_AddRefed
<nsIRunnable
> aEvent
) {
308 if (!IsCurrentThreadIn()) {
309 return NS_ERROR_FAILURE
;
311 mDirectTasks
.AddTask(std::move(aEvent
));
315 NS_IMETHODIMP
TaskQueue::DrainDirectTasks() {
316 if (!IsCurrentThreadIn()) {
317 return NS_ERROR_FAILURE
;
319 mDirectTasks
.DrainTasks();
323 NS_IMETHODIMP
TaskQueue::HaveDirectTasks(bool* aValue
) {
324 if (!IsCurrentThreadIn()) {
325 return NS_ERROR_FAILURE
;
328 *aValue
= mDirectTasks
.HaveTasks();
332 nsTArray
<RefPtr
<TaskQueue
>> TaskQueueTracker::GetAllTrackedTaskQueues() {
333 MutexAutoLock
lock(mMutex
);
334 nsTArray
<RefPtr
<TaskQueue
>> queues
;
335 for (auto* entry
: mEntries
) {
336 if (auto queue
= entry
->GetQueue()) {
337 queues
.AppendElement(queue
);
343 TaskQueueTracker::~TaskQueueTracker() = default;
345 } // namespace mozilla