summaryrefslogtreecommitdiffstats
path: root/xpcom/threads/nsTimerImpl.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /xpcom/threads/nsTimerImpl.cpp
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xpcom/threads/nsTimerImpl.cpp')
-rw-r--r--xpcom/threads/nsTimerImpl.cpp804
1 files changed, 804 insertions, 0 deletions
diff --git a/xpcom/threads/nsTimerImpl.cpp b/xpcom/threads/nsTimerImpl.cpp
new file mode 100644
index 0000000000..5e9061c9bd
--- /dev/null
+++ b/xpcom/threads/nsTimerImpl.cpp
@@ -0,0 +1,804 @@
+/* -*- 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