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