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

Generated by: LCOV version 1.13