/src/mozilla-central/xpcom/threads/CooperativeThreadPool.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 "CooperativeThreadPool.h" |
8 | | |
9 | | #include "base/message_loop.h" |
10 | | #include "mozilla/IOInterposer.h" |
11 | | #include "mozilla/ServoBindings.h" |
12 | | #include "nsError.h" |
13 | | #include "nsThreadUtils.h" |
14 | | |
15 | | using namespace mozilla; |
16 | | |
17 | | static bool gCooperativeSchedulingEnabled; |
18 | | MOZ_THREAD_LOCAL(CooperativeThreadPool::CooperativeThread*) CooperativeThreadPool::sTlsCurrentThread; |
19 | | |
20 | | // Windows silliness. winbase.h defines an empty no-argument Yield macro. |
21 | | #undef Yield |
22 | | |
23 | | CooperativeThreadPool::CooperativeThreadPool(size_t aNumThreads, |
24 | | Mutex& aMutex, |
25 | | Controller& aController) |
26 | | : mMutex(aMutex) |
27 | | , mShutdownCondition(mMutex, "CoopShutdown") |
28 | | , mRunning(false) |
29 | | , mNumThreads(std::min(aNumThreads, kMaxThreads)) |
30 | | , mRunningThreads(0) |
31 | | , mController(aController) |
32 | | , mSelectedThread(size_t(0)) |
33 | 0 | { |
34 | 0 | MOZ_ASSERT(aNumThreads <= kMaxThreads); |
35 | 0 |
|
36 | 0 | gCooperativeSchedulingEnabled = true; |
37 | 0 | sTlsCurrentThread.infallibleInit(); |
38 | 0 |
|
39 | 0 | MutexAutoLock lock(mMutex); |
40 | 0 |
|
41 | 0 | mRunning = true; |
42 | 0 | mRunningThreads = mNumThreads; |
43 | 0 |
|
44 | 0 | for (size_t i = 0; i < mNumThreads; i++) { |
45 | 0 | mThreads[i] = MakeUnique<CooperativeThread>(this, i); |
46 | 0 | } |
47 | 0 | } |
48 | | |
49 | | CooperativeThreadPool::~CooperativeThreadPool() |
50 | 0 | { |
51 | 0 | MOZ_ASSERT(!mRunning); |
52 | 0 | } |
53 | | |
54 | | const size_t CooperativeThreadPool::kMaxThreads; |
55 | | |
56 | | void |
57 | | CooperativeThreadPool::Shutdown() |
58 | 0 | { |
59 | 0 | // This will not be called on any of the cooperative threads. |
60 | 0 | { |
61 | 0 | MutexAutoLock lock(mMutex); |
62 | 0 | MOZ_ASSERT(mRunning); |
63 | 0 | mRunning = false; |
64 | 0 | } |
65 | 0 |
|
66 | 0 | for (size_t i = 0; i < mNumThreads; i++) { |
67 | 0 | mThreads[i]->BeginShutdown(); |
68 | 0 | } |
69 | 0 |
|
70 | 0 | { |
71 | 0 | MutexAutoLock lock(mMutex); |
72 | 0 | while (mRunningThreads) { |
73 | 0 | mShutdownCondition.Wait(); |
74 | 0 | } |
75 | 0 | } |
76 | 0 |
|
77 | 0 | for (size_t i = 0; i < mNumThreads; i++) { |
78 | 0 | mThreads[i]->EndShutdown(); |
79 | 0 | } |
80 | 0 | } |
81 | | |
82 | | void |
83 | | CooperativeThreadPool::RecheckBlockers(const MutexAutoLock& aProofOfLock) |
84 | 0 | { |
85 | 0 | aProofOfLock.AssertOwns(mMutex); |
86 | 0 |
|
87 | 0 | if (!mSelectedThread.is<AllThreadsBlocked>()) { |
88 | 0 | return; |
89 | 0 | } |
90 | 0 | |
91 | 0 | for (size_t i = 0; i < mNumThreads; i++) { |
92 | 0 | if (mThreads[i]->mRunning && !mThreads[i]->IsBlocked(aProofOfLock)) { |
93 | 0 | mSelectedThread = AsVariant(i); |
94 | 0 | mThreads[i]->mCondVar.Notify(); |
95 | 0 | return; |
96 | 0 | } |
97 | 0 | } |
98 | 0 |
|
99 | 0 | // It may be valid to reach this point. For example, if we are waiting for an |
100 | 0 | // event to be posted from a non-main thread. Even if the queue is non-empty, |
101 | 0 | // it may have only idle events that we do not want to run (because we are |
102 | 0 | // expecting a vsync soon). |
103 | 0 | } |
104 | | |
105 | | /* static */ void |
106 | | CooperativeThreadPool::Yield(Resource* aBlocker, const MutexAutoLock& aProofOfLock) |
107 | 0 | { |
108 | 0 | if (!gCooperativeSchedulingEnabled) { |
109 | 0 | return; |
110 | 0 | } |
111 | 0 | |
112 | 0 | CooperativeThread* thread = sTlsCurrentThread.get(); |
113 | 0 | MOZ_RELEASE_ASSERT(thread); |
114 | 0 | thread->SetBlocker(aBlocker); |
115 | 0 | thread->Yield(aProofOfLock); |
116 | 0 | } |
117 | | |
118 | | /* static */ bool |
119 | | CooperativeThreadPool::IsCooperativeThread() |
120 | 0 | { |
121 | 0 | if (!gCooperativeSchedulingEnabled) { |
122 | 0 | return false; |
123 | 0 | } |
124 | 0 | return !!sTlsCurrentThread.get(); |
125 | 0 | } |
126 | | |
127 | | CooperativeThreadPool::SelectedThread |
128 | | CooperativeThreadPool::CurrentThreadIndex(const MutexAutoLock& aProofOfLock) const |
129 | 0 | { |
130 | 0 | aProofOfLock.AssertOwns(mMutex); |
131 | 0 | return mSelectedThread; |
132 | 0 | } |
133 | | |
134 | | CooperativeThreadPool::CooperativeThread::CooperativeThread(CooperativeThreadPool* aPool, |
135 | | size_t aIndex) |
136 | | : mPool(aPool) |
137 | | , mCondVar(aPool->mMutex, "CooperativeThreadPool") |
138 | | , mBlocker(nullptr) |
139 | | , mIndex(aIndex) |
140 | | , mRunning(true) |
141 | 0 | { |
142 | 0 | mThread = PR_CreateThread(PR_USER_THREAD, ThreadFunc, this, |
143 | 0 | PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, |
144 | 0 | PR_JOINABLE_THREAD, 0); |
145 | 0 | MOZ_RELEASE_ASSERT(mThread); |
146 | 0 | } |
147 | | |
148 | | void |
149 | | CooperativeThreadPool::CooperativeThread::ThreadMethod() |
150 | 0 | { |
151 | 0 | char stackTop; |
152 | 0 |
|
153 | 0 | MOZ_ASSERT(gCooperativeSchedulingEnabled); |
154 | 0 | sTlsCurrentThread.set(this); |
155 | 0 |
|
156 | 0 | nsCString name = mPool->mThreadNaming.GetNextThreadName("Main"); |
157 | 0 | PR_SetCurrentThreadName(name.get()); |
158 | 0 |
|
159 | 0 | mozilla::IOInterposer::RegisterCurrentThread(); |
160 | 0 |
|
161 | 0 | { |
162 | 0 | // Make sure only one thread at a time can proceed. This only happens during |
163 | 0 | // thread startup. |
164 | 0 | MutexAutoLock lock(mPool->mMutex); |
165 | 0 | while (mPool->mSelectedThread != AsVariant(mIndex)) { |
166 | 0 | mCondVar.Wait(); |
167 | 0 | } |
168 | 0 | } |
169 | 0 |
|
170 | 0 | mPool->mController.OnStartThread(mIndex, name, &stackTop); |
171 | 0 |
|
172 | 0 | nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); |
173 | 0 | mEventTarget = thread; |
174 | 0 |
|
175 | 0 | // The main event loop for this thread. |
176 | 0 | for (;;) { |
177 | 0 | { |
178 | 0 | MutexAutoLock lock(mPool->mMutex); |
179 | 0 | if (!mPool->mRunning) { |
180 | 0 | break; |
181 | 0 | } |
182 | 0 | } |
183 | 0 | |
184 | 0 | bool processedEvent; |
185 | 0 | thread->ProcessNextEvent(true, &processedEvent); |
186 | 0 | } |
187 | 0 |
|
188 | 0 | mPool->mController.OnStopThread(mIndex); |
189 | 0 | mozilla::IOInterposer::UnregisterCurrentThread(); |
190 | 0 |
|
191 | 0 | MutexAutoLock lock(mPool->mMutex); |
192 | 0 | mPool->mRunningThreads--; |
193 | 0 | mRunning = false; |
194 | 0 | mPool->mSelectedThread = AsVariant(AllThreadsBlocked::Blocked); |
195 | 0 | mPool->RecheckBlockers(lock); |
196 | 0 | mPool->mShutdownCondition.Notify(); |
197 | 0 | } |
198 | | |
199 | | /* static */ void |
200 | | CooperativeThreadPool::CooperativeThread::ThreadFunc(void* aArg) |
201 | 0 | { |
202 | 0 | auto thread = static_cast<CooperativeThreadPool::CooperativeThread*>(aArg); |
203 | 0 | thread->ThreadMethod(); |
204 | 0 | } |
205 | | |
206 | | void |
207 | | CooperativeThreadPool::CooperativeThread::BeginShutdown() |
208 | 0 | { |
209 | 0 | mEventTarget->Dispatch(new mozilla::Runnable("CooperativeShutdownEvent"), |
210 | 0 | nsIEventTarget::DISPATCH_NORMAL); |
211 | 0 | } |
212 | | |
213 | | void |
214 | | CooperativeThreadPool::CooperativeThread::EndShutdown() |
215 | 0 | { |
216 | 0 | PR_JoinThread(mThread); |
217 | 0 | } |
218 | | |
219 | | bool |
220 | | CooperativeThreadPool::CooperativeThread::IsBlocked(const MutexAutoLock& aProofOfLock) |
221 | 0 | { |
222 | 0 | if (!mBlocker) { |
223 | 0 | return false; |
224 | 0 | } |
225 | 0 | |
226 | 0 | return !mBlocker->IsAvailable(aProofOfLock); |
227 | 0 | } |
228 | | |
229 | | void |
230 | | CooperativeThreadPool::CooperativeThread::Yield(const MutexAutoLock& aProofOfLock) |
231 | 0 | { |
232 | 0 | aProofOfLock.AssertOwns(mPool->mMutex); |
233 | 0 |
|
234 | 0 | // First select the next thread to run. |
235 | 0 | size_t selected = mIndex + 1; |
236 | 0 | bool found = false; |
237 | 0 | do { |
238 | 0 | if (selected >= mPool->mNumThreads) { |
239 | 0 | selected = 0; |
240 | 0 | } |
241 | 0 |
|
242 | 0 | if (mPool->mThreads[selected]->mRunning |
243 | 0 | && !mPool->mThreads[selected]->IsBlocked(aProofOfLock)) { |
244 | 0 | found = true; |
245 | 0 | break; |
246 | 0 | } |
247 | 0 | selected++; |
248 | 0 | } while (selected != mIndex + 1); |
249 | 0 |
|
250 | 0 | if (found) { |
251 | 0 | mPool->mSelectedThread = AsVariant(selected); |
252 | 0 | mPool->mThreads[selected]->mCondVar.Notify(); |
253 | 0 | } else { |
254 | 0 | // We need to block all threads. Some thread will be unblocked when |
255 | 0 | // RecheckBlockers is called (if a new event is posted for an outside |
256 | 0 | // thread, for example). |
257 | 0 | mPool->mSelectedThread = AsVariant(AllThreadsBlocked::Blocked); |
258 | 0 | } |
259 | 0 |
|
260 | 0 | mPool->mController.OnSuspendThread(mIndex); |
261 | 0 |
|
262 | 0 | while (mPool->mSelectedThread != AsVariant(mIndex)) { |
263 | 0 | mCondVar.Wait(); |
264 | 0 | } |
265 | 0 |
|
266 | 0 | mPool->mController.OnResumeThread(mIndex); |
267 | 0 | } |