Bug 1874684 - Part 4: Prefer const references instead of copying Instant values....
[gecko.git] / xpcom / threads / nsThreadPool.cpp
blobc2ef022035b387ccd824dfd6c83e7a95ec89c2e5
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"
12 #include "nsThread.h"
13 #include "nsThreadUtils.h"
14 #include "prinrval.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"
23 #include <mutex>
25 using namespace mozilla;
27 static LazyLogModule sThreadPoolLog("nsThreadPool");
28 #ifdef LOG
29 # undef LOG
30 #endif
31 #define LOG(args) MOZ_LOG(sThreadPoolLog, mozilla::LogLevel::Debug, args)
33 static MOZ_THREAD_LOCAL(nsThreadPool*) gCurrentThreadPool;
35 void nsThreadPool::InitTLS() { gCurrentThreadPool.infallibleInit(); }
37 // DESIGN:
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,
48 nsIEventTarget)
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),
61 mIdleCount(0),
62 mQoSPriority(nsIThread::QOS_PRIORITY_NORMAL),
63 mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE),
64 mShutdown(false),
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,
82 uint32_t aFlags) {
83 // Avoid spawning a new thread while holding the event queue lock...
85 bool spawnThread = false;
86 uint32_t stackSize = 0;
87 nsCString name;
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(),
95 mThreadLimit));
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) {
104 spawnThread = true;
107 nsCOMPtr<nsIRunnable> event(aEvent);
108 LogRunnable::LogDispatch(event);
109 mEvents.PutEvent(event.forget(), EventQueuePriority::Normal, lock);
110 mEventsAvailable.Notify();
111 stackSize = mStackSize;
112 name = mName;
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));
121 if (!spawnThread) {
122 return NS_OK;
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);
136 if (mShutdown) {
137 killThread = true;
138 } else if (mThreads.Count() < (int32_t)mThreadLimit) {
139 mThreads.AppendObject(thread);
140 if (mThreads.Count() >= (int32_t)mThreadLimit) {
141 mIsAPoolThreadFree = false;
143 } else {
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));
149 if (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);
153 } else {
154 thread->Dispatch(this, NS_DISPATCH_IGNORE_BLOCK_DISPATCH);
157 return NS_OK;
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));
177 NS_IMETHODIMP
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.
186 return NS_OK;
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
197 // time.
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
210 // shorter.
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.
218 NS_IMETHODIMP
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;
226 TimeStamp idleSince;
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;
247 if (listener) {
248 listener->OnThreadCreated();
251 MOZ_ASSERT(!gCurrentThreadPool.get());
252 gCurrentThreadPool.set(this);
254 do {
255 nsCOMPtr<nsIRunnable> event;
256 TimeDuration delay;
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);
267 if (!event) {
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.
275 if (mShutdown) {
276 exitThread = true;
277 } else {
278 if (wasIdle) {
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)) {
283 exitThread = true;
285 } else {
286 // if would be too many idle threads...
287 if (mIdleCount == mIdleThreadLimit) {
288 exitThread = true;
289 } else {
290 ++mIdleCount;
291 idleSince = now;
292 wasIdle = true;
297 if (exitThread) {
298 if (wasIdle) {
299 --mIdleCount;
301 shutdownThreadOnExit = mThreads.RemoveObject(current);
303 // keep track if there are threads available to start
304 mIsAPoolThreadFree = (mThreads.Count() < (int32_t)mThreadLimit);
305 } else {
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) {
317 wasIdle = false;
318 --mIdleCount;
321 if (event) {
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(),
325 event.get()));
328 // Delay event processing to encourage whoever dispatched this event
329 // to run.
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
335 // when we sample.
336 current->SetRunningEventDelay(delay, TimeStamp::Now());
339 LogRunnable::Run log(event);
340 AUTO_PROFILE_FOLLOWING_RUNNABLE(event);
341 event->Run();
342 // To cover the event's destructor code in the LogRunnable span
343 event = nullptr;
345 } while (!exitThread);
347 if (listener) {
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));
359 return NS_OK;
362 NS_IMETHODIMP
363 nsThreadPool::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
364 nsCOMPtr<nsIRunnable> event(aEvent);
365 return Dispatch(event.forget(), aFlags);
368 NS_IMETHODIMP
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);
380 return NS_OK;
383 NS_IMETHODIMP
384 nsThreadPool::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) {
385 return NS_ERROR_NOT_IMPLEMENTED;
388 NS_IMETHODIMP
389 nsThreadPool::RegisterShutdownTask(nsITargetShutdownTask*) {
390 return NS_ERROR_NOT_IMPLEMENTED;
393 NS_IMETHODIMP
394 nsThreadPool::UnregisterShutdownTask(nsITargetShutdownTask*) {
395 return NS_ERROR_NOT_IMPLEMENTED;
398 NS_IMETHODIMP_(bool)
399 nsThreadPool::IsOnCurrentThreadInfallible() {
400 return gCurrentThreadPool.get() == this;
403 NS_IMETHODIMP
404 nsThreadPool::IsOnCurrentThread(bool* aResult) {
405 MutexAutoLock lock(mMutex);
406 if (NS_WARN_IF(mShutdown)) {
407 return NS_ERROR_NOT_AVAILABLE;
410 *aResult = IsOnCurrentThreadInfallible();
411 return NS_OK;
414 NS_IMETHODIMP
415 nsThreadPool::Shutdown() { return ShutdownWithTimeout(-1); }
417 NS_IMETHODIMP
418 nsThreadPool::ShutdownWithTimeout(int32_t aTimeoutMs) {
419 nsCOMArray<nsIThread> threads;
420 nsCOMPtr<nsIThreadPoolListener> listener;
422 MutexAutoLock lock(mMutex);
423 if (mShutdown) {
424 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
426 mShutdown = true;
427 mEventsAvailable.NotifyAll();
429 threads.AppendObjects(mThreads);
430 mThreads.Clear();
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),
452 [&](nsITimer*) {
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; });
474 if (timer) {
475 timer->Cancel();
477 onCompletion->Cancel();
479 return NS_OK;
482 NS_IMETHODIMP
483 nsThreadPool::GetThreadLimit(uint32_t* aValue) {
484 MutexAutoLock lock(mMutex);
485 *aValue = mThreadLimit;
486 return NS_OK;
489 NS_IMETHODIMP
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) {
499 mEventsAvailable
500 .NotifyAll(); // wake up threads so they observe this change
502 return NS_OK;
505 NS_IMETHODIMP
506 nsThreadPool::GetIdleThreadLimit(uint32_t* aValue) {
507 MutexAutoLock lock(mMutex);
508 *aValue = mIdleThreadLimit;
509 return NS_OK;
512 NS_IMETHODIMP
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) {
523 mEventsAvailable
524 .NotifyAll(); // wake up threads so they observe this change
526 return NS_OK;
529 NS_IMETHODIMP
530 nsThreadPool::GetIdleThreadTimeout(uint32_t* aValue) {
531 MutexAutoLock lock(mMutex);
532 *aValue = mIdleThreadTimeout;
533 return NS_OK;
536 NS_IMETHODIMP
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) {
544 mEventsAvailable
545 .NotifyAll(); // wake up threads so they observe this change
547 return NS_OK;
550 NS_IMETHODIMP
551 nsThreadPool::GetIdleThreadTimeoutRegressive(bool* aValue) {
552 MutexAutoLock lock(mMutex);
553 *aValue = mRegressiveMaxIdleTime;
554 return NS_OK;
557 NS_IMETHODIMP
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) {
565 mEventsAvailable
566 .NotifyAll(); // wake up threads so they observe this change
568 return NS_OK;
571 NS_IMETHODIMP
572 nsThreadPool::GetThreadStackSize(uint32_t* aValue) {
573 MutexAutoLock lock(mMutex);
574 *aValue = mStackSize;
575 return NS_OK;
578 NS_IMETHODIMP
579 nsThreadPool::SetThreadStackSize(uint32_t aValue) {
580 MutexAutoLock lock(mMutex);
581 mStackSize = aValue;
582 return NS_OK;
585 NS_IMETHODIMP
586 nsThreadPool::GetListener(nsIThreadPoolListener** aListener) {
587 MutexAutoLock lock(mMutex);
588 NS_IF_ADDREF(*aListener = mListener);
589 return NS_OK;
592 NS_IMETHODIMP
593 nsThreadPool::SetListener(nsIThreadPoolListener* aListener) {
594 nsCOMPtr<nsIThreadPoolListener> swappedListener(aListener);
596 MutexAutoLock lock(mMutex);
597 mListener.swap(swappedListener);
599 return NS_OK;
602 NS_IMETHODIMP
603 nsThreadPool::SetName(const nsACString& aName) {
604 MutexAutoLock lock(mMutex);
605 if (mThreads.Count()) {
606 return NS_ERROR_NOT_AVAILABLE;
608 mName = aName;
609 return NS_OK;