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