mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			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);
 | 
						|
}
 |