Bug 1693453 [wpt PR 27671] - Add test to check the unavailability of storages in...
[gecko.git] / xpcom / threads / nsThreadPool.cpp
blob5dfba844179fe0696b07a44ecd5b0a7532dba930
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"
11 #include "nsThread.h"
12 #include "nsMemory.h"
13 #include "prinrval.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"
21 #include <mutex>
23 using namespace mozilla;
25 static LazyLogModule sThreadPoolLog("nsThreadPool");
26 #ifdef LOG
27 # undef LOG
28 #endif
29 #define LOG(args) MOZ_LOG(sThreadPoolLog, mozilla::LogLevel::Debug, args)
31 static MOZ_THREAD_LOCAL(nsThreadPool*) gCurrentThreadPool;
33 // DESIGN:
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,
44 nsIEventTarget)
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),
53 mIdleCount(0),
54 mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE),
55 mShutdown(false),
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,
76 uint32_t aFlags) {
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(),
88 mThreadLimit));
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) {
97 spawnThread = true;
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));
113 if (!spawnThread) {
114 return NS_OK;
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);
127 if (mShutdown) {
128 killThread = true;
129 } else if (mThreads.Count() < (int32_t)mThreadLimit) {
130 mThreads.AppendObject(thread);
131 if (mThreads.Count() >= (int32_t)mThreadLimit) {
132 mIsAPoolThreadFree = false;
134 } else {
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));
140 if (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);
144 } else {
145 thread->Dispatch(this, NS_DISPATCH_NORMAL);
148 return NS_OK;
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(
165 TaskCategory::Other,
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
178 // time.
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
191 // shorter.
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.
199 NS_IMETHODIMP
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;
209 TimeStamp idleSince;
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;
221 if (listener) {
222 listener->OnThreadCreated();
225 MOZ_ASSERT(!gCurrentThreadPool.get());
226 gCurrentThreadPool.set(this);
228 do {
229 nsCOMPtr<nsIRunnable> event;
230 TimeDuration delay;
232 MutexAutoLock lock(mMutex);
234 event = mEvents.GetEvent(lock, &delay);
235 if (!event) {
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
243 if (mShutdown) {
244 exitThread = true;
245 } else {
246 if (wasIdle) {
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)) {
251 exitThread = true;
253 } else {
254 // if would be too many idle threads...
255 if (mIdleCount == mIdleThreadLimit) {
256 exitThread = true;
257 } else {
258 ++mIdleCount;
259 idleSince = now;
260 wasIdle = true;
265 if (exitThread) {
266 if (wasIdle) {
267 --mIdleCount;
269 shutdownThreadOnExit = mThreads.RemoveObject(current);
271 // keep track if there are threads available to start
272 mIsAPoolThreadFree = (mThreads.Count() < (int32_t)mThreadLimit);
273 } else {
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) {
285 wasIdle = false;
286 --mIdleCount;
289 if (event) {
290 LOG(("THRD-P(%p) %s running [%p]\n", this, mName.BeginReading(),
291 event.get()));
293 // Delay event processing to encourage whoever dispatched this event
294 // to run.
295 DelayForChaosMode(ChaosFeature::TaskRunning, 1000);
297 // We'll handle the case of unstarted threads available
298 // when we sample.
299 current->SetRunningEventDelay(delay, TimeStamp::Now());
301 LogRunnable::Run log(event);
302 AUTO_PROFILE_FOLLOWING_RUNNABLE(event);
303 event->Run();
304 // To cover the event's destructor code in the LogRunnable span
305 event = nullptr;
307 } while (!exitThread);
309 if (listener) {
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));
321 return NS_OK;
324 NS_IMETHODIMP
325 nsThreadPool::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
326 nsCOMPtr<nsIRunnable> event(aEvent);
327 return Dispatch(event.forget(), aFlags);
330 NS_IMETHODIMP
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));
347 PutEvent(wrapper);
349 SpinEventLoopUntil(
350 [&, wrapper]() -> bool { return !wrapper->IsPending(); });
351 } else {
352 NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL || aFlags == NS_DISPATCH_AT_END,
353 "unexpected dispatch flags");
354 PutEvent(std::move(aEvent), aFlags);
356 return NS_OK;
359 NS_IMETHODIMP
360 nsThreadPool::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) {
361 return NS_ERROR_NOT_IMPLEMENTED;
364 NS_IMETHODIMP_(bool)
365 nsThreadPool::IsOnCurrentThreadInfallible() {
366 return gCurrentThreadPool.get() == this;
369 NS_IMETHODIMP
370 nsThreadPool::IsOnCurrentThread(bool* aResult) {
371 MutexAutoLock lock(mMutex);
372 if (NS_WARN_IF(mShutdown)) {
373 return NS_ERROR_NOT_AVAILABLE;
376 *aResult = IsOnCurrentThreadInfallible();
377 return NS_OK;
380 NS_IMETHODIMP
381 nsThreadPool::Shutdown() {
382 nsCOMArray<nsIThread> threads;
383 nsCOMPtr<nsIThreadPoolListener> listener;
385 MutexAutoLock lock(mMutex);
386 mShutdown = true;
387 mEventsAvailable.NotifyAll();
389 threads.AppendObjects(mThreads);
390 mThreads.Clear();
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();
405 return NS_OK;
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;
417 asa.emplace(false);
419 TimeStamp deadline = TimeStamp::Now() + aTimeout;
420 while (!aPredicate() && TimeStamp::Now() < deadline) {
421 if (!NS_ProcessNextEvent(aThread, false)) {
422 PR_Sleep(PR_MillisecondsToInterval(1));
427 NS_IMETHODIMP
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);
437 mShutdown = true;
438 mEventsAvailable.NotifyAll();
440 threads.AppendObjects(mThreads);
441 mThreads.Clear();
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) {
457 // Shutdown async
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(
469 [&]() {
470 for (nsIThread* thread : threads) {
471 if (static_cast<nsThread*>(thread)->mThread) {
472 return false;
475 return true;
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
481 // at shutdown.
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);
499 return NS_OK;
502 NS_IMETHODIMP
503 nsThreadPool::GetThreadLimit(uint32_t* aValue) {
504 *aValue = mThreadLimit;
505 return NS_OK;
508 NS_IMETHODIMP
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) {
518 mEventsAvailable
519 .NotifyAll(); // wake up threads so they observe this change
521 return NS_OK;
524 NS_IMETHODIMP
525 nsThreadPool::GetIdleThreadLimit(uint32_t* aValue) {
526 *aValue = mIdleThreadLimit;
527 return NS_OK;
530 NS_IMETHODIMP
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) {
541 mEventsAvailable
542 .NotifyAll(); // wake up threads so they observe this change
544 return NS_OK;
547 NS_IMETHODIMP
548 nsThreadPool::GetIdleThreadTimeout(uint32_t* aValue) {
549 *aValue = mIdleThreadTimeout;
550 return NS_OK;
553 NS_IMETHODIMP
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) {
561 mEventsAvailable
562 .NotifyAll(); // wake up threads so they observe this change
564 return NS_OK;
567 NS_IMETHODIMP
568 nsThreadPool::GetIdleThreadTimeoutRegressive(bool* aValue) {
569 *aValue = mRegressiveMaxIdleTime;
570 return NS_OK;
573 NS_IMETHODIMP
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) {
581 mEventsAvailable
582 .NotifyAll(); // wake up threads so they observe this change
584 return NS_OK;
587 NS_IMETHODIMP
588 nsThreadPool::GetThreadStackSize(uint32_t* aValue) {
589 MutexAutoLock lock(mMutex);
590 *aValue = mStackSize;
591 return NS_OK;
594 NS_IMETHODIMP
595 nsThreadPool::SetThreadStackSize(uint32_t aValue) {
596 MutexAutoLock lock(mMutex);
597 mStackSize = aValue;
598 return NS_OK;
601 NS_IMETHODIMP
602 nsThreadPool::GetListener(nsIThreadPoolListener** aListener) {
603 MutexAutoLock lock(mMutex);
604 NS_IF_ADDREF(*aListener = mListener);
605 return NS_OK;
608 NS_IMETHODIMP
609 nsThreadPool::SetListener(nsIThreadPoolListener* aListener) {
610 nsCOMPtr<nsIThreadPoolListener> swappedListener(aListener);
612 MutexAutoLock lock(mMutex);
613 mListener.swap(swappedListener);
615 return NS_OK;
618 NS_IMETHODIMP
619 nsThreadPool::SetName(const nsACString& aName) {
621 MutexAutoLock lock(mMutex);
622 if (mThreads.Count()) {
623 return NS_ERROR_NOT_AVAILABLE;
627 mName = aName;
628 return NS_OK;