/src/mozilla-central/xpcom/threads/TaskQueue.cpp
Line | Count | Source (jump to first uncovered line) |
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/. */ |
6 | | |
7 | | #include "mozilla/TaskQueue.h" |
8 | | |
9 | | #include "nsISerialEventTarget.h" |
10 | | #include "nsThreadUtils.h" |
11 | | |
12 | | namespace mozilla { |
13 | | |
14 | | class TaskQueue::EventTargetWrapper final : public nsISerialEventTarget |
15 | | { |
16 | | RefPtr<TaskQueue> mTaskQueue; |
17 | | |
18 | | ~EventTargetWrapper() |
19 | 0 | { |
20 | 0 | } |
21 | | |
22 | | public: |
23 | | explicit EventTargetWrapper(TaskQueue* aTaskQueue) |
24 | | : mTaskQueue(aTaskQueue) |
25 | 0 | { |
26 | 0 | MOZ_ASSERT(mTaskQueue); |
27 | 0 | } |
28 | | |
29 | | NS_IMETHOD |
30 | | DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) override |
31 | 0 | { |
32 | 0 | nsCOMPtr<nsIRunnable> ref = aEvent; |
33 | 0 | return Dispatch(ref.forget(), aFlags); |
34 | 0 | } |
35 | | |
36 | | NS_IMETHOD |
37 | | Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) override |
38 | 0 | { |
39 | 0 | nsCOMPtr<nsIRunnable> runnable = aEvent; |
40 | 0 | MonitorAutoLock mon(mTaskQueue->mQueueMonitor); |
41 | 0 | return mTaskQueue->DispatchLocked(/* passed by ref */runnable, |
42 | 0 | NormalDispatch); |
43 | 0 | } |
44 | | |
45 | | NS_IMETHOD |
46 | | DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t aFlags) override |
47 | 0 | { |
48 | 0 | return NS_ERROR_NOT_IMPLEMENTED; |
49 | 0 | } |
50 | | |
51 | | NS_IMETHOD |
52 | | IsOnCurrentThread(bool* aResult) override |
53 | 0 | { |
54 | 0 | *aResult = mTaskQueue->IsCurrentThreadIn(); |
55 | 0 | return NS_OK; |
56 | 0 | } |
57 | | |
58 | | NS_IMETHOD_(bool) |
59 | | IsOnCurrentThreadInfallible() override |
60 | 0 | { |
61 | 0 | return mTaskQueue->mTarget->IsOnCurrentThread(); |
62 | 0 | } |
63 | | |
64 | | NS_DECL_THREADSAFE_ISUPPORTS |
65 | | }; |
66 | | |
67 | | NS_IMPL_ISUPPORTS(TaskQueue::EventTargetWrapper, |
68 | | nsIEventTarget, |
69 | | nsISerialEventTarget) |
70 | | |
71 | | TaskQueue::TaskQueue(already_AddRefed<nsIEventTarget> aTarget, |
72 | | const char* aName, |
73 | | bool aRequireTailDispatch) |
74 | | : AbstractThread(aRequireTailDispatch) |
75 | | , mTarget(aTarget) |
76 | | , mQueueMonitor("TaskQueue::Queue") |
77 | | , mTailDispatcher(nullptr) |
78 | | , mIsRunning(false) |
79 | | , mIsShutdown(false) |
80 | | , mName(aName) |
81 | 0 | { |
82 | 0 | } |
83 | | |
84 | | TaskQueue::TaskQueue(already_AddRefed<nsIEventTarget> aTarget, |
85 | | bool aSupportsTailDispatch) |
86 | | : TaskQueue(std::move(aTarget), "Unnamed", aSupportsTailDispatch) |
87 | 0 | { |
88 | 0 | } |
89 | | |
90 | | TaskQueue::~TaskQueue() |
91 | 0 | { |
92 | 0 | // No one is referencing this TaskQueue anymore, meaning no tasks can be |
93 | 0 | // pending as all Runner hold a reference to this TaskQueue. |
94 | 0 | } |
95 | | |
96 | | TaskDispatcher& |
97 | | TaskQueue::TailDispatcher() |
98 | 0 | { |
99 | 0 | MOZ_ASSERT(IsCurrentThreadIn()); |
100 | 0 | MOZ_ASSERT(mTailDispatcher); |
101 | 0 | return *mTailDispatcher; |
102 | 0 | } |
103 | | |
104 | | // Note aRunnable is passed by ref to support conditional ownership transfer. |
105 | | // See Dispatch() in TaskQueue.h for more details. |
106 | | nsresult |
107 | | TaskQueue::DispatchLocked(nsCOMPtr<nsIRunnable>& aRunnable, |
108 | | DispatchReason aReason) |
109 | 0 | { |
110 | 0 | mQueueMonitor.AssertCurrentThreadOwns(); |
111 | 0 | if (mIsShutdown) { |
112 | 0 | return NS_ERROR_FAILURE; |
113 | 0 | } |
114 | 0 | |
115 | 0 | AbstractThread* currentThread; |
116 | 0 | if (aReason != TailDispatch && (currentThread = GetCurrent()) && RequiresTailDispatch(currentThread)) { |
117 | 0 | return currentThread->TailDispatcher().AddTask(this, aRunnable.forget()); |
118 | 0 | } |
119 | 0 | |
120 | 0 | mTasks.push(aRunnable.forget()); |
121 | 0 | if (mIsRunning) { |
122 | 0 | return NS_OK; |
123 | 0 | } |
124 | 0 | RefPtr<nsIRunnable> runner(new Runner(this)); |
125 | 0 | nsresult rv = mTarget->Dispatch(runner.forget(), NS_DISPATCH_NORMAL); |
126 | 0 | if (NS_FAILED(rv)) { |
127 | 0 | NS_WARNING("Failed to dispatch runnable to run TaskQueue"); |
128 | 0 | return rv; |
129 | 0 | } |
130 | 0 | mIsRunning = true; |
131 | 0 |
|
132 | 0 | return NS_OK; |
133 | 0 | } |
134 | | |
135 | | void |
136 | | TaskQueue::AwaitIdle() |
137 | 0 | { |
138 | 0 | MonitorAutoLock mon(mQueueMonitor); |
139 | 0 | AwaitIdleLocked(); |
140 | 0 | } |
141 | | |
142 | | void |
143 | | TaskQueue::AwaitIdleLocked() |
144 | 0 | { |
145 | 0 | // Make sure there are no tasks for this queue waiting in the caller's tail |
146 | 0 | // dispatcher. |
147 | 0 | MOZ_ASSERT_IF(AbstractThread::GetCurrent(), |
148 | 0 | !AbstractThread::GetCurrent()->HasTailTasksFor(this)); |
149 | 0 |
|
150 | 0 | mQueueMonitor.AssertCurrentThreadOwns(); |
151 | 0 | MOZ_ASSERT(mIsRunning || mTasks.empty()); |
152 | 0 | while (mIsRunning) { |
153 | 0 | mQueueMonitor.Wait(); |
154 | 0 | } |
155 | 0 | } |
156 | | |
157 | | void |
158 | | TaskQueue::AwaitShutdownAndIdle() |
159 | 0 | { |
160 | 0 | MOZ_ASSERT(!IsCurrentThreadIn()); |
161 | 0 | // Make sure there are no tasks for this queue waiting in the caller's tail |
162 | 0 | // dispatcher. |
163 | 0 | MOZ_ASSERT_IF(AbstractThread::GetCurrent(), |
164 | 0 | !AbstractThread::GetCurrent()->HasTailTasksFor(this)); |
165 | 0 |
|
166 | 0 | MonitorAutoLock mon(mQueueMonitor); |
167 | 0 | while (!mIsShutdown) { |
168 | 0 | mQueueMonitor.Wait(); |
169 | 0 | } |
170 | 0 | AwaitIdleLocked(); |
171 | 0 | } |
172 | | |
173 | | RefPtr<ShutdownPromise> |
174 | | TaskQueue::BeginShutdown() |
175 | 0 | { |
176 | 0 | // Dispatch any tasks for this queue waiting in the caller's tail dispatcher, |
177 | 0 | // since this is the last opportunity to do so. |
178 | 0 | if (AbstractThread* currentThread = AbstractThread::GetCurrent()) { |
179 | 0 | currentThread->TailDispatchTasksFor(this); |
180 | 0 | } |
181 | 0 | MonitorAutoLock mon(mQueueMonitor); |
182 | 0 | mIsShutdown = true; |
183 | 0 | RefPtr<ShutdownPromise> p = mShutdownPromise.Ensure(__func__); |
184 | 0 | MaybeResolveShutdown(); |
185 | 0 | mon.NotifyAll(); |
186 | 0 | return p; |
187 | 0 | } |
188 | | |
189 | | bool |
190 | | TaskQueue::IsEmpty() |
191 | 0 | { |
192 | 0 | MonitorAutoLock mon(mQueueMonitor); |
193 | 0 | return mTasks.empty(); |
194 | 0 | } |
195 | | |
196 | | bool |
197 | | TaskQueue::IsCurrentThreadIn() |
198 | 0 | { |
199 | 0 | bool in = mRunningThread == GetCurrentPhysicalThread(); |
200 | 0 | return in; |
201 | 0 | } |
202 | | |
203 | | already_AddRefed<nsISerialEventTarget> |
204 | | TaskQueue::WrapAsEventTarget() |
205 | 0 | { |
206 | 0 | nsCOMPtr<nsISerialEventTarget> ref = new EventTargetWrapper(this); |
207 | 0 | return ref.forget(); |
208 | 0 | } |
209 | | |
210 | | nsresult |
211 | | TaskQueue::Runner::Run() |
212 | 0 | { |
213 | 0 | RefPtr<nsIRunnable> event; |
214 | 0 | { |
215 | 0 | MonitorAutoLock mon(mQueue->mQueueMonitor); |
216 | 0 | MOZ_ASSERT(mQueue->mIsRunning); |
217 | 0 | if (mQueue->mTasks.empty()) { |
218 | 0 | mQueue->mIsRunning = false; |
219 | 0 | mQueue->MaybeResolveShutdown(); |
220 | 0 | mon.NotifyAll(); |
221 | 0 | return NS_OK; |
222 | 0 | } |
223 | 0 | event = mQueue->mTasks.front().forget(); |
224 | 0 | mQueue->mTasks.pop(); |
225 | 0 | } |
226 | 0 | MOZ_ASSERT(event); |
227 | 0 |
|
228 | 0 | // Note that dropping the queue monitor before running the task, and |
229 | 0 | // taking the monitor again after the task has run ensures we have memory |
230 | 0 | // fences enforced. This means that if the object we're calling wasn't |
231 | 0 | // designed to be threadsafe, it will be, provided we're only calling it |
232 | 0 | // in this task queue. |
233 | 0 | { |
234 | 0 | AutoTaskGuard g(mQueue); |
235 | 0 | event->Run(); |
236 | 0 | } |
237 | 0 |
|
238 | 0 | // Drop the reference to event. The event will hold a reference to the |
239 | 0 | // object it's calling, and we don't want to keep it alive, it may be |
240 | 0 | // making assumptions what holds references to it. This is especially |
241 | 0 | // the case if the object is waiting for us to shutdown, so that it |
242 | 0 | // can shutdown (like in the MediaDecoderStateMachine's SHUTDOWN case). |
243 | 0 | event = nullptr; |
244 | 0 |
|
245 | 0 | { |
246 | 0 | MonitorAutoLock mon(mQueue->mQueueMonitor); |
247 | 0 | if (mQueue->mTasks.empty()) { |
248 | 0 | // No more events to run. Exit the task runner. |
249 | 0 | mQueue->mIsRunning = false; |
250 | 0 | mQueue->MaybeResolveShutdown(); |
251 | 0 | mon.NotifyAll(); |
252 | 0 | return NS_OK; |
253 | 0 | } |
254 | 0 | } |
255 | 0 | |
256 | 0 | // There's at least one more event that we can run. Dispatch this Runner |
257 | 0 | // to the target again to ensure it runs again. Note that we don't just |
258 | 0 | // run in a loop here so that we don't hog the target. This means we may |
259 | 0 | // run on another thread next time, but we rely on the memory fences from |
260 | 0 | // mQueueMonitor for thread safety of non-threadsafe tasks. |
261 | 0 | nsresult rv = mQueue->mTarget->Dispatch(this, NS_DISPATCH_AT_END); |
262 | 0 | if (NS_FAILED(rv)) { |
263 | 0 | // Failed to dispatch, shutdown! |
264 | 0 | MonitorAutoLock mon(mQueue->mQueueMonitor); |
265 | 0 | mQueue->mIsRunning = false; |
266 | 0 | mQueue->mIsShutdown = true; |
267 | 0 | mQueue->MaybeResolveShutdown(); |
268 | 0 | mon.NotifyAll(); |
269 | 0 | } |
270 | 0 |
|
271 | 0 | return NS_OK; |
272 | 0 | } |
273 | | |
274 | | } // namespace mozilla |