xpcom/threads/TimerThread.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  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "nsTimerImpl.h"
     7 #include "TimerThread.h"
     9 #include "nsThreadUtils.h"
    10 #include "pratom.h"
    12 #include "nsIObserverService.h"
    13 #include "nsIServiceManager.h"
    14 #include "mozilla/Services.h"
    15 #include "mozilla/ChaosMode.h"
    16 #include "mozilla/ArrayUtils.h"
    18 #include <math.h>
    20 using namespace mozilla;
    22 NS_IMPL_ISUPPORTS(TimerThread, nsIRunnable, nsIObserver)
    24 TimerThread::TimerThread() :
    25   mInitInProgress(false),
    26   mInitialized(false),
    27   mMonitor("TimerThread.mMonitor"),
    28   mShutdown(false),
    29   mWaiting(false),
    30   mNotified(false),
    31   mSleeping(false)
    32 {
    33 }
    35 TimerThread::~TimerThread()
    36 {
    37   mThread = nullptr;
    39   NS_ASSERTION(mTimers.IsEmpty(), "Timers remain in TimerThread::~TimerThread");
    40 }
    42 nsresult
    43 TimerThread::InitLocks()
    44 {
    45   return NS_OK;
    46 }
    48 namespace {
    50 class TimerObserverRunnable : public nsRunnable
    51 {
    52 public:
    53   TimerObserverRunnable(nsIObserver* observer)
    54     : mObserver(observer)
    55   { }
    57   NS_DECL_NSIRUNNABLE
    59 private:
    60   nsCOMPtr<nsIObserver> mObserver;
    61 };
    63 NS_IMETHODIMP
    64 TimerObserverRunnable::Run()
    65 {
    66   nsCOMPtr<nsIObserverService> observerService =
    67     mozilla::services::GetObserverService();
    68   if (observerService) {
    69     observerService->AddObserver(mObserver, "sleep_notification", false);
    70     observerService->AddObserver(mObserver, "wake_notification", false);
    71     observerService->AddObserver(mObserver, "suspend_process_notification", false);
    72     observerService->AddObserver(mObserver, "resume_process_notification", false);
    73   }
    74   return NS_OK;
    75 }
    77 } // anonymous namespace
    79 nsresult TimerThread::Init()
    80 {
    81   PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("TimerThread::Init [%d]\n", mInitialized));
    83   if (mInitialized) {
    84     if (!mThread)
    85       return NS_ERROR_FAILURE;
    87     return NS_OK;
    88   }
    90   if (mInitInProgress.exchange(true) == false) {
    91     // We hold on to mThread to keep the thread alive.
    92     nsresult rv = NS_NewThread(getter_AddRefs(mThread), this);
    93     if (NS_FAILED(rv)) {
    94       mThread = nullptr;
    95     }
    96     else {
    97       nsRefPtr<TimerObserverRunnable> r = new TimerObserverRunnable(this);
    98       if (NS_IsMainThread()) {
    99         r->Run();
   100       }
   101       else {
   102         NS_DispatchToMainThread(r);
   103       }
   104     }
   106     {
   107       MonitorAutoLock lock(mMonitor);
   108       mInitialized = true;
   109       mMonitor.NotifyAll();
   110     }
   111   }
   112   else {
   113     MonitorAutoLock lock(mMonitor);
   114     while (!mInitialized) {
   115       mMonitor.Wait();
   116     }
   117   }
   119   if (!mThread)
   120     return NS_ERROR_FAILURE;
   122   return NS_OK;
   123 }
   125 nsresult TimerThread::Shutdown()
   126 {
   127   PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("TimerThread::Shutdown begin\n"));
   129   if (!mThread)
   130     return NS_ERROR_NOT_INITIALIZED;
   132   nsTArray<nsTimerImpl*> timers;
   133   {   // lock scope
   134     MonitorAutoLock lock(mMonitor);
   136     mShutdown = true;
   138     // notify the cond var so that Run() can return
   139     if (mWaiting) {
   140       mNotified = true;
   141       mMonitor.Notify();
   142     }
   144     // Need to copy content of mTimers array to a local array
   145     // because call to timers' ReleaseCallback() (and release its self)
   146     // must not be done under the lock. Destructor of a callback
   147     // might potentially call some code reentering the same lock
   148     // that leads to unexpected behavior or deadlock.
   149     // See bug 422472.
   150     timers.AppendElements(mTimers);
   151     mTimers.Clear();
   152   }
   154   uint32_t timersCount = timers.Length();
   155   for (uint32_t i = 0; i < timersCount; i++) {
   156     nsTimerImpl *timer = timers[i];
   157     timer->ReleaseCallback();
   158     ReleaseTimerInternal(timer);
   159   }
   161   mThread->Shutdown();    // wait for the thread to die
   163   PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("TimerThread::Shutdown end\n"));
   164   return NS_OK;
   165 }
   167 #ifdef MOZ_NUWA_PROCESS
   168 #include "ipc/Nuwa.h"
   169 #endif
   171 /* void Run(); */
   172 NS_IMETHODIMP TimerThread::Run()
   173 {
   174   PR_SetCurrentThreadName("Timer");
   176 #ifdef MOZ_NUWA_PROCESS
   177   if (IsNuwaProcess()) {
   178     NS_ASSERTION(NuwaMarkCurrentThread != nullptr,
   179                  "NuwaMarkCurrentThread is undefined!");
   180     NuwaMarkCurrentThread(nullptr, nullptr);
   181   }
   182 #endif
   184   MonitorAutoLock lock(mMonitor);
   186   // We need to know how many microseconds give a positive PRIntervalTime. This
   187   // is platform-dependent, we calculate it at runtime now.
   188   // First we find a value such that PR_MicrosecondsToInterval(high) = 1
   189   int32_t low = 0, high = 1;
   190   while (PR_MicrosecondsToInterval(high) == 0)
   191     high <<= 1;
   192   // We now have
   193   //    PR_MicrosecondsToInterval(low)  = 0
   194   //    PR_MicrosecondsToInterval(high) = 1
   195   // and we can proceed to find the critical value using binary search
   196   while (high-low > 1) {
   197     int32_t mid = (high+low) >> 1;
   198     if (PR_MicrosecondsToInterval(mid) == 0)
   199       low = mid;
   200     else
   201       high = mid;
   202   }
   204   // Half of the amount of microseconds needed to get positive PRIntervalTime.
   205   // We use this to decide how to round our wait times later
   206   int32_t halfMicrosecondsIntervalResolution = high >> 1;
   207   bool forceRunNextTimer = false;
   209   while (!mShutdown) {
   210     // Have to use PRIntervalTime here, since PR_WaitCondVar takes it
   211     PRIntervalTime waitFor;
   212     bool forceRunThisTimer = forceRunNextTimer;
   213     forceRunNextTimer = false;
   215     if (mSleeping) {
   216       // Sleep for 0.1 seconds while not firing timers.
   217       uint32_t milliseconds = 100;
   218       if (ChaosMode::isActive()) {
   219         milliseconds = ChaosMode::randomUint32LessThan(200);
   220       }
   221       waitFor = PR_MillisecondsToInterval(milliseconds);
   222     } else {
   223       waitFor = PR_INTERVAL_NO_TIMEOUT;
   224       TimeStamp now = TimeStamp::Now();
   225       nsTimerImpl *timer = nullptr;
   227       if (!mTimers.IsEmpty()) {
   228         timer = mTimers[0];
   230         if (now >= timer->mTimeout || forceRunThisTimer) {
   231     next:
   232           // NB: AddRef before the Release under RemoveTimerInternal to avoid
   233           // mRefCnt passing through zero, in case all other refs than the one
   234           // from mTimers have gone away (the last non-mTimers[i]-ref's Release
   235           // must be racing with us, blocked in gThread->RemoveTimer waiting
   236           // for TimerThread::mMonitor, under nsTimerImpl::Release.
   238           nsRefPtr<nsTimerImpl> timerRef(timer);
   239           RemoveTimerInternal(timer);
   240           timer = nullptr;
   242           {
   243             // We release mMonitor around the Fire call to avoid deadlock.
   244             MonitorAutoUnlock unlock(mMonitor);
   246 #ifdef DEBUG_TIMERS
   247             if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) {
   248               PR_LOG(GetTimerLog(), PR_LOG_DEBUG,
   249                      ("Timer thread woke up %fms from when it was supposed to\n",
   250                       fabs((now - timerRef->mTimeout).ToMilliseconds())));
   251             }
   252 #endif
   254             // We are going to let the call to PostTimerEvent here handle the
   255             // release of the timer so that we don't end up releasing the timer
   256             // on the TimerThread instead of on the thread it targets.
   257             timerRef = nsTimerImpl::PostTimerEvent(timerRef.forget());
   259             if (timerRef) {
   260               // We got our reference back due to an error.
   261               // Unhook the nsRefPtr, and release manually so we can get the
   262               // refcount.
   263               nsrefcnt rc = timerRef.forget().take()->Release();
   264               (void)rc;
   266               // The nsITimer interface requires that its users keep a reference
   267               // to the timers they use while those timers are initialized but
   268               // have not yet fired.  If this ever happens, it is a bug in the
   269               // code that created and used the timer.
   270               //
   271               // Further, note that this should never happen even with a
   272               // misbehaving user, because nsTimerImpl::Release checks for a
   273               // refcount of 1 with an armed timer (a timer whose only reference
   274               // is from the timer thread) and when it hits this will remove the
   275               // timer from the timer thread and thus destroy the last reference,
   276               // preventing this situation from occurring.
   277               MOZ_ASSERT(rc != 0, "destroyed timer off its target thread!");
   278             }
   279           }
   281           if (mShutdown)
   282             break;
   284           // Update now, as PostTimerEvent plus the locking may have taken a
   285           // tick or two, and we may goto next below.
   286           now = TimeStamp::Now();
   287         }
   288       }
   290       if (!mTimers.IsEmpty()) {
   291         timer = mTimers[0];
   293         TimeStamp timeout = timer->mTimeout;
   295         // Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer
   296         // is due now or overdue.
   297         //
   298         // Note that we can only sleep for integer values of a certain
   299         // resolution. We use halfMicrosecondsIntervalResolution, calculated
   300         // before, to do the optimal rounding (i.e., of how to decide what
   301         // interval is so small we should not wait at all).
   302         double microseconds = (timeout - now).ToMilliseconds()*1000;
   304         if (ChaosMode::isActive()) {
   305           // The mean value of sFractions must be 1 to ensure that
   306           // the average of a long sequence of timeouts converges to the
   307           // actual sum of their times.
   308           static const float sFractions[] = {
   309             0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 1.75f, 2.75f
   310           };
   311           microseconds *= sFractions[ChaosMode::randomUint32LessThan(ArrayLength(sFractions))];
   312           forceRunNextTimer = true;
   313         }
   315         if (microseconds < halfMicrosecondsIntervalResolution) {
   316           forceRunNextTimer = false;
   317           goto next; // round down; execute event now
   318         }
   319         waitFor = PR_MicrosecondsToInterval(static_cast<uint32_t>(microseconds)); // Floor is accurate enough.
   320         if (waitFor == 0)
   321           waitFor = 1; // round up, wait the minimum time we can wait
   322       }
   324 #ifdef DEBUG_TIMERS
   325       if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) {
   326         if (waitFor == PR_INTERVAL_NO_TIMEOUT)
   327           PR_LOG(GetTimerLog(), PR_LOG_DEBUG,
   328                  ("waiting for PR_INTERVAL_NO_TIMEOUT\n"));
   329         else
   330           PR_LOG(GetTimerLog(), PR_LOG_DEBUG,
   331                  ("waiting for %u\n", PR_IntervalToMilliseconds(waitFor)));
   332       }
   333 #endif
   334     }
   336     mWaiting = true;
   337     mNotified = false;
   338     mMonitor.Wait(waitFor);
   339     if (mNotified) {
   340       forceRunNextTimer = false;
   341     }
   342     mWaiting = false;
   343   }
   345   return NS_OK;
   346 }
   348 nsresult TimerThread::AddTimer(nsTimerImpl *aTimer)
   349 {
   350   MonitorAutoLock lock(mMonitor);
   352   // Add the timer to our list.
   353   int32_t i = AddTimerInternal(aTimer);
   354   if (i < 0)
   355     return NS_ERROR_OUT_OF_MEMORY;
   357   // Awaken the timer thread.
   358   if (mWaiting && i == 0) {
   359     mNotified = true;
   360     mMonitor.Notify();
   361   }
   363   return NS_OK;
   364 }
   366 nsresult TimerThread::TimerDelayChanged(nsTimerImpl *aTimer)
   367 {
   368   MonitorAutoLock lock(mMonitor);
   370   // Our caller has a strong ref to aTimer, so it can't go away here under
   371   // ReleaseTimerInternal.
   372   RemoveTimerInternal(aTimer);
   374   int32_t i = AddTimerInternal(aTimer);
   375   if (i < 0)
   376     return NS_ERROR_OUT_OF_MEMORY;
   378   // Awaken the timer thread.
   379   if (mWaiting && i == 0) {
   380     mNotified = true;
   381     mMonitor.Notify();
   382   }
   384   return NS_OK;
   385 }
   387 nsresult TimerThread::RemoveTimer(nsTimerImpl *aTimer)
   388 {
   389   MonitorAutoLock lock(mMonitor);
   391   // Remove the timer from our array.  Tell callers that aTimer was not found
   392   // by returning NS_ERROR_NOT_AVAILABLE.  Unlike the TimerDelayChanged case
   393   // immediately above, our caller may be passing a (now-)weak ref in via the
   394   // aTimer param, specifically when nsTimerImpl::Release loses a race with
   395   // TimerThread::Run, must wait for the mMonitor auto-lock here, and during the
   396   // wait Run drops the only remaining ref to aTimer via RemoveTimerInternal.
   398   if (!RemoveTimerInternal(aTimer))
   399     return NS_ERROR_NOT_AVAILABLE;
   401   // Awaken the timer thread.
   402   if (mWaiting) {
   403     mNotified = true;
   404     mMonitor.Notify();
   405   }
   407   return NS_OK;
   408 }
   410 // This function must be called from within a lock
   411 int32_t TimerThread::AddTimerInternal(nsTimerImpl *aTimer)
   412 {
   413   if (mShutdown)
   414     return -1;
   416   TimeStamp now = TimeStamp::Now();
   418   TimerAdditionComparator c(now, aTimer);
   419   nsTimerImpl** insertSlot = mTimers.InsertElementSorted(aTimer, c);
   421   if (!insertSlot)
   422     return -1;
   424   aTimer->mArmed = true;
   425   NS_ADDREF(aTimer);
   427 #ifdef MOZ_TASK_TRACER
   428   aTimer->DispatchTracedTask();
   429 #endif
   431   return insertSlot - mTimers.Elements();
   432 }
   434 bool TimerThread::RemoveTimerInternal(nsTimerImpl *aTimer)
   435 {
   436   if (!mTimers.RemoveElement(aTimer))
   437     return false;
   439   ReleaseTimerInternal(aTimer);
   440   return true;
   441 }
   443 void TimerThread::ReleaseTimerInternal(nsTimerImpl *aTimer)
   444 {
   445   // Order is crucial here -- see nsTimerImpl::Release.
   446   aTimer->mArmed = false;
   447   NS_RELEASE(aTimer);
   448 }
   450 void TimerThread::DoBeforeSleep()
   451 {
   452   mSleeping = true;
   453 }
   455 void TimerThread::DoAfterSleep()
   456 {
   457   mSleeping = true; // wake may be notified without preceding sleep notification
   458   for (uint32_t i = 0; i < mTimers.Length(); i ++) {
   459     nsTimerImpl *timer = mTimers[i];
   460     // get and set the delay to cause its timeout to be recomputed
   461     uint32_t delay;
   462     timer->GetDelay(&delay);
   463     timer->SetDelay(delay);
   464   }
   466   mSleeping = false;
   467 }
   470 /* void observe (in nsISupports aSubject, in string aTopic, in wstring aData); */
   471 NS_IMETHODIMP
   472 TimerThread::Observe(nsISupports* /* aSubject */, const char *aTopic, const char16_t* /* aData */)
   473 {
   474   if (strcmp(aTopic, "sleep_notification") == 0 ||
   475       strcmp(aTopic, "suspend_process_notification") == 0)
   476     DoBeforeSleep();
   477   else if (strcmp(aTopic, "wake_notification") == 0 ||
   478            strcmp(aTopic, "resume_process_notification") == 0)
   479     DoAfterSleep();
   481   return NS_OK;
   482 }

mercurial