LCOV - code coverage report
Current view: top level - xpcom/threads - nsThreadPool.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 27 201 13.4 %
Date: 2018-08-07 16:35:00 Functions: 0 0 -
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13-14-ga5dd952