forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			822 lines
		
	
	
	
		
			27 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			822 lines
		
	
	
	
		
			27 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "nsTimerImpl.h"
 | |
| 
 | |
| #include <utility>
 | |
| 
 | |
| #include "TimerThread.h"
 | |
| #include "mozilla/Atomics.h"
 | |
| #include "mozilla/IntegerPrintfMacros.h"
 | |
| #include "mozilla/Logging.h"
 | |
| #include "mozilla/Mutex.h"
 | |
| #include "mozilla/ProfilerLabels.h"
 | |
| #include "mozilla/ResultExtensions.h"
 | |
| #include "mozilla/Sprintf.h"
 | |
| #include "mozilla/StaticMutex.h"
 | |
| #include "mozilla/Try.h"
 | |
| #include "nsThreadManager.h"
 | |
| #include "nsThreadUtils.h"
 | |
| #include "pratom.h"
 | |
| 
 | |
| #ifdef XP_WIN
 | |
| #  include <process.h>
 | |
| #  ifndef getpid
 | |
| #    define getpid _getpid
 | |
| #  endif
 | |
| #else
 | |
| #  include <unistd.h>
 | |
| #endif
 | |
| 
 | |
| using mozilla::Atomic;
 | |
| using mozilla::LogLevel;
 | |
| using mozilla::MakeRefPtr;
 | |
| using mozilla::MutexAutoLock;
 | |
| using mozilla::TimeDuration;
 | |
| using mozilla::TimeStamp;
 | |
| 
 | |
| // Holds the timer thread and manages all interactions with it
 | |
| // under a locked mutex. This wrapper is not destroyed until after
 | |
| // nsThreadManager shutdown to ensure we don't UAF during an offthread access to
 | |
| // the timer thread.
 | |
| class TimerThreadWrapper {
 | |
|  public:
 | |
|   constexpr TimerThreadWrapper() : mThread(nullptr){};
 | |
|   ~TimerThreadWrapper() = default;
 | |
| 
 | |
|   nsresult Init();
 | |
|   void Shutdown();
 | |
| 
 | |
|   nsresult AddTimer(nsTimerImpl* aTimer, const MutexAutoLock& aProofOfLock)
 | |
|       MOZ_REQUIRES(aTimer->mMutex);
 | |
|   nsresult RemoveTimer(nsTimerImpl* aTimer, const MutexAutoLock& aProofOfLock)
 | |
|       MOZ_REQUIRES(aTimer->mMutex);
 | |
|   TimeStamp FindNextFireTimeForCurrentThread(TimeStamp aDefault,
 | |
|                                              uint32_t aSearchBound);
 | |
|   uint32_t AllowedEarlyFiringMicroseconds();
 | |
|   nsresult GetTimers(nsTArray<RefPtr<nsITimer>>& aRetVal);
 | |
| 
 | |
|  private:
 | |
|   static mozilla::StaticMutex sMutex;
 | |
|   TimerThread* mThread MOZ_GUARDED_BY(sMutex);
 | |
| };
 | |
| 
 | |
| mozilla::StaticMutex TimerThreadWrapper::sMutex;
 | |
| 
 | |
