mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 15:52:07 +00:00
498938c5ca
Dispatching to the underlying thread of what could be a thread-pool can lead to data-race as the code won't be run where you expect it to. Differential Revision: https://phabricator.services.mozilla.com/D82632
805 lines
25 KiB
C++
805 lines
25 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 "GeckoProfiler.h"
|
|
#include "TimerThread.h"
|
|
#include "mozilla/Atomics.h"
|
|
#include "mozilla/IntegerPrintfMacros.h"
|
|
#include "mozilla/Logging.h"
|
|
#include "mozilla/Mutex.h"
|
|
#include "mozilla/ResultExtensions.h"
|
|
#include "mozilla/Sprintf.h"
|
|
#include "nsThreadManager.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "pratom.h"
|
|
#ifdef MOZ_TASK_TRACER
|
|
# include "GeckoTaskTracerImpl.h"
|
|
using namespace mozilla::tasktracer;
|
|
#endif
|
|
|
|
#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;
|
|
|
|
static TimerThread* gThread = nullptr;
|
|
|
|
// 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 gThread
|
|
? gThread->FindNextFireTimeForCurrentThread(aDefault, aSearchBound)
|
|
: TimeStamp();
|
|
}
|
|
|
|
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_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, uint32_t aDelay,
|
|
uint32_t aType, nsTimerNameCallbackFunc aNameCallback,
|
|
nsIEventTarget* aTarget) {
|
|
nsCOMPtr<nsITimer> timer;
|
|
MOZ_TRY(NS_NewTimerWithFuncCallback(getter_AddRefs(timer), aCallback,
|
|
aClosure, aDelay, aType, aNameCallback,
|
|
aTarget));
|
|
return std::move(timer);
|
|
}
|
|
nsresult NS_NewTimerWithFuncCallback(nsITimer** aTimer,
|
|
nsTimerCallbackFunc aCallback,
|
|
void* aClosure, uint32_t aDelay,
|
|
uint32_t aType,
|
|
nsTimerNameCallbackFunc aNameCallback,
|
|
nsIEventTarget* aTarget) {
|
|
auto timer = nsTimer::WithEventTarget(aTarget);
|
|
|
|
MOZ_TRY(timer->InitWithNameableFuncCallback(aCallback, aClosure, aDelay,
|
|
aType, aNameCallback));
|
|
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. The name used for a
|
|
// |Callback::Type::Function| timer depends on the circumstances.
|
|
//
|
|
// - If it was explicitly named (e.g. it was initialized with
|
|
// InitWithNamedFuncCallback()) then that explicit name will be shown.
|
|
//
|
|
// - Otherwise, if we are on a platform that supports function name lookup
|
|
// (Mac or Linux) then the looked-up name will be shown with a
|
|
// "[from dladdr]" annotation. On Mac the looked-up name will be immediately
|
|
// useful. On Linux it'll need post-processing with `tools/rb/fix_stacks.py`.
|
|
//
|
|
// - Otherwise, no name will be printed. If many timers hit this case then
|
|
// you'll need to re-run the workload on a Mac to find out which timers they
|
|
// are, and then give them explicit names.
|
|
//
|
|
// 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>
|
|
|
|
double nsTimerImpl::sDeltaSumSquared = 0;
|
|
double nsTimerImpl::sDeltaSum = 0;
|
|
double nsTimerImpl::sDeltaNum = 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_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),
|
|
mHolder(nullptr),
|
|
mType(0),
|
|
mGeneration(0),
|
|
mITimer(aTimer),
|
|
mMutex("nsTimerImpl::mMutex"),
|
|
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() {
|
|
nsresult rv;
|
|
|
|
gThread = new TimerThread();
|
|
|
|
NS_ADDREF(gThread);
|
|
rv = gThread->InitLocks();
|
|
|
|
if (NS_FAILED(rv)) {
|
|
NS_RELEASE(gThread);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
void nsTimerImpl::Shutdown() {
|
|
if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
|
|
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));
|
|
}
|
|
|
|
if (!gThread) {
|
|
return;
|
|
}
|
|
|
|
gThread->Shutdown();
|
|
NS_RELEASE(gThread);
|
|
}
|
|
|
|
nsresult nsTimerImpl::InitCommon(uint32_t aDelayMS, uint32_t aType,
|
|
Callback&& aNewCallback) {
|
|
return InitCommon(TimeDuration::FromMilliseconds(aDelayMS), aType,
|
|
std::move(aNewCallback));
|
|
}
|
|
|
|
nsresult nsTimerImpl::InitCommon(const TimeDuration& aDelay, uint32_t aType,
|
|
Callback&& newCallback) {
|
|
mMutex.AssertCurrentThreadOwns();
|
|
|
|
if (!gThread) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
if (!mEventTarget) {
|
|
NS_ERROR("mEventTarget is NULL");
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
gThread->RemoveTimer(this);
|
|
mCallback.swap(newCallback);
|
|
++mGeneration;
|
|
|
|
mType = (uint8_t)aType;
|
|
mDelay = aDelay;
|
|
mTimeout = TimeStamp::Now() + mDelay;
|
|
|
|
return gThread->AddTimer(this);
|
|
}
|
|
|
|
nsresult nsTimerImpl::InitWithFuncCallbackCommon(nsTimerCallbackFunc aFunc,
|
|
void* aClosure,
|
|
uint32_t aDelay,
|
|
uint32_t aType,
|
|
const Callback::Name& aName) {
|
|
if (NS_WARN_IF(!aFunc)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
Callback cb; // Goes out of scope after the unlock, prevents deadlock
|
|
cb.mType = Callback::Type::Function;
|
|
cb.mCallback.c = aFunc;
|
|
cb.mClosure = aClosure;
|
|
cb.mName = aName;
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
return InitCommon(aDelay, aType, std::move(cb));
|
|
}
|
|
|
|
nsresult nsTimerImpl::InitWithNamedFuncCallback(nsTimerCallbackFunc aFunc,
|
|
void* aClosure, uint32_t aDelay,
|
|
uint32_t aType,
|
|
const char* aNameString) {
|
|
Callback::Name name(aNameString);
|
|
return InitWithFuncCallbackCommon(aFunc, aClosure, aDelay, aType, name);
|
|
}
|
|
|
|
nsresult nsTimerImpl::InitWithNameableFuncCallback(
|
|
nsTimerCallbackFunc aFunc, void* aClosure, uint32_t aDelay, uint32_t aType,
|
|
nsTimerNameCallbackFunc aNameFunc) {
|
|
Callback::Name name(aNameFunc);
|
|
return InitWithFuncCallbackCommon(aFunc, aClosure, aDelay, aType, name);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
Callback cb; // Goes out of scope after the unlock, prevents deadlock
|
|
cb.mType = Callback::Type::Interface;
|
|
cb.mCallback.i = aCallback;
|
|
NS_ADDREF(cb.mCallback.i);
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
return InitCommon(aDelay, aType, std::move(cb));
|
|
}
|
|
|
|
nsresult nsTimerImpl::Init(nsIObserver* aObserver, uint32_t aDelayInMs,
|
|
uint32_t aType) {
|
|
if (NS_WARN_IF(!aObserver)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
Callback cb; // Goes out of scope after the unlock, prevents deadlock
|
|
cb.mType = Callback::Type::Observer;
|
|
cb.mCallback.o = aObserver;
|
|
NS_ADDREF(cb.mCallback.o);
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
return InitCommon(aDelayInMs, aType, std::move(cb));
|
|
}
|
|
|
|
nsresult nsTimerImpl::Cancel() {
|
|
CancelImpl(false);
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsTimerImpl::CancelImpl(bool aClearITimer) {
|
|
Callback cbTrash;
|
|
RefPtr<nsITimer> timerTrash;
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
if (gThread) {
|
|
gThread->RemoveTimer(this);
|
|
}
|
|
|
|
cbTrash.swap(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().mType == Callback::Type::Unknown && !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;
|
|
if (gThread) {
|
|
reAdd = NS_SUCCEEDED(gThread->RemoveTimer(this));
|
|
}
|
|
|
|
mDelay = TimeDuration::FromMilliseconds(aDelay);
|
|
mTimeout = TimeStamp::Now() + mDelay;
|
|
|
|
if (reAdd) {
|
|
gThread->AddTimer(this);
|
|
}
|
|
|
|
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);
|
|
*aClosure = GetCallback().mClosure;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsTimerImpl::GetCallback(nsITimerCallback** aCallback) {
|
|
MutexAutoLock lock(mMutex);
|
|
if (GetCallback().mType == Callback::Type::Interface) {
|
|
NS_IF_ADDREF(*aCallback = GetCallback().mCallback.i);
|
|
} 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.mType != Callback::Type::Unknown)) {
|
|
return NS_ERROR_ALREADY_INITIALIZED;
|
|
}
|
|
|
|
if (aTarget) {
|
|
mEventTarget = aTarget;
|
|
} else {
|
|
mEventTarget = mozilla::GetCurrentSerialEventTarget();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsTimerImpl::GetAllowedEarlyFiringMicroseconds(uint32_t* aValueOut) {
|
|
*aValueOut = gThread ? gThread->AllowedEarlyFiringMicroseconds() : 0;
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsTimerImpl::Fire(int32_t aGeneration) {
|
|
uint8_t oldType;
|
|
uint32_t oldDelay;
|
|
TimeStamp oldTimeout;
|
|
Callback callbackDuringFire;
|
|
nsCOMPtr<nsITimer> kungFuDeathGrip;
|
|
|
|
{
|
|
// 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) {
|
|
return;
|
|
}
|
|
|
|
++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.
|
|
kungFuDeathGrip = mITimer;
|
|
}
|
|
|
|
AUTO_PROFILER_LABEL("nsTimerImpl::Fire", OTHER);
|
|
|
|
TimeStamp now = TimeStamp::Now();
|
|
if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
|
|
TimeDuration delta = now - oldTimeout;
|
|
int32_t d = delta.ToMilliseconds(); // delta in ms
|
|
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);
|
|
}
|
|
|
|
switch (callbackDuringFire.mType) {
|
|
case Callback::Type::Function:
|
|
callbackDuringFire.mCallback.c(mITimer, callbackDuringFire.mClosure);
|
|
break;
|
|
case Callback::Type::Interface:
|
|
callbackDuringFire.mCallback.i->Notify(mITimer);
|
|
break;
|
|
case Callback::Type::Observer:
|
|
callbackDuringFire.mCallback.o->Observe(mITimer, NS_TIMER_CALLBACK_TOPIC,
|
|
nullptr);
|
|
break;
|
|
default:;
|
|
}
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
if (aGeneration == mGeneration) {
|
|
if (IsRepeating()) {
|
|
// Repeating timer has not been re-init or canceled; reschedule
|
|
if (IsSlack()) {
|
|
mTimeout = TimeStamp::Now() + mDelay;
|
|
} else {
|
|
mTimeout = mTimeout + mDelay;
|
|
}
|
|
if (gThread) {
|
|
gThread->AddTimer(this);
|
|
}
|
|
} else {
|
|
// Non-repeating timer that has not been re-scheduled. Clear.
|
|
mCallback.clear();
|
|
}
|
|
}
|
|
|
|
--mFiring;
|
|
|
|
MOZ_LOG(GetTimerLog(), LogLevel::Debug,
|
|
("[this=%p] Took %fms to fire timer callback\n", this,
|
|
(TimeStamp::Now() - now).ToMilliseconds()));
|
|
}
|
|
|
|
#if defined(HAVE_DLADDR) && defined(HAVE___CXA_DEMANGLE)
|
|
# define USE_DLADDR 1
|
|
#endif
|
|
|
|
#ifdef USE_DLADDR
|
|
# include <cxxabi.h>
|
|
# include <dlfcn.h>
|
|
#endif
|
|
|
|
// 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");
|
|
}
|
|
|
|
switch (aCallback.mType) {
|
|
case Callback::Type::Function: {
|
|
bool needToFreeName = false;
|
|
const char* annotation = "";
|
|
const char* name;
|
|
static const size_t buflen = 1024;
|
|
char buf[buflen];
|
|
|
|
if (aCallback.mName.is<Callback::NameString>()) {
|
|
name = aCallback.mName.as<Callback::NameString>();
|
|
|
|
} else if (aCallback.mName.is<Callback::NameFunc>()) {
|
|
aCallback.mName.as<Callback::NameFunc>()(
|
|
mITimer, /* aAnonymize = */ false, aCallback.mClosure, buf, buflen);
|
|
name = buf;
|
|
|
|
} else {
|
|
MOZ_ASSERT(aCallback.mName.is<Callback::NameNothing>());
|
|
#ifdef USE_DLADDR
|
|
annotation = "[from dladdr] ";
|
|
|
|
Dl_info info;
|
|
void* addr = reinterpret_cast<void*>(aCallback.mCallback.c);
|
|
if (dladdr(addr, &info) == 0) {
|
|
name = "???[dladdr: failed]";
|
|
|
|
} else if (info.dli_sname) {
|
|
int status;
|
|
name = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status);
|
|
if (status == 0) {
|
|
// Success. Because we didn't pass in a buffer to __cxa_demangle it
|
|
// allocates its own one with malloc() which we must free() later.
|
|
MOZ_ASSERT(name);
|
|
needToFreeName = true;
|
|
} else if (status == -1) {
|
|
name = "???[__cxa_demangle: OOM]";
|
|
} else if (status == -2) {
|
|
name = "???[__cxa_demangle: invalid mangled name]";
|
|
} else if (status == -3) {
|
|
name = "???[__cxa_demangle: invalid argument]";
|
|
} else {
|
|
name = "???[__cxa_demangle: unexpected status value]";
|
|
}
|
|
|
|
} else if (info.dli_fname) {
|
|
// The "#0: " prefix is necessary for `fix_stacks.py` to interpret
|
|
// this string as something to convert.
|
|
SprintfLiteral(buf, "#0: ???[%s +0x%" PRIxPTR "]\n", info.dli_fname,
|
|
uintptr_t(addr) - uintptr_t(info.dli_fbase));
|
|
name = buf;
|
|
|
|
} else {
|
|
name = "???[dladdr: no symbol or shared object obtained]";
|
|
}
|
|
#else
|
|
name = "???[dladdr is unimplemented or doesn't work well on this OS]";
|
|
#endif
|
|
}
|
|
|
|
MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug,
|
|
("[%d] fn timer (%s %5d ms): %s%s\n", getpid(), typeStr,
|
|
aDelay, annotation, name));
|
|
|
|
if (needToFreeName) {
|
|
free(const_cast<char*>(name));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Callback::Type::Interface: {
|
|
MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug,
|
|
("[%d] iface timer (%s %5d ms): %p\n", getpid(), typeStr, aDelay,
|
|
aCallback.mCallback.i));
|
|
break;
|
|
}
|
|
|
|
case Callback::Type::Observer: {
|
|
MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug,
|
|
("[%d] obs timer (%s %5d ms): %p\n", getpid(), typeStr, aDelay,
|
|
aCallback.mCallback.o));
|
|
break;
|
|
}
|
|
|
|
case Callback::Type::Unknown:
|
|
default: {
|
|
MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug,
|
|
("[%d] ??? timer (%s, %5d ms)\n", getpid(), typeStr, aDelay));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void nsTimerImpl::GetName(nsACString& aName) {
|
|
MutexAutoLock lock(mMutex);
|
|
Callback& cb(GetCallback());
|
|
switch (cb.mType) {
|
|
case Callback::Type::Function:
|
|
if (cb.mName.is<Callback::NameString>()) {
|
|
aName.Assign(cb.mName.as<Callback::NameString>());
|
|
} else if (cb.mName.is<Callback::NameFunc>()) {
|
|
static const size_t buflen = 1024;
|
|
char buf[buflen];
|
|
cb.mName.as<Callback::NameFunc>()(mITimer, /* aAnonymize = */ true,
|
|
cb.mClosure, buf, buflen);
|
|
aName.Assign(buf);
|
|
} else {
|
|
MOZ_ASSERT(cb.mName.is<Callback::NameNothing>());
|
|
aName.AssignLiteral("Anonymous_callback_timer");
|
|
}
|
|
break;
|
|
|
|
case Callback::Type::Interface:
|
|
if (nsCOMPtr<nsINamed> named = do_QueryInterface(cb.mCallback.i)) {
|
|
named->GetName(aName);
|
|
} else {
|
|
aName.AssignLiteral("Anonymous_interface_timer");
|
|
}
|
|
break;
|
|
|
|
case Callback::Type::Observer:
|
|
if (nsCOMPtr<nsINamed> named = do_QueryInterface(cb.mCallback.o)) {
|
|
named->GetName(aName);
|
|
} else {
|
|
aName.AssignLiteral("Anonymous_observer_timer");
|
|
}
|
|
break;
|
|
|
|
case Callback::Type::Unknown:
|
|
aName.AssignLiteral("Canceled_timer");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void nsTimerImpl::SetHolder(nsTimerImplHolder* aHolder) { mHolder = aHolder; }
|
|
|
|
nsTimer::~nsTimer() = default;
|
|
|
|
size_t nsTimer::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
|
|
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(nsISupports* aOuter, REFNSIID aIID,
|
|
void** aResult) {
|
|
*aResult = nullptr;
|
|
if (aOuter != nullptr) {
|
|
return NS_ERROR_NO_AGGREGATION;
|
|
}
|
|
|
|
auto timer = WithEventTarget(nullptr);
|
|
|
|
return timer->QueryInterface(aIID, aResult);
|
|
}
|
|
|
|
/* static */
|
|
const nsTimerImpl::Callback::NameNothing nsTimerImpl::Callback::Nothing = 0;
|
|
|
|
#ifdef MOZ_TASK_TRACER
|
|
void nsTimerImpl::GetTLSTraceInfo() { mTracedTask.GetTLSTraceInfo(); }
|
|
|
|
TracedTaskCommon nsTimerImpl::GetTracedTask() { return mTracedTask; }
|
|
|
|
#endif
|