xpcom/threads/nsThreadPool.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
     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 "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 "prlog.h"
    16 using namespace mozilla;
    18 #ifdef PR_LOGGING
    19 static PRLogModuleInfo *
    20 GetThreadPoolLog()
    21 {
    22   static PRLogModuleInfo *sLog;
    23   if (!sLog)
    24     sLog = PR_NewLogModule("nsThreadPool");
    25   return sLog;
    26 }
    27 #endif
    28 #ifdef LOG
    29 #undef LOG
    30 #endif
    31 #define LOG(args) PR_LOG(GetThreadPoolLog(), PR_LOG_DEBUG, args)
    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_ADDREF(nsThreadPool)
    44 NS_IMPL_RELEASE(nsThreadPool)
    45 NS_IMPL_CLASSINFO(nsThreadPool, nullptr, nsIClassInfo::THREADSAFE,
    46                   NS_THREADPOOL_CID)
    47 NS_IMPL_QUERY_INTERFACE_CI(nsThreadPool, nsIThreadPool, nsIEventTarget,
    48                            nsIRunnable)
    49 NS_IMPL_CI_INTERFACE_GETTER(nsThreadPool, nsIThreadPool, nsIEventTarget)
    51 nsThreadPool::nsThreadPool()
    52   : mThreadLimit(DEFAULT_THREAD_LIMIT)
    53   , mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT)
    54   , mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT)
    55   , mIdleCount(0)
    56   , mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE)
    57   , mShutdown(false)
    58 {
    59 }
    61 nsThreadPool::~nsThreadPool()
    62 {
    63   // Threads keep a reference to the nsThreadPool until they return from Run()
    64   // after removing themselves from mThreads.
    65   MOZ_ASSERT(mThreads.IsEmpty());
    66 }
    68 nsresult
    69 nsThreadPool::PutEvent(nsIRunnable *event)
    70 {
    71   // Avoid spawning a new thread while holding the event queue lock...
    73   bool spawnThread = false;
    74   uint32_t stackSize = 0;
    75   {
    76     ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
    78     LOG(("THRD-P(%p) put [%d %d %d]\n", this, mIdleCount, mThreads.Count(),
    79          mThreadLimit));
    80     MOZ_ASSERT(mIdleCount <= (uint32_t) mThreads.Count(), "oops");
    82     // Make sure we have a thread to service this event.
    83     if (mIdleCount == 0 && mThreads.Count() < (int32_t) mThreadLimit)
    84       spawnThread = true;
    86     mEvents.PutEvent(event);
    87     stackSize = mStackSize;
    88   }
    90   LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread));
    91   if (!spawnThread)
    92     return NS_OK;
    94   nsCOMPtr<nsIThread> thread;
    95   nsThreadManager::get()->NewThread(0,
    96                                     stackSize,
    97                                     getter_AddRefs(thread));
    98   if (NS_WARN_IF(!thread))
    99     return NS_ERROR_UNEXPECTED;
   101   bool killThread = false;
   102   {
   103     ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
   104     if (mThreads.Count() < (int32_t) mThreadLimit) {
   105       mThreads.AppendObject(thread);
   106     } else {
   107       killThread = true;  // okay, we don't need this thread anymore
   108     }
   109   }
   110   LOG(("THRD-P(%p) put [%p kill=%d]\n", this, thread.get(), killThread));
   111   if (killThread) {
   112     // Pending events are processed on the current thread during
   113     // nsIThread::Shutdown() execution, so if nsThreadPool::Dispatch() is called
   114     // under caller's lock then deadlock could occur. This happens e.g. in case
   115     // of nsStreamCopier. To prevent this situation, dispatch a shutdown event
   116     // to the current thread instead of calling nsIThread::Shutdown() directly.
   118     nsRefPtr<nsIRunnable> r = NS_NewRunnableMethod(thread,
   119                                                    &nsIThread::Shutdown);
   120     NS_DispatchToCurrentThread(r);
   121   } else {
   122     thread->Dispatch(this, NS_DISPATCH_NORMAL);
   123   }
   125   return NS_OK;
   126 }
   128 void
   129 nsThreadPool::ShutdownThread(nsIThread *thread)
   130 {
   131   LOG(("THRD-P(%p) shutdown async [%p]\n", this, thread));
   133   // This method is responsible for calling Shutdown on |thread|.  This must be
   134   // done from some other thread, so we use the main thread of the application.
   136   MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
   138   nsRefPtr<nsIRunnable> r = NS_NewRunnableMethod(thread, &nsIThread::Shutdown);
   139   NS_DispatchToMainThread(r);
   140 }
   142 NS_IMETHODIMP
   143 nsThreadPool::Run()
   144 {
   145   LOG(("THRD-P(%p) enter\n", this));
   147   mThreadNaming.SetThreadPoolName(mName);
   149   nsCOMPtr<nsIThread> current;
   150   nsThreadManager::get()->GetCurrentThread(getter_AddRefs(current));
   152   bool shutdownThreadOnExit = false;
   153   bool exitThread = false;
   154   bool wasIdle = false;
   155   PRIntervalTime idleSince;
   157   nsCOMPtr<nsIThreadPoolListener> listener;
   158   {
   159     ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
   160     listener = mListener;
   161   }
   163   if (listener) {
   164     listener->OnThreadCreated();
   165   }
   167   do {
   168     nsCOMPtr<nsIRunnable> event;
   169     {
   170       ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
   171       if (!mEvents.GetPendingEvent(getter_AddRefs(event))) {
   172         PRIntervalTime now     = PR_IntervalNow();
   173         PRIntervalTime timeout = PR_MillisecondsToInterval(mIdleThreadTimeout);
   175         // If we are shutting down, then don't keep any idle threads
   176         if (mShutdown) {
   177           exitThread = true;
   178         } else {
   179           if (wasIdle) {
   180             // if too many idle threads or idle for too long, then bail.
   181             if (mIdleCount > mIdleThreadLimit || (now - idleSince) >= timeout)
   182               exitThread = true;
   183           } else {
   184             // if would be too many idle threads...
   185             if (mIdleCount == mIdleThreadLimit) {
   186               exitThread = true;
   187             } else {
   188               ++mIdleCount;
   189               idleSince = now;
   190               wasIdle = true;
   191             }
   192           }
   193         }
   195         if (exitThread) {
   196           if (wasIdle)
   197             --mIdleCount;
   198           shutdownThreadOnExit = mThreads.RemoveObject(current);
   199         } else {
   200           PRIntervalTime delta = timeout - (now - idleSince);
   201           LOG(("THRD-P(%p) waiting [%d]\n", this, delta));
   202           mon.Wait(delta);
   203         }
   204       } else if (wasIdle) {
   205         wasIdle = false;
   206         --mIdleCount;
   207       }
   208     }
   209     if (event) {
   210       LOG(("THRD-P(%p) running [%p]\n", this, event.get()));
   211       event->Run();
   212     }
   213   } while (!exitThread);
   215   if (listener) {
   216     listener->OnThreadShuttingDown();
   217   }
   219   if (shutdownThreadOnExit) {
   220     ShutdownThread(current);
   221   }
   223   LOG(("THRD-P(%p) leave\n", this));
   224   return NS_OK;
   225 }
   227 NS_IMETHODIMP
   228 nsThreadPool::Dispatch(nsIRunnable *event, uint32_t flags)
   229 {
   230   LOG(("THRD-P(%p) dispatch [%p %x]\n", this, event, flags));
   232   if (NS_WARN_IF(mShutdown))
   233     return NS_ERROR_NOT_AVAILABLE;
   235   if (flags & DISPATCH_SYNC) {
   236     nsCOMPtr<nsIThread> thread;
   237     nsThreadManager::get()->GetCurrentThread(getter_AddRefs(thread));
   238     if (NS_WARN_IF(!thread))
   239       return NS_ERROR_NOT_AVAILABLE;
   241     nsRefPtr<nsThreadSyncDispatch> wrapper =
   242         new nsThreadSyncDispatch(thread, event);
   243     PutEvent(wrapper);
   245     while (wrapper->IsPending())
   246       NS_ProcessNextEvent(thread);
   247   } else {
   248     NS_ASSERTION(flags == NS_DISPATCH_NORMAL, "unexpected dispatch flags");
   249     PutEvent(event);
   250   }
   251   return NS_OK;
   252 }
   254 NS_IMETHODIMP
   255 nsThreadPool::IsOnCurrentThread(bool *result)
   256 {
   257   ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
   258   nsIThread* thread = NS_GetCurrentThread();
   259   for (uint32_t i = 0; i < static_cast<uint32_t>(mThreads.Count()); ++i) {
   260     if (mThreads[i] == thread) {
   261       *result = true;
   262       return NS_OK;
   263     }
   264   }
   265   *result = false;
   266   return NS_OK;
   267 }
   269 NS_IMETHODIMP
   270 nsThreadPool::Shutdown()
   271 {
   272   nsCOMArray<nsIThread> threads;
   273   nsCOMPtr<nsIThreadPoolListener> listener;
   274   {
   275     ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
   276     mShutdown = true;
   277     mon.NotifyAll();
   279     threads.AppendObjects(mThreads);
   280     mThreads.Clear();
   282     // Swap in a null listener so that we release the listener at the end of
   283     // this method. The listener will be kept alive as long as the other threads
   284     // that were created when it was set.
   285     mListener.swap(listener);
   286   }
   288   // It's important that we shutdown the threads while outside the event queue
   289   // monitor.  Otherwise, we could end up dead-locking.
   291   for (int32_t i = 0; i < threads.Count(); ++i)
   292     threads[i]->Shutdown();
   294   return NS_OK;
   295 }
   297 NS_IMETHODIMP
   298 nsThreadPool::GetThreadLimit(uint32_t *value)
   299 {
   300   *value = mThreadLimit;
   301   return NS_OK;
   302 }
   304 NS_IMETHODIMP
   305 nsThreadPool::SetThreadLimit(uint32_t value)
   306 {
   307   ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
   308   mThreadLimit = value;
   309   if (mIdleThreadLimit > mThreadLimit)
   310     mIdleThreadLimit = mThreadLimit;
   312   if (static_cast<uint32_t>(mThreads.Count()) > mThreadLimit) {
   313     mon.NotifyAll();  // wake up threads so they observe this change
   314   }
   315   return NS_OK;
   316 }
   318 NS_IMETHODIMP
   319 nsThreadPool::GetIdleThreadLimit(uint32_t *value)
   320 {
   321   *value = mIdleThreadLimit;
   322   return NS_OK;
   323 }
   325 NS_IMETHODIMP
   326 nsThreadPool::SetIdleThreadLimit(uint32_t value)
   327 {
   328   ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
   329   mIdleThreadLimit = value;
   330   if (mIdleThreadLimit > mThreadLimit)
   331     mIdleThreadLimit = mThreadLimit;
   333   // Do we need to kill some idle threads?
   334   if (mIdleCount > mIdleThreadLimit) {
   335     mon.NotifyAll();  // wake up threads so they observe this change
   336   }
   337   return NS_OK;
   338 }
   340 NS_IMETHODIMP
   341 nsThreadPool::GetIdleThreadTimeout(uint32_t *value)
   342 {
   343   *value = mIdleThreadTimeout;
   344   return NS_OK;
   345 }
   347 NS_IMETHODIMP
   348 nsThreadPool::SetIdleThreadTimeout(uint32_t value)
   349 {
   350   ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
   351   uint32_t oldTimeout = mIdleThreadTimeout;
   352   mIdleThreadTimeout = value;
   354   // Do we need to notify any idle threads that their sleep time has shortened?
   355   if (mIdleThreadTimeout < oldTimeout && mIdleCount > 0) {
   356     mon.NotifyAll();  // wake up threads so they observe this change
   357   }
   358   return NS_OK;
   359 }
   361 NS_IMETHODIMP
   362 nsThreadPool::GetThreadStackSize(uint32_t* value)
   363 {
   364   ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
   365   *value = mStackSize;
   366   return NS_OK;
   367 }
   369 NS_IMETHODIMP
   370 nsThreadPool::SetThreadStackSize(uint32_t value)
   371 {
   372   ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
   373   mStackSize = value;
   374   return NS_OK;
   375 }
   377 NS_IMETHODIMP
   378 nsThreadPool::GetListener(nsIThreadPoolListener** aListener)
   379 {
   380   ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
   381   NS_IF_ADDREF(*aListener = mListener);
   382   return NS_OK;
   383 }
   385 NS_IMETHODIMP
   386 nsThreadPool::SetListener(nsIThreadPoolListener* aListener)
   387 {
   388   nsCOMPtr<nsIThreadPoolListener> swappedListener(aListener);
   389   {
   390     ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
   391     mListener.swap(swappedListener);
   392   }
   393   return NS_OK;
   394 }
   396 NS_IMETHODIMP
   397 nsThreadPool::SetName(const nsACString& aName)
   398 {
   399   {
   400     ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
   401     if (mThreads.Count())
   402       return NS_ERROR_NOT_AVAILABLE;
   403   }
   405   mName = aName;
   406   return NS_OK;
   407 }

mercurial