Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/xpcom/threads/nsThreadPool.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 "nsCOMArray.h"
8
#include "nsIClassInfoImpl.h"
9
#include "ThreadDelay.h"
10
#include "nsThreadPool.h"
11
#include "nsThreadManager.h"
12
#include "nsThread.h"
13
#include "nsMemory.h"
14
#include "nsAutoPtr.h"
15
#include "prinrval.h"
16
#include "mozilla/Logging.h"
17
#include "mozilla/SystemGroup.h"
18
#include "nsThreadSyncDispatch.h"
19
20
using namespace mozilla;
21
22
static LazyLogModule sThreadPoolLog("nsThreadPool");
23
#ifdef LOG
24
#undef LOG
25
#endif
26
21
#define LOG(args) MOZ_LOG(sThreadPoolLog, mozilla::LogLevel::Debug, args)
27
28
// DESIGN:
29
//  o  Allocate anonymous threads.
30
//  o  Use nsThreadPool::Run as the main routine for each thread.
31
//  o  Each thread waits on the event queue's monitor, checking for
32
//     pending events and rescheduling itself as an idle thread.
33
34
#define DEFAULT_THREAD_LIMIT 4
35
#define DEFAULT_IDLE_THREAD_LIMIT 1
36
#define DEFAULT_IDLE_THREAD_TIMEOUT PR_SecondsToInterval(60)
37
38
NS_IMPL_ADDREF(nsThreadPool)
39
NS_IMPL_RELEASE(nsThreadPool)
40
NS_IMPL_CLASSINFO(nsThreadPool, nullptr, nsIClassInfo::THREADSAFE,
41
                  NS_THREADPOOL_CID)
42
NS_IMPL_QUERY_INTERFACE_CI(nsThreadPool, nsIThreadPool, nsIEventTarget,
43
                           nsIRunnable)
44
NS_IMPL_CI_INTERFACE_GETTER(nsThreadPool, nsIThreadPool, nsIEventTarget)
45
46
nsThreadPool::nsThreadPool()
47
  : mMutex("[nsThreadPool.mMutex]")
48
  , mEventsAvailable(mMutex, "[nsThreadPool.mEventsAvailable]")
49
  , mThreadLimit(DEFAULT_THREAD_LIMIT)
50
  , mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT)
51
  , mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT)
52
  , mIdleCount(0)
53
  , mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE)
54
  , mShutdown(false)
55
7
{
56
7
  LOG(("THRD-P(%p) constructor!!!\n", this));
57
7
}
58
59
nsThreadPool::~nsThreadPool()
60
0
{
61
0
  // Threads keep a reference to the nsThreadPool until they return from Run()
62
0
  // after removing themselves from mThreads.
63
0
  MOZ_ASSERT(mThreads.IsEmpty());
64
0
}
65
66
nsresult
67
nsThreadPool::PutEvent(nsIRunnable* aEvent)
68
0
{
69
0
  nsCOMPtr<nsIRunnable> event(aEvent);
70
0
  return PutEvent(event.forget(), 0);
71
0
}
72
73
nsresult
74
nsThreadPool::PutEvent(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
75
0
{
76
0
  // Avoid spawning a new thread while holding the event queue lock...
77
0
78
0
  bool spawnThread = false;
79
0
  uint32_t stackSize = 0;
80
0
  {
81
0
    MutexAutoLock lock(mMutex);
82
0
83
0
    if (NS_WARN_IF(mShutdown)) {
84
0
      return NS_ERROR_NOT_AVAILABLE;
85
0
    }
86
0
    LOG(("THRD-P(%p) put [%d %d %d]\n", this, mIdleCount, mThreads.Count(),
87
0
         mThreadLimit));
88
0
    MOZ_ASSERT(mIdleCount <= (uint32_t)mThreads.Count(), "oops");
89
0
90
0
    // Make sure we have a thread to service this event.
91
0
    if (mThreads.Count() < (int32_t)mThreadLimit &&
92
0
        !(aFlags & NS_DISPATCH_AT_END) &&
93
0
        // Spawn a new thread if we don't have enough idle threads to serve
94
0
        // pending events immediately.
95
0
        mEvents.Count(lock) >= mIdleCount) {
96
0
      spawnThread = true;
97
0
    }
98
0
99
0
    mEvents.PutEvent(std::move(aEvent), EventPriority::Normal, lock);
100
0
    mEventsAvailable.Notify();
101
0
    stackSize = mStackSize;
102
0
  }
103
0
104
0
  auto delay = MakeScopeExit([&]() {
105
0
      // Delay to encourage the receiving task to run before we do work.
106
0
      DelayForChaosMode(ChaosFeature::TaskDispatching, 1000);
107
0
  });
108
0
109
0
  LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread));
