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/. */
7 #include "nsThreadPool.h"
9 #include "nsCOMArray.h"
10 #include "ThreadDelay.h"
11 #include "nsThreadManager.h"
13 #include "nsThreadUtils.h"
15 #include "mozilla/Logging.h"
16 #include "mozilla/ProfilerLabels.h"
17 #include "mozilla/ProfilerRunnable.h"
18 #include "mozilla/SchedulerGroup.h"
19 #include "mozilla/ScopeExit.h"
20 #include "mozilla/SpinEventLoopUntil.h"
21 #include "nsThreadSyncDispatch.h"
25 using namespace mozilla
;
27 static LazyLogModule
sThreadPoolLog("nsThreadPool");
31 #define LOG(args) MOZ_LOG(sThreadPoolLog, mozilla::LogLevel::Debug, args)
33 static MOZ_THREAD_LOCAL(nsThreadPool
*) gCurrentThreadPool
;
35 void nsThreadPool::InitTLS() { gCurrentThreadPool
.infallibleInit(); }
38 // o Allocate anonymous threads.
39 // o Use nsThreadPool::Run as the main routine for each thread.
40 // o Each thread waits on the event queue's monitor, checking for
41 // pending events and rescheduling itself as an idle thread.
43 #define DEFAULT_THREAD_LIMIT 4
44 #define DEFAULT_IDLE_THREAD_LIMIT 1
45 #define DEFAULT_IDLE_THREAD_TIMEOUT PR_SecondsToInterval(60)
47 NS_IMPL_ISUPPORTS_INHERITED(nsThreadPool
, Runnable
, nsIThreadPool
,
50 nsThreadPool
* nsThreadPool::GetCurrentThreadPool() {
51 return gCurrentThreadPool
.get();
54 nsThreadPool::nsThreadPool()
55 : Runnable("nsThreadPool"),
56 mMutex("[nsThreadPool.mMutex]"),
57 mEventsAvailable(mMutex
, "[nsThreadPool.mEventsAvailable]"),
58 mThreadLimit(DEFAULT_THREAD_LIMIT
),
59 mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT
),
60 mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT
),
62 mQoSPriority(nsIThread::QOS_PRIORITY_NORMAL
),
63 mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE
),
65 mRegressiveMaxIdleTime(false),
66 mIsAPoolThreadFree(true) {
67 LOG(("THRD-P(%p) constructor!!!\n", this));
70 nsThreadPool::~nsThreadPool() {
71 // Threads keep a reference to the nsThreadPool until they return from Run()
72 // after removing themselves from mThreads.
73 MOZ_ASSERT(mThreads
.IsEmpty());
76 nsresult
nsThreadPool::PutEvent(nsIRunnable
* aEvent
) {
77 nsCOMPtr
<nsIRunnable
> event(aEvent
);
78 return PutEvent(event
.forget(), 0);
81 nsresult
nsThreadPool::PutEvent(already_AddRefed
<nsIRunnable
> aEvent
,
83 // Avoid spawning a new thread while holding the event queue lock...
85 bool spawnThread
= false;
86 uint32_t stackSize
= 0;
89 MutexAutoLock
lock(mMutex
);
91 if (NS_WARN_IF(mShutdown
)) {
92 return NS_ERROR_NOT_AVAILABLE
;
94 LOG(("THRD-P(%p) put [%d %d %d]\n", this, mIdleCount
, mThreads
.Count(),
96 MOZ_ASSERT(mIdleCount
<= (uint32_t)mThreads
.Count(), "oops");
98 // Make sure we have a thread to service this event.
99 if (mThreads
.Count() < (int32_t)mThreadLimit
&&
100 !(aFlags
& NS_DISPATCH_AT_END
) &&
101 // Spawn a new thread if we don't have enough idle threads to serve
102 // pending events immediately.
103 mEvents
.Count(lock
) >= mIdleCount
) {
107 nsCOMPtr
<nsIRunnable
> event(aEvent
);
108 LogRunnable::LogDispatch(event
);
109 mEvents
.PutEvent(event
.forget(), EventQueuePriority::Normal
, lock
);
110 mEventsAvailable
.Notify();
111 stackSize
= mStackSize
;
115 auto delay
= MakeScopeExit([&]() {
116 // Delay to encourage the receiving task to run before we do work.
117 DelayForChaosMode(ChaosFeature::TaskDispatching
, 1000);
120 LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread
));
125 nsCOMPtr
<nsIThread
> thread
;
126 nsresult rv
= NS_NewNamedThread(
127 mThreadNaming
.GetNextThreadName(name
), getter_AddRefs(thread
), nullptr,
128 {.stackSize
= stackSize
, .blockDispatch
= true});
129 if (NS_WARN_IF(NS_FAILED(rv
))) {
130 return NS_ERROR_UNEXPECTED
;
133 bool killThread
= false;
135 MutexAutoLock
lock(mMutex
);
138 } else if (mThreads
.Count() < (int32_t)mThreadLimit
) {
139 mThreads
.AppendObject(thread
);
140 if (mThreads
.Count() >= (int32_t)mThreadLimit
) {
141 mIsAPoolThreadFree
= false;
144 // Someone else may have also been starting a thread
145 killThread
= true; // okay, we don't need this thread anymore
148 LOG(("THRD-P(%p) put [%p kill=%d]\n", this, thread
.get(), killThread
));
150 // We never dispatched any events to the thread, so we can shut it down
151 // asynchronously without worrying about anything.
152 ShutdownThread(thread
);
154 thread
->Dispatch(this, NS_DISPATCH_IGNORE_BLOCK_DISPATCH
);
160 void nsThreadPool::ShutdownThread(nsIThread
* aThread
) {
161 LOG(("THRD-P(%p) shutdown async [%p]\n", this, aThread
));
163 // This is either called by a threadpool thread that is out of work, or
164 // a thread that attempted to create a threadpool thread and raced in
165 // such a way that the newly created thread is no longer necessary.
166 // In the first case, we must go to another thread to shut aThread down
167 // (because it is the current thread). In the second case, we cannot
168 // synchronously shut down the current thread (because then Dispatch() would
169 // spin the event loop, and that could blow up the world), and asynchronous
170 // shutdown requires this thread have an event loop (and it may not, see bug
171 // 10204784). The simplest way to cover all cases is to asynchronously
172 // shutdown aThread from the main thread.
173 SchedulerGroup::Dispatch(NewRunnableMethod(
174 "nsIThread::AsyncShutdown", aThread
, &nsIThread::AsyncShutdown
));
178 nsThreadPool::SetQoSForThreads(nsIThread::QoSPriority aPriority
) {
179 MutexAutoLock
lock(mMutex
);
180 mQoSPriority
= aPriority
;
182 // We don't notify threads here to observe the change, because we don't want
183 // to create spurious wakeups during idle. Rather, we want threads to simply
184 // observe the change on their own if they wake up to do some task.
189 // This event 'runs' for the lifetime of the worker thread. The actual
190 // eventqueue is mEvents, and is shared by all the worker threads. This
191 // means that the set of threads together define the delay seen by a new
192 // event sent to the pool.
194 // To model the delay experienced by the pool, we can have each thread in
195 // the pool report 0 if it's idle OR if the pool is below the threadlimit;
196 // or otherwise the current event's queuing delay plus current running
199 // To reconstruct the delays for the pool, the profiler can look at all the
200 // threads that are part of a pool (pools have defined naming patterns that
201 // can be user to connect them). If all threads have delays at time X,
202 // that means that all threads saturated at that point and any event
203 // dispatched to the pool would get a delay.
205 // The delay experienced by an event dispatched when all pool threads are
206 // busy is based on the calculations shown in platform.cpp. Run that
207 // algorithm for each thread in the pool, and the delay at time X is the
208 // longest value for time X of any of the threads, OR the time from X until
209 // any one of the threads reports 0 (i.e. it's not busy), whichever is
212 // In order to record this when the profiler samples threads in the pool,
213 // each thread must (effectively) override GetRunnningEventDelay, by
214 // resetting the mLastEventDelay/Start values in the nsThread when we start
215 // to run an event (or when we run out of events to run). Note that handling
216 // the shutdown of a thread may be a little tricky.
219 nsThreadPool::Run() {
220 nsCOMPtr
<nsIThread
> current
;
221 nsThreadManager::get().GetCurrentThread(getter_AddRefs(current
));
223 bool shutdownThreadOnExit
= false;
224 bool exitThread
= false;
225 bool wasIdle
= false;
227 nsIThread::QoSPriority threadPriority
= nsIThread::QOS_PRIORITY_NORMAL
;
229 // This thread is an nsThread created below with NS_NewNamedThread()
230 static_cast<nsThread
*>(current
.get())
231 ->SetPoolThreadFreePtr(&mIsAPoolThreadFree
);
233 nsCOMPtr
<nsIThreadPoolListener
> listener
;
235 MutexAutoLock
lock(mMutex
);
236 listener
= mListener
;
237 LOG(("THRD-P(%p) enter %s\n", this, mName
.BeginReading()));
239 // Go ahead and check for thread priority. If priority is normal, do nothing
240 // because threads are created with default priority.
241 if (threadPriority
!= mQoSPriority
) {
242 current
->SetThreadQoS(threadPriority
);
243 threadPriority
= mQoSPriority
;
248 listener
->OnThreadCreated();
251 MOZ_ASSERT(!gCurrentThreadPool
.get());
252 gCurrentThreadPool
.set(this);
255 nsCOMPtr
<nsIRunnable
> event
;
258 MutexAutoLock
lock(mMutex
);
260 // Before getting the next event, we can adjust priority as needed.
261 if (threadPriority
!= mQoSPriority
) {
262 current
->SetThreadQoS(threadPriority
);
263 threadPriority
= mQoSPriority
;
266 event
= mEvents
.GetEvent(lock
, &delay
);
268 TimeStamp now
= TimeStamp::Now();
269 uint32_t idleTimeoutDivider
=
270 (mIdleCount
&& mRegressiveMaxIdleTime
) ? mIdleCount
: 1;
271 TimeDuration timeout
= TimeDuration::FromMilliseconds(
272 static_cast<double>(mIdleThreadTimeout
) / idleTimeoutDivider
);
274 // If we are shutting down, then don't keep any idle threads.
279 // if too many idle threads or idle for too long, then bail.
280 if (mIdleCount
> mIdleThreadLimit
||
281 (mIdleThreadTimeout
!= UINT32_MAX
&&
282 (now
- idleSince
) >= timeout
)) {
286 // if would be too many idle threads...
287 if (mIdleCount
== mIdleThreadLimit
) {
301 shutdownThreadOnExit
= mThreads
.RemoveObject(current
);
303 // keep track if there are threads available to start
304 mIsAPoolThreadFree
= (mThreads
.Count() < (int32_t)mThreadLimit
);
306 current
->SetRunningEventDelay(TimeDuration(), TimeStamp());
308 AUTO_PROFILER_LABEL("nsThreadPool::Run::Wait", IDLE
);
310 TimeDuration delta
= timeout
- (now
- idleSince
);
311 LOG(("THRD-P(%p) %s waiting [%f]\n", this, mName
.BeginReading(),
312 delta
.ToMilliseconds()));
313 mEventsAvailable
.Wait(delta
);
314 LOG(("THRD-P(%p) done waiting\n", this));
316 } else if (wasIdle
) {
322 if (MOZ_LOG_TEST(sThreadPoolLog
, mozilla::LogLevel::Debug
)) {
323 MutexAutoLock
lock(mMutex
);
324 LOG(("THRD-P(%p) %s running [%p]\n", this, mName
.BeginReading(),
328 // Delay event processing to encourage whoever dispatched this event
330 DelayForChaosMode(ChaosFeature::TaskRunning
, 1000);
332 if (profiler_thread_is_being_profiled(
333 ThreadProfilingFeatures::Sampling
)) {
334 // We'll handle the case of unstarted threads available
336 current
->SetRunningEventDelay(delay
, TimeStamp::Now());
339 LogRunnable::Run
log(event
);
340 AUTO_PROFILE_FOLLOWING_RUNNABLE(event
);
342 // To cover the event's destructor code in the LogRunnable span
345 } while (!exitThread
);
348 listener
->OnThreadShuttingDown();
351 MOZ_ASSERT(gCurrentThreadPool
.get() == this);
352 gCurrentThreadPool
.set(nullptr);
354 if (shutdownThreadOnExit
) {
355 ShutdownThread(current
);
358 LOG(("THRD-P(%p) leave\n", this));
363 nsThreadPool::DispatchFromScript(nsIRunnable
* aEvent
, uint32_t aFlags
) {
364 nsCOMPtr
<nsIRunnable
> event(aEvent
);
365 return Dispatch(event
.forget(), aFlags
);
369 nsThreadPool::Dispatch(already_AddRefed
<nsIRunnable
> aEvent
, uint32_t aFlags
) {
370 LOG(("THRD-P(%p) dispatch [%p %x]\n", this, /* XXX aEvent*/ nullptr, aFlags
));
372 if (NS_WARN_IF(mShutdown
)) {
373 nsCOMPtr
<nsIRunnable
> event(aEvent
);
374 return NS_ERROR_NOT_AVAILABLE
;
377 NS_ASSERTION(aFlags
== NS_DISPATCH_NORMAL
|| aFlags
== NS_DISPATCH_AT_END
,
378 "unexpected dispatch flags");
379 PutEvent(std::move(aEvent
), aFlags
);
384 nsThreadPool::DelayedDispatch(already_AddRefed
<nsIRunnable
>, uint32_t) {
385 return NS_ERROR_NOT_IMPLEMENTED
;
389 nsThreadPool::RegisterShutdownTask(nsITargetShutdownTask
*) {
390 return NS_ERROR_NOT_IMPLEMENTED
;
394 nsThreadPool::UnregisterShutdownTask(nsITargetShutdownTask
*) {
395 return NS_ERROR_NOT_IMPLEMENTED
;
399 nsThreadPool::IsOnCurrentThreadInfallible() {
400 return gCurrentThreadPool
.get() == this;
404 nsThreadPool::IsOnCurrentThread(bool* aResult
) {
405 MutexAutoLock
lock(mMutex
);
406 if (NS_WARN_IF(mShutdown
)) {
407 return NS_ERROR_NOT_AVAILABLE
;
410 *aResult
= IsOnCurrentThreadInfallible();
415 nsThreadPool::Shutdown() { return ShutdownWithTimeout(-1); }
418 nsThreadPool::ShutdownWithTimeout(int32_t aTimeoutMs
) {
419 nsCOMArray
<nsIThread
> threads
;
420 nsCOMPtr
<nsIThreadPoolListener
> listener
;
422 MutexAutoLock
lock(mMutex
);
424 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN
;
427 mEventsAvailable
.NotifyAll();
429 threads
.AppendObjects(mThreads
);
432 // Swap in a null listener so that we release the listener at the end of
433 // this method. The listener will be kept alive as long as the other threads
434 // that were created when it was set.
435 mListener
.swap(listener
);
438 nsTArray
<nsCOMPtr
<nsIThreadShutdown
>> contexts
;
439 for (int32_t i
= 0; i
< threads
.Count(); ++i
) {
440 nsCOMPtr
<nsIThreadShutdown
> context
;
441 if (NS_SUCCEEDED(threads
[i
]->BeginShutdown(getter_AddRefs(context
)))) {
442 contexts
.AppendElement(std::move(context
));
446 // Start a timer which will stop waiting & leak the thread, forcing
447 // onCompletion to be called when it expires.
448 nsCOMPtr
<nsITimer
> timer
;
449 if (aTimeoutMs
>= 0) {
450 NS_NewTimerWithCallback(
451 getter_AddRefs(timer
),
453 for (auto& context
: contexts
) {
454 context
->StopWaitingAndLeakThread();
457 aTimeoutMs
, nsITimer::TYPE_ONE_SHOT
,
458 "nsThreadPool::ShutdownWithTimeout");
461 // Start a counter and register a callback to decrement outstandingThreads
462 // when the threads finish exiting. We'll spin an event loop until
463 // outstandingThreads reaches 0.
464 uint32_t outstandingThreads
= contexts
.Length();
465 RefPtr onCompletion
= NS_NewCancelableRunnableFunction(
466 "nsThreadPool thread completion", [&] { --outstandingThreads
; });
467 for (auto& context
: contexts
) {
468 context
->OnCompletion(onCompletion
);
471 mozilla::SpinEventLoopUntil("nsThreadPool::ShutdownWithTimeout"_ns
,
472 [&] { return outstandingThreads
== 0; });
477 onCompletion
->Cancel();
483 nsThreadPool::GetThreadLimit(uint32_t* aValue
) {
484 MutexAutoLock
lock(mMutex
);
485 *aValue
= mThreadLimit
;
490 nsThreadPool::SetThreadLimit(uint32_t aValue
) {
491 MutexAutoLock
lock(mMutex
);
492 LOG(("THRD-P(%p) thread limit [%u]\n", this, aValue
));
493 mThreadLimit
= aValue
;
494 if (mIdleThreadLimit
> mThreadLimit
) {
495 mIdleThreadLimit
= mThreadLimit
;
498 if (static_cast<uint32_t>(mThreads
.Count()) > mThreadLimit
) {
500 .NotifyAll(); // wake up threads so they observe this change
506 nsThreadPool::GetIdleThreadLimit(uint32_t* aValue
) {
507 MutexAutoLock
lock(mMutex
);
508 *aValue
= mIdleThreadLimit
;
513 nsThreadPool::SetIdleThreadLimit(uint32_t aValue
) {
514 MutexAutoLock
lock(mMutex
);
515 LOG(("THRD-P(%p) idle thread limit [%u]\n", this, aValue
));
516 mIdleThreadLimit
= aValue
;
517 if (mIdleThreadLimit
> mThreadLimit
) {
518 mIdleThreadLimit
= mThreadLimit
;
521 // Do we need to kill some idle threads?
522 if (mIdleCount
> mIdleThreadLimit
) {
524 .NotifyAll(); // wake up threads so they observe this change
530 nsThreadPool::GetIdleThreadTimeout(uint32_t* aValue
) {
531 MutexAutoLock
lock(mMutex
);
532 *aValue
= mIdleThreadTimeout
;
537 nsThreadPool::SetIdleThreadTimeout(uint32_t aValue
) {
538 MutexAutoLock
lock(mMutex
);
539 uint32_t oldTimeout
= mIdleThreadTimeout
;
540 mIdleThreadTimeout
= aValue
;
542 // Do we need to notify any idle threads that their sleep time has shortened?
543 if (mIdleThreadTimeout
< oldTimeout
&& mIdleCount
> 0) {
545 .NotifyAll(); // wake up threads so they observe this change
551 nsThreadPool::GetIdleThreadTimeoutRegressive(bool* aValue
) {
552 MutexAutoLock
lock(mMutex
);
553 *aValue
= mRegressiveMaxIdleTime
;
558 nsThreadPool::SetIdleThreadTimeoutRegressive(bool aValue
) {
559 MutexAutoLock
lock(mMutex
);
560 bool oldRegressive
= mRegressiveMaxIdleTime
;
561 mRegressiveMaxIdleTime
= aValue
;
563 // Would setting regressive timeout effect idle threads?
564 if (mRegressiveMaxIdleTime
> oldRegressive
&& mIdleCount
> 1) {
566 .NotifyAll(); // wake up threads so they observe this change
572 nsThreadPool::GetThreadStackSize(uint32_t* aValue
) {
573 MutexAutoLock
lock(mMutex
);
574 *aValue
= mStackSize
;
579 nsThreadPool::SetThreadStackSize(uint32_t aValue
) {
580 MutexAutoLock
lock(mMutex
);
586 nsThreadPool::GetListener(nsIThreadPoolListener
** aListener
) {
587 MutexAutoLock
lock(mMutex
);
588 NS_IF_ADDREF(*aListener
= mListener
);
593 nsThreadPool::SetListener(nsIThreadPoolListener
* aListener
) {
594 nsCOMPtr
<nsIThreadPoolListener
> swappedListener(aListener
);
596 MutexAutoLock
lock(mMutex
);
597 mListener
.swap(swappedListener
);
603 nsThreadPool::SetName(const nsACString
& aName
) {
604 MutexAutoLock
lock(mMutex
);
605 if (mThreads
.Count()) {
606 return NS_ERROR_NOT_AVAILABLE
;