/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* 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/. */
class AutoCreateAndDestroyReentrantMonitor { public:
AutoCreateAndDestroyReentrantMonitor() {
mReentrantMonitor = new ReentrantMonitor("TestTimers::AutoMon");
MOZ_RELEASE_ASSERT(mReentrantMonitor, "Out of memory!");
}
NS_IMETHOD Notify(nsITimer* aTimer) override {
MOZ_RELEASE_ASSERT(mThreadPtr, "Callback was not supposed to be called!");
nsCOMPtr<nsIThread> current(do_GetCurrentThread());
// We do not use nsITimerCallback, because that results in a circular // reference. One of the properties we want from TimerHelper is for the // timer to be canceled when it goes out of scope. void Notify() {
MonitorAutoLock lock(mMonitor);
EXPECT_TRUE(mTarget->IsOnCurrentThread());
TimeDuration elapsed = TimeStamp::Now() - mStart;
mStart = TimeStamp::Now();
mLastDelay = Some(elapsed.ToMilliseconds()); if (mBlockTime) {
PR_Sleep(mBlockTime);
}
mMonitor.Notify();
}
// Waits for callback, and if it occurs within the limit, causes the callback // to block for the specified time. Useful for testing cases where the // callback takes a long time to return.
Maybe<uint32_t> WaitAndBlockCallback(uint32_t aLimitMs, uint32_t aBlockTime) {
MonitorAutoLock lock(mMonitor);
mBlockTime = aBlockTime;
TimeStamp start = TimeStamp::Now(); while (!mLastDelay.isSome()) {
mMonitor.Wait(TimeDuration::FromMilliseconds(aLimitMs));
TimeDuration elapsed = TimeStamp::Now() - start;
uint32_t elapsedMs = static_cast<uint32_t>(elapsed.ToMilliseconds()); if (elapsedMs >= aLimitMs) { break;
}
aLimitMs -= elapsedMs;
start = TimeStamp::Now();
}
mBlockTime = 0; return std::move(mLastDelay);
}
#ifdef XP_MACOSX // For some reason, our OS X testers fire timed condition waits _extremely_ // late (as much as 200ms). // See https://bugzilla.mozilla.org/show_bug.cgi?id=1726915 constunsigned kSlowdownFactor = 50; #elif XP_WIN // Windows also needs some extra leniency, but not nearly as much as our OS X // testers // See https://bugzilla.mozilla.org/show_bug.cgi?id=1729035 constunsigned kSlowdownFactor = 5; #else constunsigned kSlowdownFactor = 1; #endif
TEST_F(SimpleTimerTest, OneShot) { auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_ONE_SHOT); auto res = timer->Wait(110 * kSlowdownFactor);
ASSERT_TRUE(res.isSome());
ASSERT_LT(*res, 110U * kSlowdownFactor);
ASSERT_GT(*res, 95U * kSlowdownFactor);
}
TEST_F(SimpleTimerTest, TimerWithStoppedTarget) {
mThread->Shutdown(); auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_ONE_SHOT); auto res = timer->Wait(110 * kSlowdownFactor);
ASSERT_FALSE(res.isSome());
}
TEST_F(SimpleTimerTest, SlackRepeating) { auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_REPEATING_SLACK); auto delay =
timer->WaitAndBlockCallback(110 * kSlowdownFactor, 50 * kSlowdownFactor);
ASSERT_TRUE(delay.isSome());
ASSERT_LT(*delay, 110U * kSlowdownFactor);
ASSERT_GT(*delay, 95U * kSlowdownFactor); // REPEATING_SLACK timers re-schedule with the full duration when the timer // callback completes
// Delays smaller than the timer's period do not effect the period.
delay = timer->Wait(110 * kSlowdownFactor);
ASSERT_TRUE(delay.isSome());
ASSERT_LT(*delay, 110U * kSlowdownFactor);
ASSERT_GT(*delay, 95U * kSlowdownFactor);
// Delays larger than the timer's period should result in the skipping of // firings, but the cadence should remain the same.
delay =
timer->WaitAndBlockCallback(110 * kSlowdownFactor, 150 * kSlowdownFactor);
ASSERT_TRUE(delay.isSome());
ASSERT_LT(*delay, 110U * kSlowdownFactor);
ASSERT_GT(*delay, 95U * kSlowdownFactor);
// gtest on 32bit Win7 debug build is unstable and somehow this test // makes it even worse. #if !defined(XP_WIN) || !defined(DEBUG) || defined(HAVE_64BIT_BUILD)
class FindExpirationTimeState final { public: // We'll offset the timers 10 seconds into the future to assure that they // won't fire const uint32_t kTimerOffset = 10 * 1000; // And we'll set the timers spaced by 5 seconds. const uint32_t kTimerInterval = 5 * 1000; // We'll use 20 timers const uint32_t kNumTimers = 20;
TimeStamp mBefore;
TimeStamp mMiddle;
std::list<nsCOMPtr<nsITimer>> mTimers;
~FindExpirationTimeState() { while (!mTimers.empty()) {
nsCOMPtr<nsITimer> t = mTimers.front().get();
mTimers.pop_front();
t->Cancel();
}
}
// Create timers, with aNumLowPriority low priority timers first in the queue void InitTimers(uint32_t aNumLowPriority, uint32_t aType) { // aType is just for readability.
MOZ_RELEASE_ASSERT(aType == nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY);
InitTimers(aNumLowPriority, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, nullptr);
}
// Create timers, with aNumDifferentTarget timers with target aTarget first in // the queue void InitTimers(uint32_t aNumDifferentTarget, nsIEventTarget* aTarget) {
InitTimers(aNumDifferentTarget, nsITimer::TYPE_ONE_SHOT, aTarget);
}
// NS_GetTimerDeadlineHintOnCurrentThread returns clearUntil if there are // no pending timers before clearUntil. // Passing 0 ensures that we examine the next timer to fire, regardless // of its thread target. This is important, because lots of the checks // we perform in the test get confused by timers targeted at other // threads.
TimeStamp t = NS_GetTimerDeadlineHintOnCurrentThread(clearUntil, 0); if (t >= clearUntil) { break;
}
// Clear whatever random timers there might be pending.
uint32_t waitTime = 10; if (t > TimeStamp::Now()) {
waitTime = uint32_t((t - TimeStamp::Now()).ToMilliseconds());
}
PR_Sleep(PR_MillisecondsToInterval(waitTime));
} while (true);
mBefore = TimeStamp::Now(); // To avoid getting exactly the same time for a timer and mMiddle, subtract // 50 ms.
mMiddle = mBefore +
TimeDuration::FromMilliseconds(kTimerOffset +
kTimerInterval * kNumTimers / 2) -
TimeDuration::FromMilliseconds(50); for (uint32_t i = 0; i < kNumTimers; ++i) {
nsCOMPtr<nsITimer> timer = NS_NewTimer();
ASSERT_TRUE(timer);
if (i < aNumDifferingTimers) { if (aTarget) {
timer->SetTarget(aTarget);
}
TimeStamp t;
t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_EQ(t, before) << "Found time should be equal to default";
t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_EQ(t, before) << "Found time should be equal to default";
t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_LT(t, middle) << "Found time should be less than default";
t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_LT(t, middle) << "Found time should be less than default";
t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_LT(t, middle) << "Found time should be less than default";
}
TimeStamp t;
t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_EQ(t, before) << "Found time should be equal to default";
t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_EQ(t, before) << "Found time should be equal to default";
t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_LT(t, middle) << "Found time should be less than default";
t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_LT(t, middle) << "Found time should be less than default";
t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_LT(t, middle) << "Found time should be less than default";
}
TimeStamp t;
t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_EQ(t, before) << "Found time should be equal to default";
t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_EQ(t, before) << "Found time should be equal to default";
t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_LT(t, middle) << "Found time should be equal to default";
t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_EQ(t, middle) << "Found time should be equal to default";
t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_EQ(t, middle) << "Found time should be equal to default";
}
{
AutoTestThread testThread;
FindExpirationTimeState state; // 5 other targets
state.InitTimers(5, static_cast<nsIEventTarget*>(testThread));
TimeStamp before = state.mBefore;
TimeStamp middle = state.mMiddle;
TimeStamp t;
t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_EQ(t, before) << "Found time should be equal to default";
t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_EQ(t, before) << "Found time should be equal to default";
t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_LT(t, middle) << "Found time should be less than default";
t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_LT(t, middle) << "Found time should be less than default";
t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_LT(t, middle) << "Found time should be less than default";
}
{
AutoTestThread testThread;
FindExpirationTimeState state; // 15 other targets
state.InitTimers(15, static_cast<nsIEventTarget*>(testThread));
TimeStamp before = state.mBefore;
TimeStamp middle = state.mMiddle;
TimeStamp t;
t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_EQ(t, before) << "Found time should be equal to default";
t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_EQ(t, before) << "Found time should be equal to default";
t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_LT(t, middle) << "Found time should be less than default";
t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_EQ(t, middle) << "Found time should be equal to default";
t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20);
EXPECT_TRUE(t) << "We should find a time";
EXPECT_EQ(t, middle) << "Found time should be equal to default";
}
}
#endif
// Do these _after_ FindExpirationTime; apparently pausing the timer thread // schedules minute-long timers, which FindExpirationTime waits out before // starting.
TEST_F(SimpleTimerTest, SleepWakeOneShot) { if (StaticPrefs::timer_ignore_sleep_wake_notifications()) { return;
} auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_ONE_SHOT);
PauseTimerThread(); auto delay = timer->Wait(200 * kSlowdownFactor);
ResumeTimerThread();
ASSERT_FALSE(delay.isSome());
}
TEST_F(SimpleTimerTest, SleepWakeRepeatingSlack) { if (StaticPrefs::timer_ignore_sleep_wake_notifications()) { return;
} auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_REPEATING_SLACK);
PauseTimerThread(); auto delay = timer->Wait(200 * kSlowdownFactor);
ResumeTimerThread();
ASSERT_FALSE(delay.isSome());
// Timer thread slept for ~200ms, longer than the duration of the timer, so // it should fire pretty much immediately.
delay = timer->Wait(10 * kSlowdownFactor);
ASSERT_TRUE(delay.isSome());
ASSERT_LT(*delay, 210 * kSlowdownFactor);
ASSERT_GT(*delay, 199 * kSlowdownFactor);
// Timer thread only slept for ~50 ms, shorter than the duration of the // timer, so there should be no effect on the timing.
delay = timer->Wait(110 * kSlowdownFactor);
ASSERT_TRUE(delay.isSome());
ASSERT_LT(*delay, 110U * kSlowdownFactor);
ASSERT_GT(*delay, 95U * kSlowdownFactor);
}
TEST_F(SimpleTimerTest, SleepWakeRepeatingPrecise) { if (StaticPrefs::timer_ignore_sleep_wake_notifications()) { return;
} auto timer = MakeTimer(100 * kSlowdownFactor,
nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP);
PauseTimerThread(); auto delay = timer->Wait(350 * kSlowdownFactor);
ResumeTimerThread();
ASSERT_FALSE(delay.isSome());
// Timer thread slept longer than the duration of the timer, so it should // fire pretty much immediately.
delay = timer->Wait(10 * kSlowdownFactor);
ASSERT_TRUE(delay.isSome());
ASSERT_LT(*delay, 360U * kSlowdownFactor);
ASSERT_GT(*delay, 349U * kSlowdownFactor);
// After that, we should get back on our original cadence
delay = timer->Wait(110 * kSlowdownFactor);
ASSERT_TRUE(delay.isSome());
ASSERT_LT(*delay, 60U * kSlowdownFactor);
ASSERT_GT(*delay, 45U * kSlowdownFactor);
// Timer thread only slept for ~50 ms, shorter than the duration of the // timer, so there should be no effect on the timing.
delay = timer->Wait(110 * kSlowdownFactor);
ASSERT_TRUE(delay.isSome());
ASSERT_LT(*delay, 110U * kSlowdownFactor);
ASSERT_GT(*delay, 95U * kSlowdownFactor);
}
#define FUZZ_MAX_TIMEOUT 9 class FuzzTestThreadState final : public nsITimerCallback { public:
NS_DECL_THREADSAFE_ISUPPORTS
class StartRunnable final : public mozilla::Runnable { public: explicit StartRunnable(FuzzTestThreadState* threadState)
: mozilla::Runnable("FuzzTestThreadState::StartRunnable"),
mThreadState(threadState) {}
private:
~FuzzTestThreadState() { for (size_t i = 0; i <= FUZZ_MAX_TIMEOUT; ++i) {
MOZ_RELEASE_ASSERT(mOneShotTimersByDelay[i].empty(), "Timers remain at end of test.");
}
}
void RescheduleSomeTimers() { if (mStopped) { return;
}
staticconst size_t kNumRescheduled = 40;
// Reschedule some timers with a Cancel first. for (size_t i = 0; i < kNumRescheduled; ++i) {
InitRandomTimer(CancelRandomTimer().get());
} // Reschedule some timers without a Cancel first. for (size_t i = 0; i < kNumRescheduled; ++i) {
InitRandomTimer(RemoveRandomTimer().get());
}
}
for (auto it = mOneShotTimersByDelay[delayToRemove].begin();
it != mOneShotTimersByDelay[delayToRemove].end(); ++it) { if (indexToRemove) {
--indexToRemove; continue;
}
void CancelRepeatingTimer(nsITimer* aTimer) { for (auto it = mRepeatingTimers.begin(); it != mRepeatingTimers.end();
++it) { if (it->get() == aTimer) {
mRepeatingTimers.erase(it);
aTimer->Cancel();
--mTimersOutstanding; return;
}
}
}
nsCOMPtr<nsIThread> mThread; // Scheduled timers, indexed by delay between 0-9 ms, in lists // with most recently scheduled last.
std::list<nsCOMPtr<nsITimer>> mOneShotTimersByDelay[FUZZ_MAX_TIMEOUT + 1];
std::vector<nsCOMPtr<nsITimer>> mRepeatingTimers;
Atomic<bool> mStopped;
Atomic<size_t> mTimersOutstanding;
};
for (size_t i = 0; i < kNumThreads; ++i) {
threadStates[i] = new FuzzTestThreadState(&*threads[i]);
threadStates[i]->Start();
}
PR_Sleep(PR_MillisecondsToInterval(20000));
for (size_t i = 0; i < kNumThreads; ++i) {
threadStates[i]->Stop();
}
// Wait at most 10 seconds for all outstanding timers to pop
PRIntervalTime start = PR_IntervalNow(); for (auto& threadState : threadStates) { while (threadState->HasTimersOutstanding()) {
uint32_t elapsedMs = PR_IntervalToMilliseconds(PR_IntervalNow() - start);
ASSERT_LE(elapsedMs, uint32_t(10000))
<< "Timed out waiting for all timers to pop";
PR_Sleep(PR_MillisecondsToInterval(10));
}
}
}
// Reverse order, since if the timers are not high-res we'd end up // out-of-order.
MOZ_ALWAYS_SUCCEEDS(t3->InitHighResolutionWithNamedFuncCallback(
&SetTime, &third, TimeDuration::FromMicroseconds(300),
nsITimer::TYPE_ONE_SHOT, "TestTimers::HighResFuncCallback::third"));
MOZ_ALWAYS_SUCCEEDS(t2->InitHighResolutionWithNamedFuncCallback(
&SetTime, &second, TimeDuration::FromMicroseconds(200),
nsITimer::TYPE_ONE_SHOT, "TestTimers::HighResFuncCallback::second"));
MOZ_ALWAYS_SUCCEEDS(t1->InitHighResolutionWithNamedFuncCallback(
&SetTime, &first, TimeDuration::FromMicroseconds(100),
nsITimer::TYPE_ONE_SHOT, "TestTimers::HighResFuncCallback::first"));
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.