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 "nsCOMArray.h"
8 #include "ThreadDelay.h"
9 #include "nsThreadPool.h"
10 #include "nsThreadManager.h"
14 #include "mozilla/Logging.h"
15 #include "mozilla/ProfilerLabels.h"
16 #include "mozilla/SchedulerGroup.h"
17 #include "mozilla/ScopeExit.h"
18 #include "mozilla/SpinEventLoopUntil.h"
19 #include "nsThreadSyncDispatch.h"
23 using namespace mozilla
;
25 static LazyLogModule
sThreadPoolLog("nsThreadPool");
29 #define LOG(args) MOZ_LOG(sThreadPoolLog, mozilla::LogLevel::Debug, args)
31 static MOZ_THREAD_LOCAL(nsThreadPool
*) gCurrentThreadPool
;
34 // o Allocate anonymous threads.
35 // o Use nsThreadPool::Run as the main routine for each thread.
36 // o Each thread waits on the event queue's monitor, checking for
37 // pending events and rescheduling itself as an idle thread.
39 #define DEFAULT_THREAD_LIMIT 4
40 #define DEFAULT_IDLE_THREAD_LIMIT 1
41 #define DEFAULT_IDLE_THREAD_TIMEOUT PR_SecondsToInterval(60)
43 NS_IMPL_ISUPPORTS_INHERITED(nsThreadPool
, Runnable
, nsIThreadPool
,
46 nsThreadPool::nsThreadPool()
47 : Runnable("nsThreadPool"),
48 mMutex("[nsThreadPool.mMutex]"),
49 mEventsAvailable(mMutex
, "[nsThreadPool.mEventsAvailable]"),
50 mThreadLimit(DEFAULT_THREAD_LIMIT
),
51 mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT
),
52 mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT
),
54 mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE
),
56 mRegressiveMaxIdleTime(false),
57 mIsAPoolThreadFree(true) {
58 static std::once_flag flag
;
59 std::call_once(flag
, [] { gCurrentThreadPool
.infallibleInit(); });
61 LOG(("THRD-P(%p) constructor!!!\n", this));
64 nsThreadPool::~nsThreadPool() {
65 // Threads keep a reference to the nsThreadPool until they return from Run()
66 // after removing themselves from mThreads.
67 MOZ_ASSERT(mThreads
.IsEmpty());
70 nsresult
nsThreadPool::PutEvent(nsIRunnable
* aEvent
) {
71 nsCOMPtr
<nsIRunnable
> event(aEvent
);
72 return PutEvent(event
.forget(), 0);
75 nsresult
nsThreadPool::PutEvent(already_AddRefed
<nsIRunnable
> aEvent
,
77 // Avoid spawning a new thread while holding the event queue lock...
79 bool spawnThread
= false;
80 uint32_t stackSize
= 0;
82 MutexAutoLock
lock(mMutex
);
84 if (NS_WARN_IF(mShutdown
)) {
85 return NS_ERROR_NOT_AVAILABLE
;
87 LOG(("THRD-P(%p) put [%d %d %d]\n", this, mIdleCount
, mThreads
.Count(),
89 MOZ_ASSERT(mIdleCount
<= (uint32_t)mThreads
.Count(), "oops");
91 // Make sure we have a thread to service this event.
92 if (mThreads
.Count() < (int32_t)mThreadLimit
&&
93 !(aFlags
& NS_DISPATCH_AT_END
) &&
94 // Spawn a new thread if we don't have enough idle threads to serve
95 // pending events immediately.
96 mEvents
.Count(lock
) >= mIdleCount
) {
100 nsCOMPtr
<nsIRunnable
> event(aEvent
);
101 LogRunnable::LogDispatch(event
);
102 mEvents
.PutEvent(event
.forget(), EventQueuePriority::Normal
, lock
);
103 mEventsAvailable
.Notify();
104 stackSize
= mStackSize
;
107 auto delay
= MakeScopeExit([&]() {
108 // Delay to encourage the receiving task to run before we do work.
109 DelayForChaosMode(ChaosFeature::TaskDispatching
, 1000);
112 LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread
));
117 nsCOMPtr
<nsIThread
> thread
;
118 nsresult rv
= NS_NewNamedThread(mThreadNaming
.GetNextThreadName(mName
),
119 getter_AddRefs(thread
), nullptr, stackSize
);
120 if (NS_WARN_IF(NS_FAILED(rv
))) {
121 return NS_ERROR_UNEXPECTED
;
124 bool killThread
= false;
126 MutexAutoLock
lock(mMutex
);
129 } else if (mThreads
.Count() < (int32_t)mThreadLimit
) {
130 mThreads
.AppendObject(thread
);
131 if (mThreads
.Count() >= (int32_t)mThreadLimit
) {
132 mIsAPoolThreadFree
= false;
135 // Someone else may have also been starting a thread
136 killThread
= true; // okay, we don't need this thread anymore
139 LOG(("THRD-P(%p) put [%p kill=%d]\n", this, thread
.get(), killThread
));
141 // We never dispatched any events to the thread, so we can shut it down
142 // asynchronously without worrying about anything.
143 ShutdownThread(thread
);
145 thread
->Dispatch(this, NS_DISPATCH_NORMAL
);
151 void nsThreadPool::ShutdownThread(nsIThread
* aThread
) {
152 LOG(("THRD-P(%p) shutdown async [%p]\n", this, aThread
));
154 // This is either called by a threadpool thread that is out of work, or
155 // a thread that attempted to create a threadpool thread and raced in
156 // such a way that the newly created thread is no longer necessary.
157 // In the first case, we must go to another thread to shut aThread down
158 // (because it is the current thread). In the second case, we cannot
159 // synchronously shut down the current thread (because then Dispatch() would
160 // spin the event loop, and that could blow up the world), and asynchronous
161 // shutdown requires this thread have an event loop (and it may not, see bug
162 // 10204784). The simplest way to cover all cases is to asynchronously
163 // shutdown aThread from the main thread.
164 SchedulerGroup::Dispatch(
166 NewRunnableMethod("nsIThread::AsyncShutdown", aThread
,
167 &nsIThread::AsyncShutdown
));
170 // This event 'runs' for the lifetime of the worker thread. The actual
171 // eventqueue is mEvents, and is shared by all the worker threads. This
172 // means that the set of threads together define the delay seen by a new
173 // event sent to the pool.
175 // To model the delay experienced by the pool, we can have each thread in
176 // the pool report 0 if it's idle OR if the pool is below the threadlimit;
177 // or otherwise the current event's queuing delay plus current running
180 // To reconstruct the delays for the pool, the profiler can look at all the
181 // threads that are part of a pool (pools have defined naming patterns that
182 // can be user to connect them). If all threads have delays at time X,
183 // that means that all threads saturated at that point and any event
184 // dispatched to the pool would get a delay.
186 // The delay experienced by an event dispatched when all pool threads are
187 // busy is based on the calculations shown in platform.cpp. Run that
188 // algorithm for each thread in the pool, and the delay at time X is the
189 // longest value for time X of any of the threads, OR the time from X until
190 // any one of the threads reports 0 (i.e. it's not busy), whichever is
193 // In order to record this when the profiler samples threads in the pool,
194 // each thread must (effectively) override GetRunnningEventDelay, by
195 // resetting the mLastEventDelay/Start values in the nsThread when we start
196 // to run an event (or when we run out of events to run). Note that handling
197 // the shutdown of a thread may be a little tricky.
200 nsThreadPool::Run() {
201 LOG(("THRD-P(%p) enter %s\n", this, mName
.BeginReading()));
203 nsCOMPtr
<nsIThread
> current
;
204 nsThreadManager::get().GetCurrentThread(getter_AddRefs(current
));
206 bool shutdownThreadOnExit
= false;
207 bool exitThread
= false;
208 bool wasIdle
= false;
211 // This thread is an nsThread created below with NS_NewNamedThread()
212 static_cast<nsThread
*>(current
.get())
213 ->SetPoolThreadFreePtr(&mIsAPoolThreadFree
);
215 nsCOMPtr
<nsIThreadPoolListener
> listener
;
217 MutexAutoLock
lock(mMutex
);
218 listener
= mListener
;
222 listener
->OnThreadCreated();
225 MOZ_ASSERT(!gCurrentThreadPool
.get());
226 gCurrentThreadPool
.set(this);
229 nsCOMPtr
<nsIRunnable
> event
;
232 MutexAutoLock
lock(mMutex
);
234 event
= mEvents
.GetEvent(lock
, &delay
);
236 TimeStamp now
= TimeStamp::Now();
237 uint32_t idleTimeoutDivider
=
238 (mIdleCount
&& mRegressiveMaxIdleTime
) ? mIdleCount
: 1;
239 TimeDuration timeout
= TimeDuration::FromMilliseconds(
240 static_cast<double>(mIdleThreadTimeout
) / idleTimeoutDivider
);
242 // If we are shutting down, then don't keep any idle threads
247 // if too many idle threads or idle for too long, then bail.
248 if (mIdleCount
> mIdleThreadLimit
||
249 (mIdleThreadTimeout
!= UINT32_MAX
&&
250 (now
- idleSince
) >= timeout
)) {
254 // if would be too many idle threads...
255 if (mIdleCount
== mIdleThreadLimit
) {
269 shutdownThreadOnExit
= mThreads
.RemoveObject(current
);
271 // keep track if there are threads available to start
272 mIsAPoolThreadFree
= (mThreads
.Count() < (int32_t)mThreadLimit
);
274 current
->SetRunningEventDelay(TimeDuration(), TimeStamp());
276 AUTO_PROFILER_LABEL("nsThreadPool::Run::Wait", IDLE
);
278 TimeDuration delta
= timeout
- (now
- idleSince
);
279 LOG(("THRD-P(%p) %s waiting [%f]\n", this, mName
.BeginReading(),
280 delta
.ToMilliseconds()));
281 mEventsAvailable
.Wait(delta
);
282 LOG(("THRD-P(%p) done waiting\n", this));
284 } else if (wasIdle
) {
290 LOG(("THRD-P(%p) %s running [%p]\n", this, mName
.BeginReading(),
293 // Delay event processing to encourage whoever dispatched this event
295 DelayForChaosMode(ChaosFeature::TaskRunning
, 1000);
297 // We'll handle the case of unstarted threads available
299 current
->SetRunningEventDelay(delay
, TimeStamp::Now());
301 LogRunnable::Run
log(event
);
302 AUTO_PROFILE_FOLLOWING_RUNNABLE(event
);
304 // To cover the event's destructor code in the LogRunnable span
307 } while (!exitThread
);
310 listener
->OnThreadShuttingDown();
313 MOZ_ASSERT(gCurrentThreadPool
.get() == this);
314 gCurrentThreadPool
.set(nullptr);
316 if (shutdownThreadOnExit
) {
317 ShutdownThread(current
);
320 LOG(("THRD-P(%p) leave\n", this));
325 nsThreadPool::DispatchFromScript(nsIRunnable
* aEvent
, uint32_t aFlags
) {
326 nsCOMPtr
<nsIRunnable
> event(aEvent
);
327 return Dispatch(event
.forget(), aFlags
);
331 nsThreadPool::Dispatch(already_AddRefed
<nsIRunnable
> aEvent
, uint32_t aFlags
) {
332 LOG(("THRD-P(%p) dispatch [%p %x]\n", this, /* XXX aEvent*/ nullptr, aFlags
));
334 if (NS_WARN_IF(mShutdown
)) {
335 return NS_ERROR_NOT_AVAILABLE
;
338 if (aFlags
& DISPATCH_SYNC
) {
339 nsCOMPtr
<nsIThread
> thread
;
340 nsThreadManager::get().GetCurrentThread(getter_AddRefs(thread
));
341 if (NS_WARN_IF(!thread
)) {
342 return NS_ERROR_NOT_AVAILABLE
;
345 RefPtr
<nsThreadSyncDispatch
> wrapper
=
346 new nsThreadSyncDispatch(thread
.forget(), std::move(aEvent
));
350 [&, wrapper
]() -> bool { return !wrapper
->IsPending(); });
352 NS_ASSERTION(aFlags
== NS_DISPATCH_NORMAL
|| aFlags
== NS_DISPATCH_AT_END
,
353 "unexpected dispatch flags");
354 PutEvent(std::move(aEvent
), aFlags
);
360 nsThreadPool::DelayedDispatch(already_AddRefed
<nsIRunnable
>, uint32_t) {
361 return NS_ERROR_NOT_IMPLEMENTED
;
365 nsThreadPool::IsOnCurrentThreadInfallible() {
366 return gCurrentThreadPool
.get() == this;
370 nsThreadPool::IsOnCurrentThread(bool* aResult
) {
371 MutexAutoLock
lock(mMutex
);
372 if (NS_WARN_IF(mShutdown
)) {
373 return NS_ERROR_NOT_AVAILABLE
;
376 *aResult
= IsOnCurrentThreadInfallible();
381 nsThreadPool::Shutdown() {
382 nsCOMArray
<nsIThread
> threads
;
383 nsCOMPtr
<nsIThreadPoolListener
> listener
;
385 MutexAutoLock
lock(mMutex
);
387 mEventsAvailable
.NotifyAll();
389 threads
.AppendObjects(mThreads
);
392 // Swap in a null listener so that we release the listener at the end of
393 // this method. The listener will be kept alive as long as the other threads
394 // that were created when it was set.
395 mListener
.swap(listener
);
398 // It's important that we shutdown the threads while outside the event queue
399 // monitor. Otherwise, we could end up dead-locking.
401 for (int32_t i
= 0; i
< threads
.Count(); ++i
) {
402 threads
[i
]->Shutdown();
408 template <typename Pred
>
409 static void SpinMTEventLoopUntil(Pred
&& aPredicate
, nsIThread
* aThread
,
410 TimeDuration aTimeout
) {
411 MOZ_ASSERT(NS_IsMainThread(), "Must be run on the main thread");
413 // From a latency perspective, spinning the event loop is like leaving script
414 // and returning to the event loop. Tell the watchdog we stopped running
415 // script (until we return).
416 mozilla::Maybe
<xpc::AutoScriptActivity
> asa
;
419 TimeStamp deadline
= TimeStamp::Now() + aTimeout
;
420 while (!aPredicate() && TimeStamp::Now() < deadline
) {
421 if (!NS_ProcessNextEvent(aThread
, false)) {
422 PR_Sleep(PR_MillisecondsToInterval(1));
428 nsThreadPool::ShutdownWithTimeout(int32_t aTimeoutMs
) {
429 if (!NS_IsMainThread()) {
430 return NS_ERROR_NOT_AVAILABLE
;
433 nsCOMArray
<nsIThread
> threads
;
434 nsCOMPtr
<nsIThreadPoolListener
> listener
;
436 MutexAutoLock
lock(mMutex
);
438 mEventsAvailable
.NotifyAll();
440 threads
.AppendObjects(mThreads
);
443 // Swap in a null listener so that we release the listener at the end of
444 // this method. The listener will be kept alive as long as the other threads
445 // that were created when it was set.
446 mListener
.swap(listener
);
449 // IMPORTANT! Never dereference these pointers, as the objects may go away at
450 // any time. We just use the pointers values for comparison, to check if the
451 // thread has been shut down or not.
452 nsTArray
<nsThreadShutdownContext
*> contexts
;
454 // It's important that we shutdown the threads while outside the event queue
455 // monitor. Otherwise, we could end up dead-locking.
456 for (int32_t i
= 0; i
< threads
.Count(); ++i
) {
458 nsThreadShutdownContext
* maybeContext
=
459 static_cast<nsThread
*>(threads
[i
])->ShutdownInternal(false);
460 contexts
.AppendElement(maybeContext
);
463 NotNull
<nsThread
*> currentThread
=
464 WrapNotNull(nsThreadManager::get().GetCurrentThread());
466 // We spin the event loop until all of the threads in the thread pool
467 // have shut down, or the timeout expires.
468 SpinMTEventLoopUntil(
470 for (nsIThread
* thread
: threads
) {
471 if (static_cast<nsThread
*>(thread
)->mThread
) {
477 currentThread
, TimeDuration::FromMilliseconds(aTimeoutMs
));
479 // For any threads that have not shutdown yet, we need to remove them from
480 // mRequestedShutdownContexts so the thread manager does not wait for them
482 static const nsThread::ShutdownContextsComp comparator
{};
483 for (int32_t i
= 0; i
< threads
.Count(); ++i
) {
484 nsThread
* thread
= static_cast<nsThread
*>(threads
[i
]);
485 // If mThread is not null on the thread it means that it hasn't shutdown
486 // context[i] corresponds to thread[i]
487 if (thread
->mThread
&& contexts
[i
]) {
488 auto index
= currentThread
->mRequestedShutdownContexts
.IndexOf(
489 contexts
[i
], 0, comparator
);
490 if (index
!= nsThread::ShutdownContexts::NoIndex
) {
491 // We must leak the shutdown context just in case the leaked thread
492 // does get unstuck and completes before the main thread is done.
493 Unused
<< currentThread
->mRequestedShutdownContexts
[index
].release();
494 currentThread
->mRequestedShutdownContexts
.RemoveElementAt(index
);
503 nsThreadPool::GetThreadLimit(uint32_t* aValue
) {
504 *aValue
= mThreadLimit
;
509 nsThreadPool::SetThreadLimit(uint32_t aValue
) {
510 MutexAutoLock
lock(mMutex
);
511 LOG(("THRD-P(%p) thread limit [%u]\n", this, aValue
));
512 mThreadLimit
= aValue
;
513 if (mIdleThreadLimit
> mThreadLimit
) {
514 mIdleThreadLimit
= mThreadLimit
;
517 if (static_cast<uint32_t>(mThreads
.Count()) > mThreadLimit
) {
519 .NotifyAll(); // wake up threads so they observe this change
525 nsThreadPool::GetIdleThreadLimit(uint32_t* aValue
) {
526 *aValue
= mIdleThreadLimit
;
531 nsThreadPool::SetIdleThreadLimit(uint32_t aValue
) {
532 MutexAutoLock
lock(mMutex
);
533 LOG(("THRD-P(%p) idle thread limit [%u]\n", this, aValue
));
534 mIdleThreadLimit
= aValue
;
535 if (mIdleThreadLimit
> mThreadLimit
) {
536 mIdleThreadLimit
= mThreadLimit
;
539 // Do we need to kill some idle threads?
540 if (mIdleCount
> mIdleThreadLimit
) {
542 .NotifyAll(); // wake up threads so they observe this change
548 nsThreadPool::GetIdleThreadTimeout(uint32_t* aValue
) {
549 *aValue
= mIdleThreadTimeout
;
554 nsThreadPool::SetIdleThreadTimeout(uint32_t aValue
) {
555 MutexAutoLock
lock(mMutex
);
556 uint32_t oldTimeout
= mIdleThreadTimeout
;
557 mIdleThreadTimeout
= aValue
;
559 // Do we need to notify any idle threads that their sleep time has shortened?
560 if (mIdleThreadTimeout
< oldTimeout
&& mIdleCount
> 0) {
562 .NotifyAll(); // wake up threads so they observe this change
568 nsThreadPool::GetIdleThreadTimeoutRegressive(bool* aValue
) {
569 *aValue
= mRegressiveMaxIdleTime
;
574 nsThreadPool::SetIdleThreadTimeoutRegressive(bool aValue
) {
575 MutexAutoLock
lock(mMutex
);
576 bool oldRegressive
= mRegressiveMaxIdleTime
;
577 mRegressiveMaxIdleTime
= aValue
;
579 // Would setting regressive timeout effect idle threads?
580 if (mRegressiveMaxIdleTime
> oldRegressive
&& mIdleCount
> 1) {
582 .NotifyAll(); // wake up threads so they observe this change
588 nsThreadPool::GetThreadStackSize(uint32_t* aValue
) {
589 MutexAutoLock
lock(mMutex
);
590 *aValue
= mStackSize
;
595 nsThreadPool::SetThreadStackSize(uint32_t aValue
) {
596 MutexAutoLock
lock(mMutex
);
602 nsThreadPool::GetListener(nsIThreadPoolListener
** aListener
) {
603 MutexAutoLock
lock(mMutex
);
604 NS_IF_ADDREF(*aListener
= mListener
);
609 nsThreadPool::SetListener(nsIThreadPoolListener
* aListener
) {
610 nsCOMPtr
<nsIThreadPoolListener
> swappedListener(aListener
);
612 MutexAutoLock
lock(mMutex
);
613 mListener
.swap(swappedListener
);
619 nsThreadPool::SetName(const nsACString
& aName
) {
621 MutexAutoLock
lock(mMutex
);
622 if (mThreads
.Count()) {
623 return NS_ERROR_NOT_AVAILABLE
;