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 
23 namespace mozilla {
24 
25 LazyLogModule gMozPromiseLog("MozPromise");
26 LazyLogModule gStateWatchingLog("StateWatching");
27 
28 StaticRefPtr<AbstractThread> sMainThread;
29 MOZ_THREAD_LOCAL(AbstractThread*) AbstractThread::sCurrentThreadTLS;
30 
31 class XPCOMThreadWrapper : public AbstractThread
32 {
33 public:
XPCOMThreadWrapper(nsIThread * aTarget,bool aRequireTailDispatch)34   explicit XPCOMThreadWrapper(nsIThread* aTarget, bool aRequireTailDispatch)
35     : AbstractThread(aRequireTailDispatch)
36     , mTarget(aTarget)
37   {
38     // Our current mechanism of implementing tail dispatch is appshell-specific.
39     // This is because a very similar mechanism already exists on the main
40     // thread, and we want to avoid making event dispatch on the main thread
41     // more complicated than it already is.
42     //
43     // If you need to use tail dispatch on other XPCOM threads, you'll need to
44     // implement an nsIThreadObserver to fire the tail dispatcher at the
45     // appropriate times.
46     MOZ_ASSERT_IF(aRequireTailDispatch,
47                   NS_IsMainThread() && NS_GetCurrentThread() == aTarget);
48   }
49 
Dispatch(already_AddRefed<nsIRunnable> aRunnable,DispatchFailureHandling aFailureHandling=AssertDispatchSuccess,DispatchReason aReason=NormalDispatch)50   virtual void Dispatch(already_AddRefed<nsIRunnable> aRunnable,
51                         DispatchFailureHandling aFailureHandling = AssertDispatchSuccess,
52                         DispatchReason aReason = NormalDispatch) override
53   {
54     nsCOMPtr<nsIRunnable> r = aRunnable;
55     AbstractThread* currentThread;
56     if (aReason != TailDispatch && (currentThread = GetCurrent()) && RequiresTailDispatch(currentThread)) {
57       currentThread->TailDispatcher().AddTask(this, r.forget(), aFailureHandling);
58       return;
59     }
60 
61     nsresult rv = mTarget->Dispatch(r, NS_DISPATCH_NORMAL);
62     MOZ_DIAGNOSTIC_ASSERT(aFailureHandling == DontAssertDispatchSuccess || NS_SUCCEEDED(rv));
63     Unused << rv;
64   }
65 
IsCurrentThreadIn()66   virtual bool IsCurrentThreadIn() override
67   {
68     // Compare NSPR threads so that this works after shutdown when
69     // NS_GetCurrentThread starts returning null.
70     PRThread* thread = nullptr;
71     mTarget->GetPRThread(&thread);
72     bool in = PR_GetCurrentThread() == thread;
73     return in;
74   }
75 
FireTailDispatcher()76   void FireTailDispatcher()
77   {
78     MOZ_DIAGNOSTIC_ASSERT(mTailDispatcher.isSome());
79     mTailDispatcher.ref().DrainDirectTasks();
80     mTailDispatcher.reset();
81   }
82 
TailDispatcher()83   virtual TaskDispatcher& TailDispatcher() override
84   {
85     MOZ_ASSERT(this == sMainThread); // See the comment in the constructor.
86     MOZ_ASSERT(IsCurrentThreadIn());
87     if (!mTailDispatcher.isSome()) {
88       mTailDispatcher.emplace(/* aIsTailDispatcher = */ true);
89 
90       nsCOMPtr<nsIRunnable> event = NewRunnableMethod(this, &XPCOMThreadWrapper::FireTailDispatcher);
91       nsContentUtils::RunInStableState(event.forget());
92     }
93 
94     return mTailDispatcher.ref();
95   }
96 
MightHaveTailTasks()97   virtual bool MightHaveTailTasks() override
98   {
99     return mTailDispatcher.isSome();
100   }
101 
AsXPCOMThread()102   virtual nsIThread* AsXPCOMThread() override { return mTarget; }
103 
104 private:
105   RefPtr<nsIThread> mTarget;
106   Maybe<AutoTaskDispatcher> mTailDispatcher;
107 };
108 
109 void
TailDispatchTasksFor(AbstractThread * aThread)110 AbstractThread::TailDispatchTasksFor(AbstractThread* aThread)
111 {
112   if (MightHaveTailTasks()) {
113     TailDispatcher().DispatchTasksFor(aThread);
114   }
115 }
116 
117 bool
HasTailTasksFor(AbstractThread * aThread)118 AbstractThread::HasTailTasksFor(AbstractThread* aThread)
119 {
120   if (!MightHaveTailTasks()) {
121     return false;
122   }
123   return TailDispatcher().HasTasksFor(aThread);
124 }
125 
126 bool
RequiresTailDispatch(AbstractThread * aThread) const127 AbstractThread::RequiresTailDispatch(AbstractThread* aThread) const
128 {
129   MOZ_ASSERT(aThread);
130   // We require tail dispatch if both the source and destination
131   // threads support it.
132   return SupportsTailDispatch() && aThread->SupportsTailDispatch();
133 }
134 
135 bool
RequiresTailDispatchFromCurrentThread() const136 AbstractThread::RequiresTailDispatchFromCurrentThread() const
137 {
138   AbstractThread* current = GetCurrent();
139   return current && RequiresTailDispatch(current);
140 }
141 
142 AbstractThread*
MainThread()143 AbstractThread::MainThread()
144 {
145   MOZ_ASSERT(sMainThread);
146   return sMainThread;
147 }
148 
149 void
InitStatics()150 AbstractThread::InitStatics()
151 {
152   MOZ_ASSERT(NS_IsMainThread());
153   MOZ_ASSERT(!sMainThread);
154   nsCOMPtr<nsIThread> mainThread;
155   NS_GetMainThread(getter_AddRefs(mainThread));
156   MOZ_DIAGNOSTIC_ASSERT(mainThread);
157   sMainThread = new XPCOMThreadWrapper(mainThread.get(), /* aRequireTailDispatch = */ true);
158   ClearOnShutdown(&sMainThread);
159 
160   if (!sCurrentThreadTLS.init()) {
161     MOZ_CRASH();
162   }
163   sCurrentThreadTLS.set(sMainThread);
164 }
165 
166 void
DispatchStateChange(already_AddRefed<nsIRunnable> aRunnable)167 AbstractThread::DispatchStateChange(already_AddRefed<nsIRunnable> aRunnable)
168 {
169   GetCurrent()->TailDispatcher().AddStateChangeTask(this, Move(aRunnable));
170 }
171 
172 /* static */ void
DispatchDirectTask(already_AddRefed<nsIRunnable> aRunnable)173 AbstractThread::DispatchDirectTask(already_AddRefed<nsIRunnable> aRunnable)
174 {
175   GetCurrent()->TailDispatcher().AddDirectTask(Move(aRunnable));
176 }
177 
178 /* static */
179 already_AddRefed<AbstractThread>
CreateXPCOMThreadWrapper(nsIThread * aThread,bool aRequireTailDispatch)180 AbstractThread::CreateXPCOMThreadWrapper(nsIThread* aThread, bool aRequireTailDispatch)
181 {
182   RefPtr<XPCOMThreadWrapper> wrapper = new XPCOMThreadWrapper(aThread, aRequireTailDispatch);
183   // Set the thread-local sCurrentThreadTLS to point to the wrapper on the
184   // target thread. This ensures that sCurrentThreadTLS is as expected by
185   // AbstractThread::GetCurrent() on the target thread.
186   nsCOMPtr<nsIRunnable> r =
187     NS_NewRunnableFunction([wrapper]() { sCurrentThreadTLS.set(wrapper); });
188   aThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
189   return wrapper.forget();
190 }
191 
192 } // namespace mozilla
193