110
0
  if (!spawnThread) {
111
0
    return NS_OK;
112
0
  }
113
0
114
0
  nsCOMPtr<nsIThread> thread;
115
0
  nsresult rv = NS_NewNamedThread(mThreadNaming.GetNextThreadName(mName),
116
0
                                  getter_AddRefs(thread), nullptr, stackSize);
117
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
118
0
    return NS_ERROR_UNEXPECTED;
119
0
  }
120
0
121
0
  bool killThread = false;
122
0
  {
123
0
    MutexAutoLock lock(mMutex);
124
0
    if (mThreads.Count() < (int32_t)mThreadLimit) {
125
0
      mThreads.AppendObject(thread);
126
0
    } else {
127
0
      killThread = true;  // okay, we don't need this thread anymore
128
0
    }
129
0
  }
130
0
  LOG(("THRD-P(%p) put [%p kill=%d]\n", this, thread.get(), killThread));
131
0
  if (killThread) {
132
0
    // We never dispatched any events to the thread, so we can shut it down
133
0
    // asynchronously without worrying about anything.
134
0
    ShutdownThread(thread);
135
0
  } else {
136
0
    thread->Dispatch(this, NS_DISPATCH_NORMAL);
137
0
  }
138
0
139
0
  return NS_OK;
140
0
}
141
142
void
143
nsThreadPool::ShutdownThread(nsIThread* aThread)
144
0
{
145
0
  LOG(("THRD-P(%p) shutdown async [%p]\n", this, aThread));
146
0
147
0
  // This is either called by a threadpool thread that is out of work, or
148
0
  // a thread that attempted to create a threadpool thread and raced in
149
0
  // such a way that the newly created thread is no longer necessary.
150
0
  // In the first case, we must go to another thread to shut aThread down
151
0
  // (because it is the current thread).  In the second case, we cannot
152
0
  // synchronously shut down the current thread (because then Dispatch() would
153
0
  // spin the event loop, and that could blow up the world), and asynchronous
154
0
  // shutdown requires this thread have an event loop (and it may not, see bug
155
0
  // 10204784).  The simplest way to cover all cases is to asynchronously
156
0
  // shutdown aThread from the main thread.
157
0
  SystemGroup::Dispatch(TaskCategory::Other, NewRunnableMethod(
158
0
        "nsIThread::AsyncShutdown", aThread, &nsIThread::AsyncShutdown));
159
0
}
160
161
NS_IMETHODIMP
162
nsThreadPool::Run()
163
0
{
164
0
  LOG(("THRD-P(%p) enter %s\n", this, mName.BeginReading()));
165
0
166
0
  nsCOMPtr<nsIThread> current;
167
0
  nsThreadManager::get().GetCurrentThread(getter_AddRefs(current));
168
0
169
0
  bool shutdownThreadOnExit = false;
170
0
  bool exitThread = false;
171
0
  bool wasIdle = false;
172
0
  TimeStamp idleSince;
173
0
174
0
  nsCOMPtr<nsIThreadPoolListener> listener;
175
0
  {
176
0
    MutexAutoLock lock(mMutex);
177
0
    listener = mListener;
178
0
  }
179
0
180
0
  if (listener) {
181
0
    listener->OnThreadCreated();
182
0
  }
183
0
184
0
  do {
185
0
    nsCOMPtr<nsIRunnable> event;
186
0
    {
187
0
      MutexAutoLock lock(mMutex);
188
0
189
0
      event = mEvents.GetEvent(nullptr, lock);
190
0
      if (!event) {
191
0
        TimeStamp now = TimeStamp::Now();
192
0
        TimeDuration timeout = TimeDuration::FromMilliseconds(mIdleThreadTimeout);
193
0
194
0
        // If we are shutting down, then don't keep any idle threads
195
0
        if (mShutdown) {
196
0
          exitThread = true;
197
0
        } else {
198
0
          if (wasIdle) {
199
0
            // if too many idle threads or idle for too long, then bail.
200
0
            if (mIdleCount > mIdleThreadLimit ||
201
0
                (mIdleThreadTimeout != UINT32_MAX && (now - idleSince) >= timeout)) {
202
0
              exitThread = true;
203
0
            }
204
0
          } else {
205
0
            // if would be too many idle threads...
206
0
            if (mIdleCount == mIdleThreadLimit) {
207
0
              exitThread = true;
208
0
            } else {
209
0
              ++mIdleCount;
210
0
              idleSince = now;
211
0
              wasIdle = true;
212
0
            }
213
0
          }
214
0
        }
215
0
216
0
        if (exitThread) {
217
0
          if (wasIdle) {
218
0
            --mIdleCount;
219
0
          }
220
0
          shutdownThreadOnExit = mThreads.RemoveObject(current);
221
0
        } else {
222
0
          AUTO_PROFILER_LABEL("nsThreadPool::Run::Wait", IDLE);
223
0
224
0
          TimeDuration delta = timeout - (now - idleSince);
225
0
          LOG(("THRD-P(%p) %s waiting [%f]\n", this, mName.BeginReading(),
226
0
               delta.ToMilliseconds()));
227
0
          mEventsAvailable.Wait(delta);
228
0
          LOG(("THRD-P(%p) done waiting\n", this));
229
0
        }
230
0
      } else if (wasIdle) {
231
0
        wasIdle = false;
232
0
        --mIdleCount;
233
0
      }
234
0
    }
235
0
    if (event) {
236
0
      LOG(("THRD-P(%p) %s running [%p]\n", this, mName.BeginReading(), event.get()));
237
0
238
0
      // Delay event processing to encourage whoever dispatched this event
239
0
      // to run.
240
0
      DelayForChaosMode(ChaosFeature::TaskRunning, 1000);
241
0
242
0
      event->Run();
243
0
    }
244
0
  } while (!exitThread);
245
0
246
0
  if (listener) {
247
0
    listener->OnThreadShuttingDown();
248
0
  }
249
0
250
0
  if (shutdownThreadOnExit) {
251
0
    ShutdownThread(current);
252
0
  }
253
0
254
0
  LOG(("THRD-P(%p) leave\n", this));
255
0
  return NS_OK;
256
0
}
257
258
NS_IMETHODIMP
259
nsThreadPool::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
260
0
{
261
0
  nsCOMPtr<nsIRunnable> event(aEvent);
262
0
  return Dispatch(event.forget(), aFlags);
263
0
}
264
265
NS_IMETHODIMP
266
nsThreadPool::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
267
0
{
268
0
  LOG(("THRD-P(%p) dispatch [%p %x]\n", this, /* XXX aEvent*/ nullptr, aFlags));
269
0
270
0
  if (NS_WARN_IF(mShutdown)) {
271
0
    return NS_ERROR_NOT_AVAILABLE;
272
0
  }
273
0
274
0
  if (aFlags & DISPATCH_SYNC) {
275
0
    nsCOMPtr<nsIThread> thread;
276
0
    nsThreadManager::get().GetCurrentThread(getter_AddRefs(thread));
277
0
    if (NS_WARN_IF(!thread)) {
278
0
      return NS_ERROR_NOT_AVAILABLE;
279
0
    }
280
0
281
0
    RefPtr<nsThreadSyncDispatch> wrapper =
282
0
      new nsThreadSyncDispatch(thread.forget(), std::move(aEvent));
283
0
    PutEvent(wrapper);
284
0
285
0
    SpinEventLoopUntil([&, wrapper]() -> bool {
286
0
        return !wrapper->IsPending();
287
0
      });
288
0
  } else {
289
0
    NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL ||
290
0
                 aFlags == NS_DISPATCH_AT_END, "unexpected dispatch flags");
291
0
    PutEvent(std::move(aEvent), aFlags);
292
0
  }
