Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#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:
//
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);
}