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 #ifndef XPCOM_THREADS_TASKDISPATCHER_H_
8 #define XPCOM_THREADS_TASKDISPATCHER_H_
12 #include "mozilla/AbstractThread.h"
13 #include "mozilla/Maybe.h"
14 #include "mozilla/ProfilerRunnable.h"
15 #include "mozilla/UniquePtr.h"
16 #include "nsIDirectTaskDispatcher.h"
17 #include "nsISupportsImpl.h"
19 #include "nsThreadUtils.h"
23 class SimpleTaskQueue
{
25 SimpleTaskQueue() = default;
26 virtual ~SimpleTaskQueue() = default;
28 void AddTask(already_AddRefed
<nsIRunnable
> aRunnable
) {
32 mTasks
->push(std::move(aRunnable
));
39 auto& queue
= mTasks
.ref();
40 while (!queue
.empty()) {
41 nsCOMPtr
<nsIRunnable
> r
= std::move(queue
.front());
43 AUTO_PROFILE_FOLLOWING_RUNNABLE(r
);
48 bool HaveTasks() const { return mTasks
&& !mTasks
->empty(); }
51 // We use a Maybe<> because (a) when used for DirectTasks it often doesn't get
52 // anything put into it, and (b) the std::queue implementation in GNU
53 // libstdc++ does two largish heap allocations when creating a new std::queue.
54 Maybe
<std::queue
<nsCOMPtr
<nsIRunnable
>>> mTasks
;
58 * A classic approach to cross-thread communication is to dispatch asynchronous
59 * runnables to perform updates on other threads. This generally works well, but
60 * there are sometimes reasons why we might want to delay the actual dispatch of
61 * these tasks until a specified moment. At present, this is primarily useful to
62 * ensure that mirrored state gets updated atomically - but there may be other
63 * applications as well.
65 * TaskDispatcher is a general abstract class that accepts tasks and dispatches
66 * them at some later point. These groups of tasks are per-target-thread, and
67 * contain separate queues for several kinds of tasks (see comments below). -
68 * "state change tasks" (which run first, and are intended to be used to update
69 * the value held by mirrors), and regular tasks, which are other arbitrary
70 * operations that the are gated to run after all the state changes have
73 class TaskDispatcher
{
75 TaskDispatcher() = default;
76 virtual ~TaskDispatcher() = default;
78 // Direct tasks are run directly (rather than dispatched asynchronously) when
79 // the tail dispatcher fires. A direct task may cause other tasks to be added
80 // to the tail dispatcher.
81 virtual void AddDirectTask(already_AddRefed
<nsIRunnable
> aRunnable
) = 0;
83 // State change tasks are dispatched asynchronously always run before regular
84 // tasks. They are intended to be used to update the value held by mirrors
85 // before any other dispatched tasks are run on the target thread.
86 virtual void AddStateChangeTask(AbstractThread
* aThread
,
87 already_AddRefed
<nsIRunnable
> aRunnable
) = 0;
89 // Regular tasks are dispatched asynchronously, and run after state change
91 virtual nsresult
AddTask(AbstractThread
* aThread
,
92 already_AddRefed
<nsIRunnable
> aRunnable
) = 0;
94 virtual nsresult
DispatchTasksFor(AbstractThread
* aThread
) = 0;
95 virtual bool HasTasksFor(AbstractThread
* aThread
) = 0;
96 virtual void DrainDirectTasks() = 0;
100 * AutoTaskDispatcher is a stack-scoped TaskDispatcher implementation that fires
101 * its queued tasks when it is popped off the stack.
103 class AutoTaskDispatcher
: public TaskDispatcher
{
105 explicit AutoTaskDispatcher(nsIDirectTaskDispatcher
* aDirectTaskDispatcher
,
106 bool aIsTailDispatcher
= false)
107 : mDirectTaskDispatcher(aDirectTaskDispatcher
),
108 mIsTailDispatcher(aIsTailDispatcher
) {}
110 ~AutoTaskDispatcher() {
111 // Given that direct tasks may trigger other code that uses the tail
112 // dispatcher, it's better to avoid processing them in the tail dispatcher's
113 // destructor. So we require TailDispatchers to manually invoke
114 // DrainDirectTasks before the AutoTaskDispatcher gets destroyed. In truth,
115 // this is only necessary in the case where this AutoTaskDispatcher can be
116 // accessed by the direct tasks it dispatches (true for TailDispatchers, but
117 // potentially not true for other hypothetical AutoTaskDispatchers). Feel
118 // free to loosen this restriction to apply only to mIsTailDispatcher if a
119 // use-case requires it.
120 MOZ_ASSERT(!HaveDirectTasks());
122 for (size_t i
= 0; i
< mTaskGroups
.Length(); ++i
) {
123 DispatchTaskGroup(std::move(mTaskGroups
[i
]));
127 bool HaveDirectTasks() {
128 return mDirectTaskDispatcher
&& mDirectTaskDispatcher
->HaveDirectTasks();
131 void DrainDirectTasks() override
{
132 if (mDirectTaskDispatcher
) {
133 mDirectTaskDispatcher
->DrainDirectTasks();
137 void AddDirectTask(already_AddRefed
<nsIRunnable
> aRunnable
) override
{
138 MOZ_ASSERT(mDirectTaskDispatcher
);
139 mDirectTaskDispatcher
->DispatchDirectTask(std::move(aRunnable
));
142 void AddStateChangeTask(AbstractThread
* aThread
,
143 already_AddRefed
<nsIRunnable
> aRunnable
) override
{
144 nsCOMPtr
<nsIRunnable
> r
= aRunnable
;
145 MOZ_RELEASE_ASSERT(r
);
146 EnsureTaskGroup(aThread
).mStateChangeTasks
.AppendElement(r
.forget());
149 nsresult
AddTask(AbstractThread
* aThread
,
150 already_AddRefed
<nsIRunnable
> aRunnable
) override
{
151 nsCOMPtr
<nsIRunnable
> r
= aRunnable
;
152 MOZ_RELEASE_ASSERT(r
);
153 // To preserve the event order, we need to append a new group if the last
154 // group is not targeted for |aThread|.
155 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1318226&mark=0-3#c0
156 // for the details of the issue.
157 if (mTaskGroups
.Length() == 0 ||
158 mTaskGroups
.LastElement()->mThread
!= aThread
) {
159 mTaskGroups
.AppendElement(new PerThreadTaskGroup(aThread
));
162 PerThreadTaskGroup
& group
= *mTaskGroups
.LastElement();
163 group
.mRegularTasks
.AppendElement(r
.forget());
168 bool HasTasksFor(AbstractThread
* aThread
) override
{
169 return !!GetTaskGroup(aThread
) ||
170 (aThread
== AbstractThread::GetCurrent() && HaveDirectTasks());
173 nsresult
DispatchTasksFor(AbstractThread
* aThread
) override
{
176 // Dispatch all groups that match |aThread|.
177 for (size_t i
= 0; i
< mTaskGroups
.Length(); ++i
) {
178 if (mTaskGroups
[i
]->mThread
== aThread
) {
179 nsresult rv2
= DispatchTaskGroup(std::move(mTaskGroups
[i
]));
181 if (NS_WARN_IF(NS_FAILED(rv2
)) && NS_SUCCEEDED(rv
)) {
182 // We should try our best to call DispatchTaskGroup() as much as
183 // possible and return an error if any of DispatchTaskGroup() calls
188 mTaskGroups
.RemoveElementAt(i
--);
196 struct PerThreadTaskGroup
{
198 explicit PerThreadTaskGroup(AbstractThread
* aThread
) : mThread(aThread
) {
199 MOZ_COUNT_CTOR(PerThreadTaskGroup
);
202 MOZ_COUNTED_DTOR(PerThreadTaskGroup
)
204 RefPtr
<AbstractThread
> mThread
;
205 nsTArray
<nsCOMPtr
<nsIRunnable
>> mStateChangeTasks
;
206 nsTArray
<nsCOMPtr
<nsIRunnable
>> mRegularTasks
;
209 class TaskGroupRunnable
: public Runnable
{
211 explicit TaskGroupRunnable(UniquePtr
<PerThreadTaskGroup
>&& aTasks
)
212 : Runnable("AutoTaskDispatcher::TaskGroupRunnable"),
213 mTasks(std::move(aTasks
)) {}
215 NS_IMETHOD
Run() override
{
216 // State change tasks get run all together before any code is run, so
217 // that all state changes are made in an atomic unit.
218 for (size_t i
= 0; i
< mTasks
->mStateChangeTasks
.Length(); ++i
) {
219 mTasks
->mStateChangeTasks
[i
]->Run();
222 // Once the state changes have completed, drain any direct tasks
223 // generated by those state changes (i.e. watcher notification tasks).
224 // This needs to be outside the loop because we don't want to run code
225 // that might observe intermediate states.
226 MaybeDrainDirectTasks();
228 for (size_t i
= 0; i
< mTasks
->mRegularTasks
.Length(); ++i
) {
229 AUTO_PROFILE_FOLLOWING_RUNNABLE(mTasks
->mRegularTasks
[i
]);
230 mTasks
->mRegularTasks
[i
]->Run();
232 // Scope direct tasks tightly to the task that generated them.
233 MaybeDrainDirectTasks();
240 void MaybeDrainDirectTasks() {
241 AbstractThread
* currentThread
= AbstractThread::GetCurrent();
242 if (currentThread
&& currentThread
->MightHaveTailTasks()) {
243 currentThread
->TailDispatcher().DrainDirectTasks();
247 UniquePtr
<PerThreadTaskGroup
> mTasks
;
250 PerThreadTaskGroup
& EnsureTaskGroup(AbstractThread
* aThread
) {
251 PerThreadTaskGroup
* existing
= GetTaskGroup(aThread
);
256 mTaskGroups
.AppendElement(new PerThreadTaskGroup(aThread
));
257 return *mTaskGroups
.LastElement();
260 PerThreadTaskGroup
* GetTaskGroup(AbstractThread
* aThread
) {
261 for (size_t i
= 0; i
< mTaskGroups
.Length(); ++i
) {
262 if (mTaskGroups
[i
]->mThread
== aThread
) {
263 return mTaskGroups
[i
].get();
271 nsresult
DispatchTaskGroup(UniquePtr
<PerThreadTaskGroup
> aGroup
) {
272 RefPtr
<AbstractThread
> thread
= aGroup
->mThread
;
274 AbstractThread::DispatchReason reason
=
275 mIsTailDispatcher
? AbstractThread::TailDispatch
276 : AbstractThread::NormalDispatch
;
277 nsCOMPtr
<nsIRunnable
> r
= new TaskGroupRunnable(std::move(aGroup
));
278 return thread
->Dispatch(r
.forget(), reason
);
281 // Task groups, organized by thread.
282 nsTArray
<UniquePtr
<PerThreadTaskGroup
>> mTaskGroups
;
284 nsCOMPtr
<nsIDirectTaskDispatcher
> mDirectTaskDispatcher
;
285 // True if this TaskDispatcher represents the tail dispatcher for the thread
286 // upon which it runs.
287 const bool mIsTailDispatcher
;
290 // Little utility class to allow declaring AutoTaskDispatcher as a default
291 // parameter for methods that take a TaskDispatcher&.
292 template <typename T
>
295 PassByRef() = default;
296 operator T
&() { return mVal
; }
302 } // namespace mozilla