293
0
  return NS_OK;
294
0
}
295
296
NS_IMETHODIMP
297
nsThreadPool::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
298
0
{
299
0
  return NS_ERROR_NOT_IMPLEMENTED;
300
0
}
301
302
NS_IMETHODIMP_(bool)
303
nsThreadPool::IsOnCurrentThreadInfallible()
304
0
{
305
0
  MutexAutoLock lock(mMutex);
306
0
307
0
  nsIThread* thread = NS_GetCurrentThread();
308
0
  for (uint32_t i = 0; i < static_cast<uint32_t>(mThreads.Count()); ++i) {
309
0
    if (mThreads[i] == thread) {
310
0
      return true;
311
0
    }
312
0
  }
313
0
  return false;
314
0
}
315
316
NS_IMETHODIMP
317
nsThreadPool::IsOnCurrentThread(bool* aResult)
318
0
{
319
0
  MutexAutoLock lock(mMutex);
320
0
  if (NS_WARN_IF(mShutdown)) {
321
0
    return NS_ERROR_NOT_AVAILABLE;
322
0
  }
323
0
324
0
  nsIThread* thread = NS_GetCurrentThread();
325
0
  for (uint32_t i = 0; i < static_cast<uint32_t>(mThreads.Count()); ++i) {
326
0
    if (mThreads[i] == thread) {
327
0
      *aResult = true;
328
0
      return NS_OK;
329
0
    }
330
0
  }
331
0
  *aResult = false;
332
0
  return NS_OK;
333
0
}
334
335
NS_IMETHODIMP
336
nsThreadPool::Shutdown()
337
0
{
338
0
  nsCOMArray<nsIThread> threads;
339
0
  nsCOMPtr<nsIThreadPoolListener> listener;
340
0
  {
341
0
    MutexAutoLock lock(mMutex);
342
0
    mShutdown = true;
343
0
    mEventsAvailable.NotifyAll();
344
0
345
0
    threads.AppendObjects(mThreads);
346
0
    mThreads.Clear();
347
0
348
0
    // Swap in a null listener so that we release the listener at the end of
349
0
    // this method. The listener will be kept alive as long as the other threads
350
0
    // that were created when it was set.
351
0
    mListener.swap(listener);
352
0
  }
353
0
354
0
  // It's important that we shutdown the threads while outside the event queue
355
0
  // monitor.  Otherwise, we could end up dead-locking.
356
0
357
0
  for (int32_t i = 0; i < threads.Count(); ++i) {
358
0
    threads[i]->Shutdown();
359
0
  }
360
0
361
0
  return NS_OK;
362
0
}
363
364
NS_IMETHODIMP
365
nsThreadPool::GetThreadLimit(uint32_t* aValue)
366
0
{
367
0
  *aValue = mThreadLimit;
368
0
  return NS_OK;
369
0
}
370
371
NS_IMETHODIMP
372
nsThreadPool::SetThreadLimit(uint32_t aValue)
373
7
{
374
7
  MutexAutoLock lock(mMutex);
375
7
  LOG(("THRD-P(%p) thread limit [%u]\n", this, aValue));
376
7
  mThreadLimit = aValue;
377
7
  if (mIdleThreadLimit > mThreadLimit) {
378
0
    mIdleThreadLimit = mThreadLimit;
379
0
  }
380
7
381
7
  if (static_cast<uint32_t>(mThreads.Count()) > mThreadLimit) {
382
0
    mEventsAvailable.NotifyAll();  // wake up threads so they observe this change
383
0
  }
384
7
  return NS_OK;
385
7
}
386
387
NS_IMETHODIMP
388
nsThreadPool::GetIdleThreadLimit(uint32_t* aValue)
389
0
{
390
0
  *aValue = mIdleThreadLimit;
391
0
  return NS_OK;
392
0
}
393
394
NS_IMETHODIMP
395
nsThreadPool::SetIdleThreadLimit(uint32_t aValue)
396
7
{
397
7
  MutexAutoLock lock(mMutex);
398
7
  LOG(("THRD-P(%p) idle thread limit [%u]\n", this, aValue));
399
7
  mIdleThreadLimit = aValue;
400
7
  if (mIdleThreadLimit > mThreadLimit) {
401
3
    mIdleThreadLimit = mThreadLimit;
402
3
  }
403
7
404
7
  // Do we need to kill some idle threads?
405
7
  if (mIdleCount > mIdleThreadLimit) {
406
0
    mEventsAvailable.NotifyAll();  // wake up threads so they observe this change
407
0
  }
408
7
  return NS_OK;
409
7
}
410
411
NS_IMETHODIMP
412
nsThreadPool::GetIdleThreadTimeout(uint32_t* aValue)
413
0
{
414
0
  *aValue = mIdleThreadTimeout;
415
0
  return NS_OK;
416
0
}
417
418
NS_IMETHODIMP
419
nsThreadPool::SetIdleThreadTimeout(uint32_t aValue)
420
7
{
421
7
  MutexAutoLock lock(mMutex);
422
7
  uint32_t oldTimeout = mIdleThreadTimeout;
423
7
  mIdleThreadTimeout = aValue;
424
7
425
7
  // Do we need to notify any idle threads that their sleep time has shortened?
426
7
  if (mIdleThreadTimeout < oldTimeout && mIdleCount > 0) {
427
0
    mEventsAvailable.NotifyAll();  // wake up threads so they observe this change
428
0
  }
429
7
  return NS_OK;
430
7
}
431
432
NS_IMETHODIMP
433
nsThreadPool::GetThreadStackSize(uint32_t* aValue)
434
0
{
435
0
  MutexAutoLock lock(mMutex);
436
0
  *aValue = mStackSize;
437
0
  return NS_OK;
438
0
}
439
440
NS_IMETHODIMP
441
nsThreadPool::SetThreadStackSize(uint32_t aValue)
442
3
{
443
3
  MutexAutoLock lock(mMutex);
444
3
  mStackSize = aValue;
445
3
  return NS_OK;
446
3
}
447
448
NS_IMETHODIMP
449
nsThreadPool::GetListener(nsIThreadPoolListener** aListener)
450
0
{
451
0
  MutexAutoLock lock(mMutex);
452
0
  NS_IF_ADDREF(*aListener = mListener);
453
0
  return NS_OK;
454
0
}
455
456
NS_IMETHODIMP
457
nsThreadPool::SetListener(nsIThreadPoolListener* aListener)
458
0
{
459
0
  nsCOMPtr<nsIThreadPoolListener> swappedListener(aListener);
460
0
  {
461
0
    MutexAutoLock lock(mMutex);
462
0
    mListener.swap(swappedListener);
463
0
  }
464
0
  return NS_OK;
465
0
}
466
467
NS_IMETHODIMP
468
nsThreadPool::SetName(const nsACString& aName)
469
7
{
470
7
  {
471
7
    MutexAutoLock lock(mMutex);
472
7
    if (mThreads.Count()) {
473
0
      return NS_ERROR_NOT_AVAILABLE;
474
0
    }
475
7
  }
476
7
477
7
  mName = aName;
478
7
  return NS_OK;
479
7
}