diff options
Diffstat (limited to 'xpcom/threads/nsTimerImpl.cpp')
-rw-r--r-- | xpcom/threads/nsTimerImpl.cpp | 822 |
1 files changed, 822 insertions, 0 deletions
diff --git a/xpcom/threads/nsTimerImpl.cpp b/xpcom/threads/nsTimerImpl.cpp new file mode 100644 index 0000000000..b350e9c0a9 --- /dev/null +++ b/xpcom/threads/nsTimerImpl.cpp @@ -0,0 +1,822 @@ +/* -*- 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); +} |