Bug 1828013 - Add a scriptable API to list all current nsITimer instances, r=smaug.

Differential Revision: https://phabricator.services.mozilla.com/D175430
This commit is contained in:
Florian Quèze 2023-04-20 09:43:41 +00:00
Родитель 58bcaf9a55
Коммит acf498ccc7
8 изменённых файлов: 258 добавлений и 3 удалений

Просмотреть файл

@ -0,0 +1,90 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const { AppConstants } = ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
const timerManager = Cc["@mozilla.org/timer-manager;1"].getService(
Ci.nsITimerManager
);
function newTimer(name, delay, type) {
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(
{
QueryInterface: ChromeUtils.generateQI(["nsITimerCallback", "nsINamed"]),
name,
notify: () => {},
},
delay,
type
);
return timer;
}
function getTimers() {
return timerManager.getTimers().filter(t => {
if (t.name == "BackgroundHangThread_timer") {
// BHR is Nightly-only, so just ignore it.
return false;
}
if (AppConstants.platform == "win" && t.name == "nsAnonTempFileRemover") {
// On Windows there's a 3min timer added at startup to then add an
// idle observer that finally triggers removing leftover temp files.
// Ignore that too.
return false;
}
return true;
});
}
function run_test() {
{
let timers = getTimers();
for (let timer of timers) {
// Print info about unexpected startup timers to help debugging.
info(`${timer.name}: ${timer.delay}ms, ${timer.type}`);
}
Assert.equal(
timers.length,
0,
"there should be no timer at xpcshell startup"
);
}
let timerData = [
["t1", 500, Ci.nsITimer.TYPE_ONE_SHOT],
["t2", 1500, Ci.nsITimer.TYPE_REPEATING_SLACK],
["t3", 2500, Ci.nsITimer.TYPE_REPEATING_PRECISE],
["t4", 3500, Ci.nsITimer.TYPE_REPEATING_PRECISE_CAN_SKIP],
["t5", 5500, Ci.nsITimer.TYPE_REPEATING_SLACK_LOW_PRIORITY],
["t6", 7500, Ci.nsITimer.TYPE_ONE_SHOT_LOW_PRIORITY],
];
info("Add timers one at a time.");
for (let [name, delay, type] of timerData) {
let timer = newTimer(name, delay, type);
let timers = getTimers();
Assert.equal(timers.length, 1, "there should be only one timer");
Assert.equal(name, timers[0].name, "the name is correct");
Assert.equal(delay, timers[0].delay, "the delay is correct");
Assert.equal(type, timers[0].type, "the type is correct");
timer.cancel();
Assert.equal(getTimers().length, 0, "no timer left after cancelling");
}
info("Add all timers at once.");
let timers = [];
for (let [name, delay, type] of timerData) {
timers.push(newTimer(name, delay, type));
}
while (timers.length) {
Assert.equal(getTimers().length, timers.length, "correct timer count");
timers.pop().cancel();
}
Assert.equal(getTimers().length, 0, "no timer left after cancelling");
}

Просмотреть файл

@ -22,6 +22,7 @@ fail-if = os == "android"
[test_debugger_malloc_size_of.js]
[test_file_createUnique.js]
[test_file_equality.js]
[test_getTimers.js]
[test_hidden_files.js]
[test_home.js]
# Bug 676998: test fails consistently on Android

Просмотреть файл

