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/AbstractThread.h"
8
9 #include "mozilla/ClearOnShutdown.h"
10 #include "mozilla/Maybe.h"
11 #include "mozilla/MozPromise.h" // We initialize the MozPromise logging in this file.
12 #include "mozilla/StaticPtr.h"
13 #include "mozilla/StateWatching.h" // We initialize the StateWatching logging in this file.
14 #include "mozilla/TaskQueue.h"
15 #include "mozilla/TaskDispatcher.h"
16 #include "mozilla/Unused.h"
17
18 #include "nsThreadUtils.h"
19 #include "nsContentUtils.h"
20 #include "nsServiceManagerUtils.h"
21
22 namespace mozilla {
23
24 LazyLogModule gMozPromiseLog("MozPromise");
25 LazyLogModule gStateWatchingLog("StateWatching");
26
27 StaticRefPtr<AbstractThread> sMainThread;
28 MOZ_THREAD_LOCAL(AbstractThread*) AbstractThread::sCurrentThreadTLS;
29
30 class EventTargetWrapper : public AbstractThread {
31 public:
EventTargetWrapper(nsIEventTarget * aTarget,bool aRequireTailDispatch)32 explicit EventTargetWrapper(nsIEventTarget* aTarget,
33 bool aRequireTailDispatch)
34 : AbstractThread(aRequireTailDispatch), mTarget(aTarget) {
35 // Our current mechanism of implementing tail dispatch is appshell-specific.
36 // This is because a very similar mechanism already exists on the main
37 // thread, and we want to avoid making event dispatch on the main thread
38 // more complicated than it already is.
39 //
40 // If you need to use tail dispatch on other XPCOM threads, you'll need to
41 // implement an nsIThreadObserver to fire the tail dispatcher at the
42 // appropriate times. You will also need to modify this assertion.
43 MOZ_ASSERT_IF(aRequireTailDispatch,
44 NS_IsMainThread() && aTarget->IsOnCurrentThread());
45 }
46
Dispatch(already_AddRefed<nsIRunnable> aRunnable,DispatchReason aReason=NormalDispatch)47 virtual nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable,
48 DispatchReason aReason = NormalDispatch) override {
49 AbstractThread* currentThread;
50 if (aReason != TailDispatch && (currentThread = GetCurrent()) &&
51 RequiresTailDispatch(currentThread)) {
52 return currentThread->TailDispatcher().AddTask(this, Move(aRunnable));
53 }
54
55 RefPtr<nsIRunnable> runner(
56 new Runner(this, Move(aRunnable),
57 false /* already drained by TaskGroupRunnable */));
58 return mTarget->Dispatch(runner.forget(), NS_DISPATCH_NORMAL);
59 }
60
61 // Prevent a GCC warning about the other overload of Dispatch being hidden.
62 using AbstractThread::Dispatch;
63
IsCurrentThreadIn()64 virtual bool IsCurrentThreadIn() override {
65 return mTarget->IsOnCurrentThread();
66 }
67
FireTailDispatcher()68 void FireTailDispatcher() {
69 AutoEnter context(this);
70
71 MOZ_DIAGNOSTIC_ASSERT(mTailDispatcher.isSome());
72 mTailDispatcher.ref().DrainDirectTasks();
73 mTailDispatcher.reset();
74 }
75
TailDispatcher()76 virtual TaskDispatcher& TailDispatcher() override {
77 MOZ_ASSERT(IsCurrentThreadIn());
78 if (!mTailDispatcher.isSome()) {
79 mTailDispatcher.emplace(/* aIsTailDispatcher = */ true);
80
81 nsCOMPtr<nsIRunnable> event =
82 NewRunnableMethod("EventTargetWrapper::FireTailDispatcher", this,
83 &EventTargetWrapper::FireTailDispatcher);
84 nsContentUtils::RunInStableState(event.forget());
85 }
86
87 return mTailDispatcher.ref();
88 }
89
MightHaveTailTasks()90 virtual bool MightHaveTailTasks() override {
91 return mTailDispatcher.isSome();
92 }
93
AsEventTarget()94 virtual nsIEventTarget* AsEventTarget() override { return mTarget; }
95
96 private:
97 nsCOMPtr<nsIThread> mRunningThread;
98 RefPtr<nsIEventTarget> mTarget;
99 Maybe<AutoTaskDispatcher> mTailDispatcher;
100
CreateDirectTaskDrainer(already_AddRefed<nsIRunnable> aRunnable)101 virtual already_AddRefed<nsIRunnable> CreateDirectTaskDrainer(
102 already_AddRefed<nsIRunnable> aRunnable) override {
103 RefPtr<Runner> runner =
104 new Runner(this, Move(aRunnable), /* aDrainDirectTasks */ true);
105 return runner.forget();
106 }
107
108 class Runner : public CancelableRunnable {
109 class MOZ_STACK_CLASS AutoTaskGuard final {
110 public:
AutoTaskGuard(EventTargetWrapper * aThread)111 explicit AutoTaskGuard(EventTargetWrapper* aThread)
112 : mLastCurrentThread(nullptr) {
113 MOZ_ASSERT(aThread);
114 mLastCurrentThread = sCurrentThreadTLS.get();
115 sCurrentThreadTLS.set(aThread);
116 }
117
~AutoTaskGuard()118 ~AutoTaskGuard() { sCurrentThreadTLS.set(mLastCurrentThread); }
119
120 private:
121 AbstractThread* mLastCurrentThread;
122 };
123
124 public:
Runner(EventTargetWrapper * aThread,already_AddRefed<nsIRunnable> aRunnable,bool aDrainDirectTasks)125 explicit Runner(EventTargetWrapper* aThread,
126 already_AddRefed<nsIRunnable> aRunnable,
127 bool aDrainDirectTasks)
128 : CancelableRunnable("EventTargetWrapper::Runner"),
129 mThread(aThread),
130 mRunnable(aRunnable),
131 mDrainDirectTasks(aDrainDirectTasks) {}
132
Run()133 NS_IMETHOD Run() override {
134 AutoTaskGuard taskGuard(mThread);
135
136 MOZ_ASSERT(mThread == AbstractThread::GetCurrent());
137 MOZ_ASSERT(mThread->IsCurrentThreadIn());
138 nsresult rv = mRunnable->Run();
139
140 if (mDrainDirectTasks) {
141 mThread->TailDispatcher().DrainDirectTasks();
142 }
143
144 return rv;
145 }
146
Cancel()147 nsresult Cancel() override {
148 // Set the TLS during Cancel() just in case it calls Run().
149 AutoTaskGuard taskGuard(mThread);
150
151 nsresult rv = NS_OK;
152
153 // Try to cancel the runnable if it implements the right interface.
154 // Otherwise just skip the runnable.
155 nsCOMPtr<nsICancelableRunnable> cr = do_QueryInterface(mRunnable);
156 if (cr) {
157 rv = cr->Cancel();
158 }
159
160 return rv;
161 }
162
GetName(nsACString & aName)163 NS_IMETHOD GetName(nsACString& aName) override {
164 aName.AssignLiteral("AbstractThread::Runner");
165 if (nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable)) {
166 nsAutoCString name;
167 named->GetName(name);
168 if (!name.IsEmpty()) {
169 aName.AppendLiteral(" for ");
170 aName.Append(name);
171 }
172 }
173 return NS_OK;
174 }
175
176 private:
177 RefPtr<EventTargetWrapper> mThread;
178 RefPtr<nsIRunnable> mRunnable;
179 bool mDrainDirectTasks;
180 };
181 };
182
NS_IMPL_ISUPPORTS(AbstractThread,nsIEventTarget,nsISerialEventTarget)183 NS_IMPL_ISUPPORTS(AbstractThread, nsIEventTarget, nsISerialEventTarget)
184
185 NS_IMETHODIMP_(bool)
186 AbstractThread::IsOnCurrentThreadInfallible() { return IsCurrentThreadIn(); }
187
188 NS_IMETHODIMP
IsOnCurrentThread(bool * aResult)189 AbstractThread::IsOnCurrentThread(bool* aResult) {
190 *aResult = IsCurrentThreadIn();
191 return NS_OK;
192 }
193
194 NS_IMETHODIMP
DispatchFromScript(nsIRunnable * aEvent,uint32_t aFlags)195 AbstractThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
196 nsCOMPtr<nsIRunnable> event(aEvent);
197 return Dispatch(event.forget(), aFlags);
198 }
199
200 NS_IMETHODIMP
Dispatch(already_AddRefed<nsIRunnable> aEvent,uint32_t aFlags)201 AbstractThread::Dispatch(already_AddRefed<nsIRunnable> aEvent,
202 uint32_t aFlags) {
203 return Dispatch(Move(aEvent), NormalDispatch);
204 }
205
206 NS_IMETHODIMP
DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,uint32_t aDelayMs)207 AbstractThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
208 uint32_t aDelayMs) {
209 return NS_ERROR_NOT_IMPLEMENTED;
210 }
211
TailDispatchTasksFor(AbstractThread * aThread)212 nsresult AbstractThread::TailDispatchTasksFor(AbstractThread* aThread) {
213 if (MightHaveTailTasks()) {
214 return TailDispatcher().DispatchTasksFor(aThread);
215 }
216
217 return NS_OK;
218 }
219
HasTailTasksFor(AbstractThread * aThread)220 bool AbstractThread::HasTailTasksFor(AbstractThread* aThread) {
221 if (!MightHaveTailTasks()) {
222 return false;
223 }
224 return TailDispatcher().HasTasksFor(aThread);
225 }
226
RequiresTailDispatch(AbstractThread * aThread) const227 bool AbstractThread::RequiresTailDispatch(AbstractThread* aThread) const {
228 MOZ_ASSERT(aThread);
229 // We require tail dispatch if both the source and destination
230 // threads support it.
231 return SupportsTailDispatch() && aThread->SupportsTailDispatch();
232 }
233
RequiresTailDispatchFromCurrentThread() const234 bool AbstractThread::RequiresTailDispatchFromCurrentThread() const {
235 AbstractThread* current = GetCurrent();
236 return current && RequiresTailDispatch(current);
237 }
238
MainThread()239 AbstractThread* AbstractThread::MainThread() {
240 MOZ_ASSERT(sMainThread);
241 return sMainThread;
242 }
243
InitTLS()244 void AbstractThread::InitTLS() {
245 if (!sCurrentThreadTLS.init()) {
246 MOZ_CRASH();
247 }
248 }
249
InitMainThread()250 void AbstractThread::InitMainThread() {
251 MOZ_ASSERT(NS_IsMainThread());
252 MOZ_ASSERT(!sMainThread);
253 nsCOMPtr<nsIThread> mainThread;
254 NS_GetMainThread(getter_AddRefs(mainThread));
255 MOZ_DIAGNOSTIC_ASSERT(mainThread);
256 sMainThread = new EventTargetWrapper(mainThread.get(),
257 /* aRequireTailDispatch = */ true);
258 ClearOnShutdown(&sMainThread);
259
260 if (!sCurrentThreadTLS.init()) {
261 MOZ_CRASH();
262 }
263 }
264
DispatchStateChange(already_AddRefed<nsIRunnable> aRunnable)265 void AbstractThread::DispatchStateChange(
266 already_AddRefed<nsIRunnable> aRunnable) {
267 GetCurrent()->TailDispatcher().AddStateChangeTask(this, Move(aRunnable));
268 }
269
DispatchDirectTask(already_AddRefed<nsIRunnable> aRunnable)270 /* static */ void AbstractThread::DispatchDirectTask(
271 already_AddRefed<nsIRunnable> aRunnable) {
272 GetCurrent()->TailDispatcher().AddDirectTask(Move(aRunnable));
273 }
274
275 /* static */
CreateXPCOMThreadWrapper(nsIThread * aThread,bool aRequireTailDispatch)276 already_AddRefed<AbstractThread> AbstractThread::CreateXPCOMThreadWrapper(
277 nsIThread* aThread, bool aRequireTailDispatch) {
278 RefPtr<EventTargetWrapper> wrapper =
279 new EventTargetWrapper(aThread, aRequireTailDispatch);
280
281 bool onCurrentThread = false;
282 Unused << aThread->IsOnCurrentThread(&onCurrentThread);
283
284 if (onCurrentThread) {
285 sCurrentThreadTLS.set(wrapper);
286 return wrapper.forget();
287 }
288
289 // Set the thread-local sCurrentThreadTLS to point to the wrapper on the
290 // target thread. This ensures that sCurrentThreadTLS is as expected by
291 // AbstractThread::GetCurrent() on the target thread.
292 nsCOMPtr<nsIRunnable> r =
293 NS_NewRunnableFunction("AbstractThread::CreateXPCOMThreadWrapper",
294 [wrapper]() { sCurrentThreadTLS.set(wrapper); });
295 aThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
296 return wrapper.forget();
297 }
298
299 /* static */
CreateEventTargetWrapper(nsIEventTarget * aEventTarget,bool aRequireTailDispatch)300 already_AddRefed<AbstractThread> AbstractThread::CreateEventTargetWrapper(
301 nsIEventTarget* aEventTarget, bool aRequireTailDispatch) {
302 MOZ_ASSERT(aEventTarget);
303 nsCOMPtr<nsIThread> thread(do_QueryInterface(aEventTarget));
304 Unused << thread; // simpler than DebugOnly<nsCOMPtr<nsIThread>>
305 MOZ_ASSERT(!thread,
306 "nsIThread should be wrapped by CreateXPCOMThreadWrapper!");
307
308 RefPtr<EventTargetWrapper> wrapper =
309 new EventTargetWrapper(aEventTarget, aRequireTailDispatch);
310
311 return wrapper.forget();
312 }
313
314 } // namespace mozilla
315