xpcom/threads/LazyIdleThread.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: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ts=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 "LazyIdleThread.h"
     9 #include "nsIObserverService.h"
    11 #include "GeckoProfiler.h"
    12 #include "nsComponentManagerUtils.h"
    13 #include "nsServiceManagerUtils.h"
    14 #include "nsThreadUtils.h"
    15 #include "mozilla/Services.h"
    17 #ifdef DEBUG
    18 #define ASSERT_OWNING_THREAD()                                                 \
    19   PR_BEGIN_MACRO                                                               \
    20     nsIThread* currentThread = NS_GetCurrentThread();                          \
    21     if (currentThread) {                                                       \
    22       nsCOMPtr<nsISupports> current(do_QueryInterface(currentThread));         \
    23       nsCOMPtr<nsISupports> test(do_QueryInterface(mOwningThread));            \
    24       MOZ_ASSERT(current == test, "Wrong thread!");                            \
    25     }                                                                          \
    26   PR_END_MACRO
    27 #else
    28 #define ASSERT_OWNING_THREAD() /* nothing */
    29 #endif
    31 namespace mozilla {
    33 LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS,
    34                                const nsCSubstring& aName,
    35                                ShutdownMethod aShutdownMethod,
    36                                nsIObserver* aIdleObserver)
    37 : mMutex("LazyIdleThread::mMutex"),
    38   mOwningThread(NS_GetCurrentThread()),
    39   mIdleObserver(aIdleObserver),
    40   mQueuedRunnables(nullptr),
    41   mIdleTimeoutMS(aIdleTimeoutMS),
    42   mPendingEventCount(0),
    43   mIdleNotificationCount(0),
    44   mShutdownMethod(aShutdownMethod),
    45   mShutdown(false),
    46   mThreadIsShuttingDown(false),
    47   mIdleTimeoutEnabled(true),
    48   mName(aName)
    49 {
    50   MOZ_ASSERT(mOwningThread, "Need owning thread!");
    51 }
    53 LazyIdleThread::~LazyIdleThread()
    54 {
    55   ASSERT_OWNING_THREAD();
    57   Shutdown();
    58 }
    60 void
    61 LazyIdleThread::SetWeakIdleObserver(nsIObserver* aObserver)
    62 {
    63   ASSERT_OWNING_THREAD();
    65   if (mShutdown) {
    66     NS_WARN_IF_FALSE(!aObserver,
    67                      "Setting an observer after Shutdown was called!");
    68     return;
    69   }
    71   mIdleObserver = aObserver;
    72 }
    74 void
    75 LazyIdleThread::DisableIdleTimeout()
    76 {
    77   ASSERT_OWNING_THREAD();
    78   if (!mIdleTimeoutEnabled) {
    79     return;
    80   }
    81   mIdleTimeoutEnabled = false;
    83   if (mIdleTimer && NS_FAILED(mIdleTimer->Cancel())) {
    84     NS_WARNING("Failed to cancel timer!");
    85   }
    87   MutexAutoLock lock(mMutex);
    89   // Pretend we have a pending event to keep the idle timer from firing.
    90   MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!");
    91   mPendingEventCount++;
    92 }
    94 void
    95 LazyIdleThread::EnableIdleTimeout()
    96 {
    97   ASSERT_OWNING_THREAD();
    98   if (mIdleTimeoutEnabled) {
    99     return;
   100   }
   101   mIdleTimeoutEnabled = true;
   103   {
   104     MutexAutoLock lock(mMutex);
   106     MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!");
   107     --mPendingEventCount;
   108   }
   110   if (mThread) {
   111     nsCOMPtr<nsIRunnable> runnable(new nsRunnable());
   112     if (NS_FAILED(Dispatch(runnable, NS_DISPATCH_NORMAL))) {
   113       NS_WARNING("Failed to dispatch!");
   114     }
   115   }
   116 }
   118 void
   119 LazyIdleThread::PreDispatch()
   120 {
   121   MutexAutoLock lock(mMutex);
   123   MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!");
   124   mPendingEventCount++;
   125 }
   127 nsresult
   128 LazyIdleThread::EnsureThread()
   129 {
   130   ASSERT_OWNING_THREAD();
   132   if (mShutdown) {
   133     return NS_ERROR_UNEXPECTED;
   134   }
   136   if (mThread) {
   137     return NS_OK;
   138   }
   140   MOZ_ASSERT(!mPendingEventCount, "Shouldn't have events yet!");
   141   MOZ_ASSERT(!mIdleNotificationCount, "Shouldn't have idle events yet!");
   142   MOZ_ASSERT(!mIdleTimer, "Should have killed this long ago!");
   143   MOZ_ASSERT(!mThreadIsShuttingDown, "Should have cleared that!");
   145   nsresult rv;
   147   if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) {
   148     nsCOMPtr<nsIObserverService> obs =
   149       do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
   150     if (NS_WARN_IF(NS_FAILED(rv)))
   151       return rv;
   153     rv = obs->AddObserver(this, "xpcom-shutdown-threads", false);
   154     if (NS_WARN_IF(NS_FAILED(rv)))
   155       return rv;
   156   }
   158   mIdleTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
   159   if (NS_WARN_IF(!mIdleTimer))
   160     return NS_ERROR_UNEXPECTED;
   162   nsCOMPtr<nsIRunnable> runnable =
   163     NS_NewRunnableMethod(this, &LazyIdleThread::InitThread);
   164   if (NS_WARN_IF(!runnable))
   165     return NS_ERROR_UNEXPECTED;
   167   rv = NS_NewThread(getter_AddRefs(mThread), runnable);
   168   if (NS_WARN_IF(NS_FAILED(rv)))
   169     return rv;
   171   return NS_OK;
   172 }
   174 void
   175 LazyIdleThread::InitThread()
   176 {
   177   char aLocal;
   178   profiler_register_thread(mName.get(), &aLocal);
   180   PR_SetCurrentThreadName(mName.get());
   182   // Happens on mThread but mThread may not be set yet...
   184   nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread()));
   185   MOZ_ASSERT(thread, "This should always succeed!");
   187   if (NS_FAILED(thread->SetObserver(this))) {
   188     NS_WARNING("Failed to set thread observer!");
   189   }
   190 }
   192 void
   193 LazyIdleThread::CleanupThread()
   194 {
   195   nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread()));
   196   MOZ_ASSERT(thread, "This should always succeed!");
   198   if (NS_FAILED(thread->SetObserver(nullptr))) {
   199     NS_WARNING("Failed to set thread observer!");
   200   }
   202   {
   203     MutexAutoLock lock(mMutex);
   205     MOZ_ASSERT(!mThreadIsShuttingDown, "Shouldn't be true ever!");
   206     mThreadIsShuttingDown = true;
   207   }
   209   profiler_unregister_thread();
   210 }
   212 void
   213 LazyIdleThread::ScheduleTimer()
   214 {
   215   ASSERT_OWNING_THREAD();
   217   bool shouldSchedule;
   218   {
   219     MutexAutoLock lock(mMutex);
   221     MOZ_ASSERT(mIdleNotificationCount, "Should have at least one!");
   222     --mIdleNotificationCount;
   224     shouldSchedule = !mIdleNotificationCount && !mPendingEventCount;
   225   }
   227   if (NS_FAILED(mIdleTimer->Cancel())) {
   228     NS_WARNING("Failed to cancel timer!");
   229   }
   231   if (shouldSchedule &&
   232       NS_FAILED(mIdleTimer->InitWithCallback(this, mIdleTimeoutMS,
   233                                              nsITimer::TYPE_ONE_SHOT))) {
   234     NS_WARNING("Failed to schedule timer!");
   235   }
   236 }
   238 nsresult
   239 LazyIdleThread::ShutdownThread()
   240 {
   241   ASSERT_OWNING_THREAD();
   243   // Before calling Shutdown() on the real thread we need to put a queue in
   244   // place in case a runnable is posted to the thread while it's in the
   245   // process of shutting down. This will be our queue.
   246   nsAutoTArray<nsCOMPtr<nsIRunnable>, 10> queuedRunnables;
   248   nsresult rv;
   250   if (mThread) {
   251     if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) {
   252       nsCOMPtr<nsIObserverService> obs =
   253         mozilla::services::GetObserverService();
   254       NS_WARN_IF_FALSE(obs, "Failed to get observer service!");
   256       if (obs &&
   257           NS_FAILED(obs->RemoveObserver(this, "xpcom-shutdown-threads"))) {
   258         NS_WARNING("Failed to remove observer!");
   259       }
   260     }
   262     if (mIdleObserver) {
   263       mIdleObserver->Observe(static_cast<nsIThread*>(this), IDLE_THREAD_TOPIC,
   264                              nullptr);
   265     }
   267 #ifdef DEBUG
   268     {
   269       MutexAutoLock lock(mMutex);
   270       MOZ_ASSERT(!mThreadIsShuttingDown, "Huh?!");
   271     }
   272 #endif
   274     nsCOMPtr<nsIRunnable> runnable =
   275       NS_NewRunnableMethod(this, &LazyIdleThread::CleanupThread);
   276     if (NS_WARN_IF(!runnable))
   277       return NS_ERROR_UNEXPECTED;
   279     PreDispatch();
   281     rv = mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
   282     if (NS_WARN_IF(NS_FAILED(rv)))
   283       return rv;
   285     // Put the temporary queue in place before calling Shutdown().
   286     mQueuedRunnables = &queuedRunnables;
   288     if (NS_FAILED(mThread->Shutdown())) {
   289       NS_ERROR("Failed to shutdown the thread!");
   290     }
   292     // Now unset the queue.
   293     mQueuedRunnables = nullptr;
   295     mThread = nullptr;
   297     {
   298       MutexAutoLock lock(mMutex);
   300       MOZ_ASSERT(!mPendingEventCount, "Huh?!");
   301       MOZ_ASSERT(!mIdleNotificationCount, "Huh?!");
   302       MOZ_ASSERT(mThreadIsShuttingDown, "Huh?!");
   303       mThreadIsShuttingDown = false;
   304     }
   305   }
   307   if (mIdleTimer) {
   308     rv = mIdleTimer->Cancel();
   309     if (NS_WARN_IF(NS_FAILED(rv)))
   310       return rv;
   312     mIdleTimer = nullptr;
   313   }
   315   // If our temporary queue has any runnables then we need to dispatch them.
   316   if (queuedRunnables.Length()) {
   317     // If the thread manager has gone away then these runnables will never run.
   318     if (mShutdown) {
   319       NS_ERROR("Runnables dispatched to LazyIdleThread will never run!");
   320       return NS_OK;
   321     }
   323     // Re-dispatch the queued runnables.
   324     for (uint32_t index = 0; index < queuedRunnables.Length(); index++) {
   325       nsCOMPtr<nsIRunnable> runnable;
   326       runnable.swap(queuedRunnables[index]);
   327       MOZ_ASSERT(runnable, "Null runnable?!");
   329       if (NS_FAILED(Dispatch(runnable, NS_DISPATCH_NORMAL))) {
   330         NS_ERROR("Failed to re-dispatch queued runnable!");
   331       }
   332     }
   333   }
   335   return NS_OK;
   336 }
   338 void
   339 LazyIdleThread::SelfDestruct()
   340 {
   341   MOZ_ASSERT(mRefCnt == 1, "Bad refcount!");
   342   delete this;
   343 }
   345 NS_IMPL_ADDREF(LazyIdleThread)
   347 NS_IMETHODIMP_(MozExternalRefCountType)
   348 LazyIdleThread::Release()
   349 {
   350   nsrefcnt count = --mRefCnt;
   351   NS_LOG_RELEASE(this, count, "LazyIdleThread");
   353   if (!count) {
   354     // Stabilize refcount.
   355     mRefCnt = 1;
   357     nsCOMPtr<nsIRunnable> runnable =
   358       NS_NewNonOwningRunnableMethod(this, &LazyIdleThread::SelfDestruct);
   359     NS_WARN_IF_FALSE(runnable, "Couldn't make runnable!");
   361     if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
   362       MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
   363       // The only way this could fail is if we're in shutdown, and in that case
   364       // threads should have been joined already. Deleting here isn't dangerous
   365       // anymore because we won't spin the event loop waiting to join the
   366       // thread.
   367       SelfDestruct();
   368     }
   369   }
   371   return count;
   372 }
   374 NS_IMPL_QUERY_INTERFACE(LazyIdleThread, nsIThread,
   375                         nsIEventTarget,
   376                         nsITimerCallback,
   377                         nsIThreadObserver,
   378                         nsIObserver)
   380 NS_IMETHODIMP
   381 LazyIdleThread::Dispatch(nsIRunnable* aEvent,
   382                          uint32_t aFlags)
   383 {
   384   ASSERT_OWNING_THREAD();
   386   // LazyIdleThread can't always support synchronous dispatch currently.
   387   if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL))
   388     return NS_ERROR_NOT_IMPLEMENTED;
   390   // If our thread is shutting down then we can't actually dispatch right now.
   391   // Queue this runnable for later.
   392   if (UseRunnableQueue()) {
   393     mQueuedRunnables->AppendElement(aEvent);
   394     return NS_OK;
   395   }
   397   nsresult rv = EnsureThread();
   398   if (NS_WARN_IF(NS_FAILED(rv)))
   399     return rv;
   401   PreDispatch();
   403   return mThread->Dispatch(aEvent, aFlags);
   404 }
   406 NS_IMETHODIMP
   407 LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread)
   408 {
   409   if (mThread) {
   410     return mThread->IsOnCurrentThread(aIsOnCurrentThread);
   411   }
   413   *aIsOnCurrentThread = false;
   414   return NS_OK;
   415 }
   417 NS_IMETHODIMP
   418 LazyIdleThread::GetPRThread(PRThread** aPRThread)
   419 {
   420   if (mThread) {
   421     return mThread->GetPRThread(aPRThread);
   422   }
   424   *aPRThread = nullptr;
   425   return NS_ERROR_NOT_AVAILABLE;
   426 }
   428 NS_IMETHODIMP
   429 LazyIdleThread::Shutdown()
   430 {
   431   ASSERT_OWNING_THREAD();
   433   mShutdown = true;
   435   nsresult rv = ShutdownThread();
   436   MOZ_ASSERT(!mThread, "Should have destroyed this by now!");
   438   mIdleObserver = nullptr;
   440   if (NS_WARN_IF(NS_FAILED(rv)))
   441     return rv;
   443   return NS_OK;
   444 }
   446 NS_IMETHODIMP
   447 LazyIdleThread::HasPendingEvents(bool* aHasPendingEvents)
   448 {
   449   // This is only supposed to be called from the thread itself so it's not
   450   // implemented here.
   451   NS_NOTREACHED("Shouldn't ever call this!");
   452   return NS_ERROR_UNEXPECTED;
   453 }
   455 NS_IMETHODIMP
   456 LazyIdleThread::ProcessNextEvent(bool aMayWait,
   457                                  bool* aEventWasProcessed)
   458 {
   459   // This is only supposed to be called from the thread itself so it's not
   460   // implemented here.
   461   NS_NOTREACHED("Shouldn't ever call this!");
   462   return NS_ERROR_UNEXPECTED;
   463 }
   465 NS_IMETHODIMP
   466 LazyIdleThread::Notify(nsITimer* aTimer)
   467 {
   468   ASSERT_OWNING_THREAD();
   470   {
   471     MutexAutoLock lock(mMutex);
   473     if (mPendingEventCount || mIdleNotificationCount) {
   474       // Another event was scheduled since this timer was set. Don't do
   475       // anything and wait for the timer to fire again.
   476       return NS_OK;
   477     }
   478   }
   480   nsresult rv = ShutdownThread();
   481   if (NS_WARN_IF(NS_FAILED(rv)))
   482     return rv;
   484   return NS_OK;
   485 }
   487 NS_IMETHODIMP
   488 LazyIdleThread::OnDispatchedEvent(nsIThreadInternal* /*aThread */)
   489 {
   490   MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread, "Wrong thread!");
   491   return NS_OK;
   492 }
   494 NS_IMETHODIMP
   495 LazyIdleThread::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
   496                                    bool /* aMayWait */,
   497                                    uint32_t /* aRecursionDepth */)
   498 {
   499   return NS_OK;
   500 }
   502 NS_IMETHODIMP
   503 LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
   504                                       uint32_t /* aRecursionDepth */,
   505                                       bool aEventWasProcessed)
   506 {
   507   bool shouldNotifyIdle;
   508   {
   509     MutexAutoLock lock(mMutex);
   511     if (aEventWasProcessed) {
   512       MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!");
   513       --mPendingEventCount;
   514     }
   516     if (mThreadIsShuttingDown) {
   517       // We're shutting down, no need to fire any timer.
   518       return NS_OK;
   519     }
   521     shouldNotifyIdle = !mPendingEventCount;
   522     if (shouldNotifyIdle) {
   523       MOZ_ASSERT(mIdleNotificationCount < UINT32_MAX, "Way too many!");
   524       mIdleNotificationCount++;
   525     }
   526   }
   528   if (shouldNotifyIdle) {
   529     nsCOMPtr<nsIRunnable> runnable =
   530       NS_NewRunnableMethod(this, &LazyIdleThread::ScheduleTimer);
   531     if (NS_WARN_IF(!runnable))
   532       return NS_ERROR_UNEXPECTED;
   534     nsresult rv = mOwningThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
   535     if (NS_WARN_IF(NS_FAILED(rv)))
   536       return rv;
   537   }
   539   return NS_OK;
   540 }
   542 NS_IMETHODIMP
   543 LazyIdleThread::Observe(nsISupports* /* aSubject */,
   544                         const char*  aTopic,
   545                         const char16_t* /* aData */)
   546 {
   547   MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
   548   MOZ_ASSERT(mShutdownMethod == AutomaticShutdown,
   549              "Should not receive notifications if not AutomaticShutdown!");
   550   MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!");
   552   Shutdown();
   553   return NS_OK;
   554 }
   556 } // namespace mozilla

mercurial