@ -1363,3 +1363,121 @@ void TimerThread::PrintStatistics() const {
mTotalEarlyWakeupTime / mEarlyWakeups);
}
#endif
/* This nsReadOnlyTimer class is used for the values returned by the
* TimerThread::GetTimers method.
* It is not possible to return a strong reference to the nsTimerImpl
* instance (that could extend the lifetime of the timer and cause it to fire
* a callback pointing to already freed memory) or a weak reference
* (nsSupportsWeakReference doesn't support freeing the referee on a thread
* that isn't the thread that owns the weak reference), so instead the timer
* name, delay and type are copied to a new object. */
class nsReadOnlyTimer final : public nsITimer {
public:
explicit nsReadOnlyTimer(const nsACString& aName, uint32_t aDelay,
uint32_t aType)
: mName(aName), mDelay(aDelay), mType(aType) {}
NS_DECL_ISUPPORTS
NS_IMETHOD Init(nsIObserver* aObserver, uint32_t aDelayInMs,
uint32_t aType) override {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHOD InitWithCallback(nsITimerCallback* aCallback, uint32_t aDelayInMs,
uint32_t aType) override {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHOD InitHighResolutionWithCallback(nsITimerCallback* aCallback,
const mozilla::TimeDuration& aDelay,
uint32_t aType) override {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHOD Cancel(void) override { return NS_ERROR_NOT_IMPLEMENTED; }
NS_IMETHOD InitWithNamedFuncCallback(nsTimerCallbackFunc aCallback,
void* aClosure, uint32_t aDelay,
uint32_t aType,
const char* aName) override {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHOD InitHighResolutionWithNamedFuncCallback(
nsTimerCallbackFunc aCallback, void* aClosure,
const mozilla::TimeDuration& aDelay, uint32_t aType,
const char* aName) override {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHOD GetName(nsACString& aName) override {
aName = mName;
return NS_OK;
}
NS_IMETHOD GetDelay(uint32_t* aDelay) override {
*aDelay = mDelay;
return NS_OK;
}
NS_IMETHOD SetDelay(uint32_t aDelay) override {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHOD GetType(uint32_t* aType) override {
*aType = mType;
return NS_OK;
}
NS_IMETHOD SetType(uint32_t aType) override {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHOD GetClosure(void** aClosure) override {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHOD GetCallback(nsITimerCallback** aCallback) override {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHOD GetTarget(nsIEventTarget** aTarget) override {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHOD SetTarget(nsIEventTarget* aTarget) override {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHOD GetAllowedEarlyFiringMicroseconds(
uint32_t* aAllowedEarlyFiringMicroseconds) override {
return NS_ERROR_NOT_IMPLEMENTED;
}
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) override {
return sizeof(*this);
}
private:
nsCString mName;
uint32_t mDelay;
uint32_t mType;
~nsReadOnlyTimer() = default;
};
NS_IMPL_ISUPPORTS(nsReadOnlyTimer, nsITimer)
nsresult TimerThread::GetTimers(nsTArray<RefPtr<nsITimer>>& aRetVal) {
nsTArray<RefPtr<nsTimerImpl>> timers;
{
MonitorAutoLock lock(mMonitor);
for (const auto& entry : mTimers) {
nsTimerImpl* timer = entry.Value();
if (!timer) {
continue;
}
timers.AppendElement(timer);
}
}
for (nsTimerImpl* timer : timers) {
nsAutoCString name;
timer->GetName(name);
uint32_t delay;
timer->GetDelay(&delay);
uint32_t type;
timer->GetType(&type);
aRetVal.AppendElement(new nsReadOnlyTimer(name, delay, type));
}
return NS_OK;
}

Просмотреть файл

@ -64,6 +64,7 @@ class TimerThread final : public mozilla::Runnable, public nsIObserver {
bool IsOnTimerThread() const { return mThread->IsOnCurrentThread(); }
uint32_t AllowedEarlyFiringMicroseconds();
nsresult GetTimers(nsTArray<RefPtr<nsITimer>>& aRetVal);
private:
~TimerThread();

Просмотреть файл

@ -20,4 +20,10 @@ Classes = [
'headers': ['/xpcom/threads/nsTimerImpl.h'],
'processes': ProcessSelector.ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS,
},
{
'cid': '{d39a8904-2e09-4a3a-a273-c3bec7db2bfe}',
'contract_ids': ['@mozilla.org/timer-manager;1'],
'headers': ['/xpcom/threads/nsTimerImpl.h'],
'type': 'nsTimerManager',
},
]

Просмотреть файл

@ -237,6 +237,8 @@ interface nsITimer : nsISupports
*/
attribute nsIEventTarget target;
readonly attribute ACString name;
/**
* The number of microseconds this nsITimer implementation can possibly
* fire early.
@ -361,3 +363,14 @@ NS_NewTimerWithFuncCallback(nsTimerCallbackFunc aCallback,
}
#endif
%}
[scriptable, builtinclass, uuid(5482506d-1d21-4d08-b01c-95c87e1295ad)]
interface nsITimerManager : nsISupports
{
/**
* Returns a read-only list of nsITimer objects, implementing only the name,
* delay and type attribute getters.
* This is meant to be used for tests, to verify that no timer is leftover
* at the end of a test. */
Array<nsITimer> getTimers();
};

Просмотреть файл

@ -56,6 +56,7 @@ class TimerThreadWrapper {
TimeStamp FindNextFireTimeForCurrentThread(TimeStamp aDefault,
uint32_t aSearchBound);
uint32_t AllowedEarlyFiringMicroseconds();
nsresult GetTimers(nsTArray<RefPtr<nsITimer>>& aRetVal);
private:
static mozilla::StaticMutex sMutex;
@ -124,6 +125,18 @@ uint32_t TimerThreadWrapper::AllowedEarlyFiringMicroseconds() {
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.
@ -337,6 +350,12 @@ static void myNS_MeanAndStdDev(double n, double sumOfValues,
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;
@ -772,9 +791,10 @@ void nsTimerImpl::GetName(nsACString& aName,
[&](const ClosureCallback& c) { aName.Assign(c.mName); });
}
void nsTimerImpl::GetName(nsACString& aName) {
nsresult nsTimerImpl::GetName(nsACString& aName) {
MutexAutoLock lock(mMutex);
GetName(aName, lock);
return NS_OK;
}
nsTimer::~nsTimer() = default;

Просмотреть файл

@ -134,8 +134,6 @@ class nsTimerImpl {
void GetName(nsACString& aName, const mozilla::MutexAutoLock& aProofOfLock)
MOZ_REQUIRES(mMutex);
void GetName(nsACString& aName);
bool IsInTimerThread() const { return mIsInTimerThread; }
void SetIsInTimerThread(bool aIsInTimerThread) {
mIsInTimerThread = aIsInTimerThread;
@ -222,4 +220,12 @@ class nsTimer final : public nsITimer {
RefPtr<nsTimerImpl> mImpl;
};
class nsTimerManager final : public nsITimerManager {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSITIMERMANAGER
private:
~nsTimerManager() = default;
};
#endif /* nsTimerImpl_h___ */