| nsresult TimerThreadWrapper::Init() {
 | |
|   mozilla::StaticMutexAutoLock lock(sMutex);
 | |
|   mThread = new TimerThread();
 | |
| 
 | |
|   NS_ADDREF(mThread);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void TimerThreadWrapper::Shutdown() {
 | |
|   RefPtr<TimerThread> thread;
 | |
| 
 | |
|   {
 | |
|     mozilla::StaticMutexAutoLock lock(sMutex);
 | |
|     if (!mThread) {
 | |
|       return;
 | |
|     }
 | |
|     thread = mThread;
 | |
|   }
 | |
|   // Shutdown calls |nsTimerImpl::Cancel| which needs to make a call into
 | |
|   // |RemoveTimer|. This can't be done under the lock.
 | |
|   thread->Shutdown();
 | |
| 
 | |
|   {
 | |
|     mozilla::StaticMutexAutoLock lock(sMutex);
 | |
|     NS_RELEASE(mThread);
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult TimerThreadWrapper::AddTimer(nsTimerImpl* aTimer,
 | |
|                                       const MutexAutoLock& aProofOfLock) {
 | |
|   mozilla::StaticMutexAutoLock lock(sMutex);
 | |
|   if (mThread) {
 | |
|     return mThread->AddTimer(aTimer, aProofOfLock);
 | |
|   }
 | |
|   return NS_ERROR_NOT_AVAILABLE;
 | |
| }
 | |
| 
 | |
| nsresult TimerThreadWrapper::RemoveTimer(nsTimerImpl* aTimer,
 | |
|                                          const MutexAutoLock& aProofOfLock) {
 | |
|   mozilla::StaticMutexAutoLock lock(sMutex);
 | |
|   if (mThread) {
 | |
|     return mThread->RemoveTimer(aTimer, aProofOfLock);
 | |
|   }
 | |
|   return NS_ERROR_NOT_AVAILABLE;
 | |
| }
 | |
| 
 | |
| TimeStamp TimerThreadWrapper::FindNextFireTimeForCurrentThread(
 | |
|     TimeStamp aDefault, uint32_t aSearchBound) {
 | |
|   mozilla::StaticMutexAutoLock lock(sMutex);
 | |
|   return mThread
 | |
|              ? mThread->FindNextFireTimeForCurrentThread(aDefault, aSearchBound)
 | |
|              : TimeStamp();
 | |
| }
 | |
| 
 | |
| uint32_t TimerThreadWrapper::AllowedEarlyFiringMicroseconds() {
 | |
|   mozilla::StaticMutexAutoLock lock(sMutex);
 | |
|   return mThread ? mThread->AllowedEarlyFiringMicroseconds() : 0;
 | |
| }
 | |
| 
 | |
| nsresult TimerThreadWrapper::GetTimers(nsTArray<RefPtr<nsITimer>>& aRetVal) {
 | |
|   RefPtr<TimerThread> thread;
 | |
|   {
 | |
|     mozilla::StaticMutexAutoLock lock(sMutex);
 | |
|     if (!mThread) {
 | |
|       return NS_ERROR_NOT_AVAILABLE;
 | |
|     }
 | |
|     thread = mThread;
 | |
|   }
 | |
|   return thread->GetTimers(aRetVal);
 | |
| }
 | |
| 
 | |
| static TimerThreadWrapper gThreadWrapper;
 | |
| 
 | |
| // This module prints info about the precision of timers.
 | |
| static mozilla::LazyLogModule sTimerLog("nsTimerImpl");
 | |
| 
 | |
| mozilla::LogModule* GetTimerLog() { return sTimerLog; }
 | |
| 
 | |
| TimeStamp NS_GetTimerDeadlineHintOnCurrentThread(TimeStamp aDefault,
 | |
|                                                  uint32_t aSearchBound) {
 | |
|   return gThreadWrapper.FindNextFireTimeForCurrentThread(aDefault,
 | |
|                                                          aSearchBound);
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsITimer> NS_NewTimer() { return NS_NewTimer(nullptr); }
 | |
| 
 | |
| already_AddRefed<nsITimer> NS_NewTimer(nsIEventTarget* aTarget) {
 | |
|   return nsTimer::WithEventTarget(aTarget).forget();
 | |
| }
 | |
| 
 | |
| mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithObserver(
 | |
|     nsIObserver* aObserver, uint32_t aDelay, uint32_t aType,
 | |
|     nsIEventTarget* aTarget) {
 | |
|   nsCOMPtr<nsITimer> timer;
 | |
|   MOZ_TRY(NS_NewTimerWithObserver(getter_AddRefs(timer), aObserver, aDelay,
 | |
|                                   aType, aTarget));
 | |
|   return std::move(timer);
 | |
| }
 | |
| nsresult NS_NewTimerWithObserver(nsITimer** aTimer, nsIObserver* aObserver,
 | |
|                                  uint32_t aDelay, uint32_t aType,
 | |
|                                  nsIEventTarget* aTarget) {
 | |
|   auto timer = nsTimer::WithEventTarget(aTarget);
 | |
| 
 | |
|   MOZ_TRY(timer->Init(aObserver, aDelay, aType));
 | |
|   timer.forget(aTimer);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithCallback(
 | |
|     nsITimerCallback* aCallback, uint32_t aDelay, uint32_t aType,
 | |
|     nsIEventTarget* aTarget) {
 | |
|   nsCOMPtr<nsITimer> timer;
 | |
|   MOZ_TRY(NS_NewTimerWithCallback(getter_AddRefs(timer), aCallback, aDelay,
 | |
|                                   aType, aTarget));
 | |
|   return std::move(timer);
 | |
| }
 | |
| nsresult NS_NewTimerWithCallback(nsITimer** aTimer, nsITimerCallback* aCallback,
 | |
|                                  uint32_t aDelay, uint32_t aType,
 | |
|                                  nsIEventTarget* aTarget) {
 | |
|   auto timer = nsTimer::WithEventTarget(aTarget);
 | |
| 
 | |
|   MOZ_TRY(timer->InitWithCallback(aCallback, aDelay, aType));
 | |
|   timer.forget(aTimer);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithCallback(
 | |
|     nsITimerCallback* aCallback, const TimeDuration& aDelay, uint32_t aType,
 | |
|     nsIEventTarget* aTarget) {
 | |
|   nsCOMPtr<nsITimer> timer;
 | |
|   MOZ_TRY(NS_NewTimerWithCallback(getter_AddRefs(timer), aCallback, aDelay,
 | |
|                                   aType, aTarget));
 | |
|   return std::move(timer);
 | |
| }
 | |
| nsresult NS_NewTimerWithCallback(nsITimer** aTimer, nsITimerCallback* aCallback,
 | |
|                                  const TimeDuration& aDelay, uint32_t aType,
 | |
|                                  nsIEventTarget* aTarget) {
 | |
|   auto timer = nsTimer::WithEventTarget(aTarget);
 | |
| 
 | |
|   MOZ_TRY(timer->InitHighResolutionWithCallback(aCallback, aDelay, aType));
 | |
|   timer.forget(aTimer);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithCallback(
 | |
|     std::function<void(nsITimer*)>&& aCallback, uint32_t aDelay, uint32_t aType,
 | |
|     const char* aNameString, nsIEventTarget* aTarget) {
 | |
|   nsCOMPtr<nsITimer> timer;
 | |
|   MOZ_TRY(NS_NewTimerWithCallback(getter_AddRefs(timer), std::move(aCallback),
 | |
|                                   aDelay, aType, aNameString, aTarget));
 | |
|   return timer;
 | |
| }
 | |
| nsresult NS_NewTimerWithCallback(nsITimer** aTimer,
 | |
|                                  std::function<void(nsITimer*)>&& aCallback,
 | |
|                                  uint32_t aDelay, uint32_t aType,
 | |
|                                  const char* aNameString,
 | |
|                                  nsIEventTarget* aTarget) {
 | |
|   return NS_NewTimerWithCallback(aTimer, std::move(aCallback),
 | |
|                                  TimeDuration::FromMilliseconds(aDelay), aType,
 | |
|                                  aNameString, aTarget);
 | |
| }
 | |
| 
 | |
| mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithCallback(
 | |
|     std::function<void(nsITimer*)>&& aCallback, const TimeDuration& aDelay,
 | |
|     uint32_t aType, const char* aNameString, nsIEventTarget* aTarget) {
 | |
|   nsCOMPtr<nsITimer> timer;
 | |
|   MOZ_TRY(NS_NewTimerWithCallback(getter_AddRefs(timer), std::move(aCallback),
 | |
|                                   aDelay, aType, aNameString, aTarget));
 | |
|   return timer;
 | |
| }
 | |
| nsresult NS_NewTimerWithCallback(nsITimer** aTimer,
 | |
|                                  std::function<void(nsITimer*)>&& aCallback,
 | |
|                                  const TimeDuration& aDelay, uint32_t aType,
 | |
|                                  const char* aNameString,
 | |
|                                  nsIEventTarget* aTarget) {
 | |
|   RefPtr<nsTimer> timer = nsTimer::WithEventTarget(aTarget);
 | |
| 
 | |
|   MOZ_TRY(timer->InitWithClosureCallback(std::move(aCallback), aDelay, aType,
 | |
|                                          aNameString));
 | |
|   timer.forget(aTimer);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithFuncCallback(
 | |
|     nsTimerCallbackFunc aCallback, void* aClosure, uint32_t aDelay,
 | |
|     uint32_t aType, const char* aNameString, nsIEventTarget* aTarget) {
 | |
|   nsCOMPtr<nsITimer> timer;
 | |
|   MOZ_TRY(NS_NewTimerWithFuncCallback(getter_AddRefs(timer), aCallback,
 | |
|                                       aClosure, aDelay, aType, aNameString,
 | |
|                                       aTarget));
 | |
|   return std::move(timer);
 | |
| }
 | |
| nsresult NS_NewTimerWithFuncCallback(nsITimer** aTimer,
 | |
|                                      nsTimerCallbackFunc aCallback,
 | |
|                                      void* aClosure, uint32_t aDelay,
 | |
|                                      uint32_t aType, const char* aNameString,
 | |
|                                      nsIEventTarget* aTarget) {
 | |
|   auto timer = nsTimer::WithEventTarget(aTarget);
 | |
| 
 | |
|   MOZ_TRY(timer->InitWithNamedFuncCallback(aCallback, aClosure, aDelay, aType,
 | |
|                                            aNameString));
 | |
|   timer.forget(aTimer);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithFuncCallback(
 | |
|     nsTimerCallbackFunc aCallback, void* aClosure, const TimeDuration& aDelay,
 | |
|     uint32_t aType, const char* aNameString, nsIEventTarget* aTarget) {
 | |
|   nsCOMPtr<nsITimer> timer;
 | |
|   MOZ_TRY(NS_NewTimerWithFuncCallback(getter_AddRefs(timer), aCallback,
 | |
|                                       aClosure, aDelay, aType, aNameString,
 | |
|                                       aTarget));
 | |
|   return std::move(timer);
 | |
| }
 | |
| nsresult NS_NewTimerWithFuncCallback(nsITimer** aTimer,
 | |
|                                      nsTimerCallbackFunc aCallback,
 | |
|                                      void* aClosure, const TimeDuration& aDelay,
 | |
|                                      uint32_t aType, const char* aNameString,
 | |
|                                      nsIEventTarget* aTarget) {
 | |
|   auto timer = nsTimer::WithEventTarget(aTarget);
 | |
| 
 | |
|   MOZ_TRY(timer->InitHighResolutionWithNamedFuncCallback(
 | |
|       aCallback, aClosure, aDelay, aType, aNameString));
 | |
|   timer.forget(aTimer);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // This module prints info about which timers are firing, which is useful for
 | |
| // wakeups for the purposes of power profiling. Set the following environment
 | |
| // variable before starting the browser.
 | |
| //
 | |
| //   MOZ_LOG=TimerFirings:4
 | |
| //
 | |
| // Then a line will be printed for every timer that fires.
 | |
| //
 | |
| // If you redirect this output to a file called "out", you can then
 | |
| // post-process it with a command something like the following.
 | |
| //
 | |
| //   cat out | grep timer | sort | uniq -c | sort -r -n
 | |
| //
 | |
| // This will show how often each unique line appears, with the most common ones
 | |
| // first.
 | |
| //
 | |
| // More detailed docs are here:
 | |
| // https://developer.mozilla.org/en-US/docs/Mozilla/Performance/TimerFirings_logging
 | |
| //
 | |
| static mozilla::LazyLogModule sTimerFiringsLog("TimerFirings");
 | |
| 
 | |
| static mozilla::LogModule* GetTimerFiringsLog() { return sTimerFiringsLog; }
 | |
| 
 | |
| #include <math.h>
 | |
| 
 | |
| /* static */
 | |
| mozilla::StaticMutex nsTimerImpl::sDeltaMutex;
 | |
| /* static */
 | |
| double nsTimerImpl::sDeltaSumSquared MOZ_GUARDED_BY(nsTimerImpl::sDeltaMutex) =
 | |
|     0;
 | |
| /* static */
 | |
| double nsTimerImpl::sDeltaSum MOZ_GUARDED_BY(nsTimerImpl::sDeltaMutex) = 0;
 | |
| /* static */
 | |
| double nsTimerImpl::sDeltaNum MOZ_GUARDED_BY(nsTimerImpl::sDeltaMutex) = 0;
 | |
| 
 | |
| static void myNS_MeanAndStdDev(double n, double sumOfValues,
 | |
|                                double sumOfSquaredValues, double* meanResult,
 | |
|                                double* stdDevResult) {
 | |
|   double mean = 0.0, var = 0.0, stdDev = 0.0;
 | |
|   if (n > 0.0 && sumOfValues >= 0) {
 | |
|     mean = sumOfValues / n;
 | |
|     double temp = (n * sumOfSquaredValues) - (sumOfValues * sumOfValues);
 | |
|     if (temp < 0.0 || n <= 1) {
 | |
|       var = 0.0;
 | |
|     } else {
 | |
|       var = temp / (n * (n - 1));
 | |
|     }
 | |
|     // for some reason, Windows says sqrt(0.0) is "-1.#J" (?!) so do this:
 | |
|     stdDev = var != 0.0 ? sqrt(var) : 0.0;
 | |
|   }
 | |
|   *meanResult = mean;
 | |
|   *stdDevResult = stdDev;
 | |
| }
 | |
| 
 | |
| NS_IMPL_QUERY_INTERFACE(nsTimer, nsITimer)
 | |
| NS_IMPL_ADDREF(nsTimer)
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(nsTimerManager, nsITimerManager)
 | |
| 
 | |
| NS_IMETHODIMP nsTimerManager::GetTimers(nsTArray<RefPtr<nsITimer>>& aRetVal) {
 | |
|   return gThreadWrapper.GetTimers(aRetVal);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP_(MozExternalRefCountType)
 | |
| nsTimer::Release(void) {
 | |
|   nsrefcnt count = --mRefCnt;
 | |
|   NS_LOG_RELEASE(this, count, "nsTimer");
 | |
| 
 | |
|   if (count == 1) {
 | |
|     // Last ref, in nsTimerImpl::mITimer. Make sure the cycle is broken.
 | |
|     mImpl->CancelImpl(true);
 | |
|   } else if (count == 0) {
 | |
|     delete this;
 | |
|   }
 | |
| 
 | |
|   return count;
 | |
| }
 | |
| 
 | |
| nsTimerImpl::nsTimerImpl(nsITimer* aTimer, nsIEventTarget* aTarget)
 | |
|     : mEventTarget(aTarget),
 | |
|       mIsInTimerThread(false),
 | |
|       mType(0),
 | |
|       mGeneration(0),
 | |
|       mITimer(aTimer),
 | |
|       mMutex("nsTimerImpl::mMutex"),
 | |
|       mCallback(UnknownCallback{}),
 | |
|       mFiring(0) {
 | |
|   // XXX some code creates timers during xpcom shutdown, when threads are no
 | |
|   // longer available, so we cannot turn this on yet.
 | |
|   // MOZ_ASSERT(mEventTarget);
 | |
| }
 | |
| 
 | |
| // static
 | |
| nsresult nsTimerImpl::Startup() { return gThreadWrapper.Init(); }
 | |
| 
 | |
| void nsTimerImpl::Shutdown() {
 | |
|   if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
 | |
|     mozilla::StaticMutexAutoLock lock(sDeltaMutex);
 | |
|     double mean = 0, stddev = 0;
 | |
|     myNS_MeanAndStdDev(sDeltaNum, sDeltaSum, sDeltaSumSquared, &mean, &stddev);
 | |
| 
 | |
|     MOZ_LOG(GetTimerLog(), LogLevel::Debug,
 | |
|             ("sDeltaNum = %f, sDeltaSum = %f, sDeltaSumSquared = %f\n",
 | |
|              sDeltaNum, sDeltaSum, sDeltaSumSquared));
 | |
|     MOZ_LOG(GetTimerLog(), LogLevel::Debug,
 | |
|             ("mean: %fms, stddev: %fms\n", mean, stddev));
 | |
|   }
 | |
| 
 | |
|   gThreadWrapper.Shutdown();
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::InitCommon(const TimeDuration& aDelay, uint32_t aType,
 | |
|                                  Callback&& newCallback,
 | |
|                                  const MutexAutoLock& aProofOfLock) {
 | |
|   if (!mEventTarget) {
 | |
|     return NS_ERROR_NOT_INITIALIZED;
 | |
|   }
 | |
| 
 | |
|   gThreadWrapper.RemoveTimer(this, aProofOfLock);
 | |
| 
 | |
|   // If we have an existing callback, using `swap` ensures it's destroyed after
 | |
|   // the mutex is unlocked in our caller.
 | |
|   std::swap(mCallback, newCallback);
 | |
|   ++mGeneration;
 | |
| 
 | |
|   mType = (uint8_t)aType;
 | |
|   mDelay = aDelay;
 | |
|   mTimeout = TimeStamp::Now() + mDelay;
 | |
| 
 | |
|   return gThreadWrapper.AddTimer(this, aProofOfLock);
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::InitWithNamedFuncCallback(nsTimerCallbackFunc aFunc,
 | |
|                                                 void* aClosure, uint32_t aDelay,
 | |
|                                                 uint32_t aType,
 | |
|                                                 const char* aName) {
 | |
|   return InitHighResolutionWithNamedFuncCallback(
 | |
|       aFunc, aClosure, TimeDuration::FromMilliseconds(aDelay), aType, aName);
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::InitHighResolutionWithNamedFuncCallback(
 | |
|     nsTimerCallbackFunc aFunc, void* aClosure, const TimeDuration& aDelay,
 | |
|     uint32_t aType, const char* aName) {
 | |
|   if (NS_WARN_IF(!aFunc)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   Callback cb{FuncCallback{aFunc, aClosure, aName}};
 | |
| 
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return InitCommon(aDelay, aType, std::move(cb), lock);
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::InitWithCallback(nsITimerCallback* aCallback,
 | |
|                                        uint32_t aDelayInMs, uint32_t aType) {
 | |
|   return InitHighResolutionWithCallback(
 | |
|       aCallback, TimeDuration::FromMilliseconds(aDelayInMs), aType);
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::InitHighResolutionWithCallback(
 | |
|     nsITimerCallback* aCallback, const TimeDuration& aDelay, uint32_t aType) {
 | |
|   if (NS_WARN_IF(!aCallback)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   // Goes out of scope after the unlock, prevents deadlock
 | |
|   Callback cb{nsCOMPtr{aCallback}};
 | |
| 
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return InitCommon(aDelay, aType, std::move(cb), lock);
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::Init(nsIObserver* aObserver, uint32_t aDelayInMs,
 | |
|                            uint32_t aType) {
 | |
|   if (NS_WARN_IF(!aObserver)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   Callback cb{nsCOMPtr{aObserver}};
 | |
| 
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return InitCommon(TimeDuration::FromMilliseconds(aDelayInMs), aType,
 | |
|                     std::move(cb), lock);
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::InitWithClosureCallback(
 | |
|     std::function<void(nsITimer*)>&& aCallback, const TimeDuration& aDelay,
 | |
|     uint32_t aType, const char* aNameString) {
 | |
|   if (NS_WARN_IF(!aCallback)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   Callback cb{ClosureCallback{std::move(aCallback), aNameString}};
 | |
| 
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return InitCommon(aDelay, aType, std::move(cb), lock);
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::Cancel() {
 | |
|   CancelImpl(false);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsTimerImpl::CancelImpl(bool aClearITimer) {
 | |
|   Callback cbTrash{UnknownCallback{}};
 | |
|   RefPtr<nsITimer> timerTrash;
 | |
| 
 | |
|   {
 | |
|     MutexAutoLock lock(mMutex);
 | |
|     gThreadWrapper.RemoveTimer(this, lock);
 | |
| 
 | |
|     // The swap ensures our callback isn't dropped until after the mutex is
 | |
|     // unlocked.
 | |
|     std::swap(cbTrash, mCallback);
 | |
|     ++mGeneration;
 | |
| 
 | |
|     // Don't clear this if we're firing; once Fire returns, we'll get this call
 | |
|     // again.
 | |
|     if (aClearITimer && !mFiring) {
 | |
|       MOZ_RELEASE_ASSERT(
 | |
|           mITimer,
 | |
|           "mITimer was nulled already! "
 | |
|           "This indicates that someone has messed up the refcount on nsTimer!");
 | |
|       timerTrash.swap(mITimer);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::SetDelay(uint32_t aDelay) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   if (GetCallback().is<UnknownCallback>() && !IsRepeating()) {
 | |
|     // This may happen if someone tries to re-use a one-shot timer
 | |
|     // by re-setting delay instead of reinitializing the timer.
 | |
|     NS_ERROR(
 | |
|         "nsITimer->SetDelay() called when the "
 | |
|         "one-shot timer is not set up.");
 | |
|     return NS_ERROR_NOT_INITIALIZED;
 | |
|   }
 | |
| 
 | |
|   bool reAdd = false;
 | |
|   reAdd = NS_SUCCEEDED(gThreadWrapper.RemoveTimer(this, lock));
 | |
| 
 | |
|   mDelay = TimeDuration::FromMilliseconds(aDelay);
 | |
|   mTimeout = TimeStamp::Now() + mDelay;
 | |
| 
 | |
|   if (reAdd) {
 | |
|     gThreadWrapper.AddTimer(this, lock);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::GetDelay(uint32_t* aDelay) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   *aDelay = mDelay.ToMilliseconds();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::SetType(uint32_t aType) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   mType = (uint8_t)aType;
 | |
|   // XXX if this is called, we should change the actual type.. this could effect
 | |
|   // repeating timers.  we need to ensure in Fire() that if mType has changed
 | |
|   // during the callback that we don't end up with the timer in the queue twice.
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::GetType(uint32_t* aType) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   *aType = mType;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::GetClosure(void** aClosure) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   if (GetCallback().is<FuncCallback>()) {
 | |
|     *aClosure = GetCallback().as<FuncCallback>().mClosure;
 | |
|   } else {
 | |
|     *aClosure = nullptr;
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::GetCallback(nsITimerCallback** aCallback) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   if (GetCallback().is<InterfaceCallback>()) {
 | |
|     NS_IF_ADDREF(*aCallback = GetCallback().as<InterfaceCallback>());
 | |
|   } else {
 | |
|     *aCallback = nullptr;
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::GetTarget(nsIEventTarget** aTarget) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   NS_IF_ADDREF(*aTarget = mEventTarget);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::SetTarget(nsIEventTarget* aTarget) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   if (NS_WARN_IF(!mCallback.is<UnknownCallback>())) {
 | |
|     return NS_ERROR_ALREADY_INITIALIZED;
 | |
|   }
 | |
| 
 | |
|   if (aTarget) {
 | |
|     mEventTarget = aTarget;
 | |
|   } else {
 | |
|     mEventTarget = mozilla::GetCurrentSerialEventTarget();
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::GetAllowedEarlyFiringMicroseconds(uint32_t* aValueOut) {
 | |
|   *aValueOut = gThreadWrapper.AllowedEarlyFiringMicroseconds();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsTimerImpl::Fire(int32_t aGeneration) {
 | |
|   uint8_t oldType;
 | |
|   uint32_t oldDelay;
 | |
|   TimeStamp oldTimeout;
 | |
|   Callback callbackDuringFire{UnknownCallback{}};
 | |
|   nsCOMPtr<nsITimer> timer;
 | |
| 
 | |
|   {
 | |
|     // Don't fire callbacks or fiddle with refcounts when the mutex is locked.
 | |
|     // If some other thread Cancels/Inits after this, they're just too late.
 | |
|     MutexAutoLock lock(mMutex);
 | |
|     if (aGeneration != mGeneration) {
 | |
|       // This timer got rescheduled or cancelled before we fired, so ignore this
 | |
|       // firing
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // We modify mTimeout, so we must not be in the current TimerThread's
 | |
|     // mTimers list.
 | |
|     MOZ_ASSERT(!mIsInTimerThread);
 | |
| 
 | |
|     ++mFiring;
 | |
|     callbackDuringFire = mCallback;
 | |
|     oldType = mType;
 | |
|     oldDelay = mDelay.ToMilliseconds();
 | |
|     oldTimeout = mTimeout;
 | |
|     // Ensure that the nsITimer does not unhook from the nsTimerImpl during
 | |
|     // Fire; this will cause null pointer crashes if the user of the timer drops
 | |
|     // its reference, and then uses the nsITimer* passed in the callback.
 | |
|     timer = mITimer;
 | |
|   }
 | |
| 
 | |
|   AUTO_PROFILER_LABEL("nsTimerImpl::Fire", OTHER);
 | |
| 
 | |
|   TimeStamp fireTime;
 | |
|   if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
 | |
|     fireTime = TimeStamp::Now();
 | |
|     TimeDuration delta = fireTime - oldTimeout;
 | |
|     int32_t d = delta.ToMilliseconds();  // delta in ms
 | |
|     {
 | |
|       mozilla::StaticMutexAutoLock lock(sDeltaMutex);
 | |
|       sDeltaSum += abs(d);
 | |
|       sDeltaSumSquared += double(d) * double(d);
 | |
|       sDeltaNum++;
 | |
|     }
 | |
| 
 | |
|     MOZ_LOG(GetTimerLog(), LogLevel::Debug,
 | |
|             ("[this=%p] expected delay time %4ums\n", this, oldDelay));
 | |
|     MOZ_LOG(GetTimerLog(), LogLevel::Debug,
 | |
|             ("[this=%p] actual delay time   %4dms\n", this, oldDelay + d));
 | |
|     MOZ_LOG(GetTimerLog(), LogLevel::Debug,
 | |
|             ("[this=%p] (mType is %d)       -------\n", this, oldType));
 | |
|     MOZ_LOG(GetTimerLog(), LogLevel::Debug,
 | |
|             ("[this=%p]     delta           %4dms\n", this, d));
 | |
|   }
 | |
| 
 | |
|   if (MOZ_LOG_TEST(GetTimerFiringsLog(), LogLevel::Debug)) {
 | |
|     LogFiring(callbackDuringFire, oldType, oldDelay);
 | |
|   }
 | |
| 
 | |
|   callbackDuringFire.match(
 | |
|       [](const UnknownCallback&) {},
 | |
|       [&](const InterfaceCallback& i) { i->Notify(timer); },
 | |
|       [&](const ObserverCallback& o) {
 | |
|         o->Observe(timer, NS_TIMER_CALLBACK_TOPIC, nullptr);
 | |
|       },
 | |
|       [&](const FuncCallback& f) { f.mFunc(timer, f.mClosure); },
 | |
|       [&](const ClosureCallback& c) { c.mFunc(timer); });
 | |
| 
 | |
|   TimeStamp now = TimeStamp::Now();
 | |
| 
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   if (aGeneration == mGeneration) {
 | |
|     if (IsRepeating()) {
 | |
|       // Repeating timer has not been re-init or canceled; reschedule
 | |
|       if (IsSlack()) {
 | |
|         mTimeout = now + mDelay;
 | |
|       } else {
 | |
|         if (mDelay) {
 | |
|           // If we are late enough finishing the callback that we have missed
 | |
|           // some firings, do not attempt to play catchup, just get back on the
 | |
|           // cadence we're supposed to maintain.
 | |
|           unsigned missedFirings =
 | |
|               static_cast<unsigned>((now - mTimeout) / mDelay);
 | |
|           mTimeout += mDelay * (missedFirings + 1);
 | |
|         } else {
 | |
|           // Can we stop allowing repeating timers with delay 0?
 | |
|           mTimeout = now;
 | |
|         }
 | |
|       }
 | |
|       gThreadWrapper.AddTimer(this, lock);
 | |
|     } else {
 | |
|       // Non-repeating timer that has not been re-scheduled. Clear.
 | |
|       // XXX(nika): Other callsites seem to go to some effort to avoid
 | |
|       // destroying mCallback when it's held. Why not this one?
 | |
|       mCallback = mozilla::AsVariant(UnknownCallback{});
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   --mFiring;
 | |
| 
 | |
|   MOZ_LOG(GetTimerLog(), LogLevel::Debug,
 | |
|           ("[this=%p] Took %fms to fire timer callback\n", this,
 | |
|            (now - fireTime).ToMilliseconds()));
 | |
| }
 | |
| 
 | |
| // See the big comment above GetTimerFiringsLog() to understand this code.
 | |
| void nsTimerImpl::LogFiring(const Callback& aCallback, uint8_t aType,
 | |
|                             uint32_t aDelay) {
 | |
|   const char* typeStr;
 | |
|   switch (aType) {
 | |
|     case nsITimer::TYPE_ONE_SHOT:
 | |
|       typeStr = "ONE_SHOT  ";
 | |
|       break;
 | |
|     case nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY:
 | |
|       typeStr = "ONE_LOW   ";
 | |
|       break;
 | |
|     case nsITimer::TYPE_REPEATING_SLACK:
 | |
|       typeStr = "SLACK     ";
 | |
|       break;
 | |
|     case nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY:
 | |
|       typeStr = "SLACK_LOW ";
 | |
|       break;
 | |
|     case nsITimer::TYPE_REPEATING_PRECISE: /* fall through */
 | |
|     case nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP:
 | |
|       typeStr = "PRECISE   ";
 | |
|       break;
 | |
|     default:
 | |
|       MOZ_CRASH("bad type");
 | |
|   }
 | |
| 
 | |
|   aCallback.match(
 | |
|       [&](const UnknownCallback&) {
 | |
|         MOZ_LOG(
 | |
|             GetTimerFiringsLog(), LogLevel::Debug,
 | |
|             ("[%d]     ??? timer (%s, %5d ms)\n", getpid(), typeStr, aDelay));
 | |
|       },
 | |
|       [&](const InterfaceCallback& i) {
 | |
|         MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug,
 | |
|                 ("[%d]   iface timer (%s %5d ms): %p\n", getpid(), typeStr,
 | |
|                  aDelay, i.get()));
 | |
|       },
 | |
|       [&](const ObserverCallback& o) {
 | |
|         MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug,
 | |
|                 ("[%d]     obs timer (%s %5d ms): %p\n", getpid(), typeStr,
 | |
|                  aDelay, o.get()));
 | |
|       },
 | |
|       [&](const FuncCallback& f) {
 | |
|         MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug,
 | |
|                 ("[%d]      fn timer (%s %5d ms): %s\n", getpid(), typeStr,
 | |
|                  aDelay, f.mName));
 | |
|       },
 | |
|       [&](const ClosureCallback& c) {
 | |
|         MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug,
 | |
|                 ("[%d] closure timer (%s %5d ms): %s\n", getpid(), typeStr,
 | |
|                  aDelay, c.mName));
 | |
|       });
 | |
| }
 | |
| 
 | |
| void nsTimerImpl::GetName(nsACString& aName,
 | |
|                           const MutexAutoLock& aProofOfLock) {
 | |
|   GetCallback().match(
 | |
|       [&](const UnknownCallback&) { aName.AssignLiteral("Canceled_timer"); },
 | |
|       [&](const InterfaceCallback& i) {
 | |
|         if (nsCOMPtr<nsINamed> named = do_QueryInterface(i)) {
 | |
|           named->GetName(aName);
 | |
|         } else {
 | |
|           aName.AssignLiteral("Anonymous_interface_timer");
 | |
|         }
 | |
|       },
 | |
|       [&](const ObserverCallback& o) {
 | |
|         if (nsCOMPtr<nsINamed> named = do_QueryInterface(o)) {
 | |
|           named->GetName(aName);
 | |
|         } else {
 | |
|           aName.AssignLiteral("Anonymous_observer_timer");
 | |
|         }
 | |
|       },
 | |
|       [&](const FuncCallback& f) { aName.Assign(f.mName); },
 | |
|       [&](const ClosureCallback& c) { aName.Assign(c.mName); });
 | |
| }
 | |
| 
 | |
| nsresult nsTimerImpl::GetName(nsACString& aName) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   GetName(aName, lock);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsTimer::~nsTimer() = default;
 | |
| 
 | |
| size_t nsTimer::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
 | |
|   return aMallocSizeOf(this);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| RefPtr<nsTimer> nsTimer::WithEventTarget(nsIEventTarget* aTarget) {
 | |
|   if (!aTarget) {
 | |
|     aTarget = mozilla::GetCurrentSerialEventTarget();
 | |
|   }
 | |
|   return do_AddRef(new nsTimer(aTarget));
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| nsresult nsTimer::XPCOMConstructor(REFNSIID aIID, void** aResult) {
 | |
|   *aResult = nullptr;
 | |
|   auto timer = WithEventTarget(nullptr);
 | |
| 
 | |
|   return timer->QueryInterface(aIID, aResult);
 | |
| }
 | 
