Coverage Report

Created: 2018-09-25 14:53

/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
}