From 6e85d7f4356b112fb9e2258c7249b5a02f97e6b8 Mon Sep 17 00:00:00 2001 From: "Byron Campen [:bwc]" Date: Wed, 29 Jul 2015 11:16:14 -0500 Subject: [PATCH] Bug 1059572 - Part 0: Fuzz test for timers. r=nfroyd --- xpcom/tests/TestTimers.cpp | 307 ++++++++++++++++++++++++++++++++++++- 1 file changed, 306 insertions(+), 1 deletion(-) diff --git a/xpcom/tests/TestTimers.cpp b/xpcom/tests/TestTimers.cpp index 29534d2d036..c5ced993eb1 100644 --- a/xpcom/tests/TestTimers.cpp +++ b/xpcom/tests/TestTimers.cpp @@ -18,6 +18,11 @@ #include "mozilla/Attributes.h" #include "mozilla/ReentrantMonitor.h" + +#include +#include +#include + using namespace mozilla; typedef nsresult(*TestFuncPtr)(); @@ -168,6 +173,305 @@ TestTimerWithStoppedTarget() return NS_OK; } +#define FUZZ_MAX_TIMEOUT 9 +class FuzzTestThreadState final : public nsITimerCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit FuzzTestThreadState(nsIThread* thread) : + mThread(thread), + mStopped(false) + {} + + class StartRunnable final : public nsRunnable { + public: + explicit StartRunnable(FuzzTestThreadState* threadState) : + mThreadState(threadState) + {} + + NS_IMETHOD Run() override + { + mThreadState->ScheduleOrCancelTimers(); + return NS_OK; + } + + private: + nsRefPtr mThreadState; + }; + + void Start() + { + nsCOMPtr runnable = new StartRunnable(this); + nsresult rv = mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Failed to dispatch StartRunnable."); + } + } + + void Stop() + { + mStopped = true; + } + + NS_IMETHOD Notify(nsITimer* aTimer) override + { + bool onCorrectThread; + nsresult rv = mThread->IsOnCurrentThread(&onCorrectThread); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to perform thread check."); + MOZ_ASSERT(onCorrectThread, "Notify invoked on wrong thread."); + + uint32_t delay; + rv = aTimer->GetDelay(&delay); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "GetDelay failed."); + return rv; + } + + if (delay > FUZZ_MAX_TIMEOUT) { + MOZ_ASSERT(false, "Delay was an invalid value for this test."); + return NS_ERROR_FAILURE; + } + + uint32_t type; + rv = aTimer->GetType(&type); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to get timer type."); + MOZ_ASSERT(type <= nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP); + + if (type == nsITimer::TYPE_ONE_SHOT) { + if (mOneShotTimersByDelay[delay].empty()) { + MOZ_ASSERT(false, "Unexpected one-shot timer."); + return NS_ERROR_FAILURE; + } + + if (mOneShotTimersByDelay[delay].front().get() != aTimer) { + MOZ_ASSERT(false, + "One-shot timers for a given duration have been reordered."); + return NS_ERROR_FAILURE; + } + + mOneShotTimersByDelay[delay].pop_front(); + --mTimersOutstanding; + } else if (mStopped) { + CancelRepeatingTimer(aTimer); + } + + ScheduleOrCancelTimers(); + RescheduleSomeTimers(); + return NS_OK; + } + + bool HasTimersOutstanding() const + { + return !!mTimersOutstanding; + } + + private: + ~FuzzTestThreadState() + { + for (size_t i = 0; i <= FUZZ_MAX_TIMEOUT; ++i) { + MOZ_ASSERT(mOneShotTimersByDelay[i].empty(), + "Timers remain at end of test."); + } + } + + uint32_t GetRandomType() const + { + return rand() % (nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP + 1); + } + + size_t CountOneShotTimers() const + { + size_t count = 0; + for (size_t i = 0; i <= FUZZ_MAX_TIMEOUT; ++i) { + count += mOneShotTimersByDelay[i].size(); + } + return count; + } + + void ScheduleOrCancelTimers() + { + if (mStopped) { + return; + } + + const size_t numTimersDesired = (rand() % 100) + 100; + MOZ_ASSERT(numTimersDesired >= 100); + MOZ_ASSERT(numTimersDesired < 200); + int adjustment = numTimersDesired - mTimersOutstanding; + + while (adjustment > 0) { + CreateRandomTimer(); + --adjustment; + } + + while (adjustment < 0) { + CancelRandomTimer(); + ++adjustment; + } + + MOZ_ASSERT(numTimersDesired == mTimersOutstanding); + } + + void RescheduleSomeTimers() + { + if (mStopped) { + return; + } + + static const 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()); + } + } + + void CreateRandomTimer() + { + nsresult rv; + nsCOMPtr timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Failed to create timer."); + return; + } + + rv = timer->SetTarget(static_cast(mThread.get())); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Failed to set target."); + return; + } + + InitRandomTimer(timer.get()); + } + + nsCOMPtr CancelRandomTimer() + { + nsCOMPtr timer(RemoveRandomTimer()); + timer->Cancel(); + return timer; + } + + nsCOMPtr RemoveRandomTimer() + { + MOZ_ASSERT(mTimersOutstanding); + + if ((GetRandomType() == nsITimer::TYPE_ONE_SHOT && CountOneShotTimers()) + || mRepeatingTimers.empty()) { + uint32_t delayToRemove = rand() % (FUZZ_MAX_TIMEOUT + 1); + while (mOneShotTimersByDelay[delayToRemove].empty()) { + // ++delayToRemove mod FUZZ_MAX_TIMEOUT + 1 + delayToRemove = (delayToRemove + 1) % (FUZZ_MAX_TIMEOUT + 1); + } + + uint32_t indexToRemove = + rand() % mOneShotTimersByDelay[delayToRemove].size(); + + for (auto it = mOneShotTimersByDelay[delayToRemove].begin(); + it != mOneShotTimersByDelay[delayToRemove].end(); + ++it) { + if (indexToRemove) { + --indexToRemove; + continue; + } + + nsCOMPtr removed = *it; + mOneShotTimersByDelay[delayToRemove].erase(it); + --mTimersOutstanding; + return removed; + } + } else { + size_t indexToRemove = rand() % mRepeatingTimers.size(); + nsCOMPtr removed(mRepeatingTimers[indexToRemove]); + mRepeatingTimers.erase(mRepeatingTimers.begin() + indexToRemove); + --mTimersOutstanding; + return removed; + } + + MOZ_ASSERT_UNREACHABLE("Unable to remove a timer"); + return nullptr; + } + + void InitRandomTimer(nsITimer* aTimer) + { + // Between 0 and FUZZ_MAX_TIMEOUT + uint32_t delay = rand() % (FUZZ_MAX_TIMEOUT + 1); + uint32_t type = GetRandomType(); + nsresult rv = aTimer->InitWithCallback(this, delay, type); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Failed to set timer."); + return; + } + + if (type == nsITimer::TYPE_ONE_SHOT) { + mOneShotTimersByDelay[delay].push_back(aTimer); + } else { + mRepeatingTimers.push_back(aTimer); + } + ++mTimersOutstanding; + } + + 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 mThread; + // Scheduled timers, indexed by delay between 0-9 ms, in lists + // with most recently scheduled last. + std::list> mOneShotTimersByDelay[FUZZ_MAX_TIMEOUT + 1]; + std::vector> mRepeatingTimers; + Atomic mStopped; + Atomic mTimersOutstanding; +}; + +NS_IMPL_ISUPPORTS(FuzzTestThreadState, nsITimerCallback) + +nsresult +FuzzTestTimers() +{ + static const size_t kNumThreads(10); + AutoTestThread threads[kNumThreads]; + nsRefPtr threadStates[kNumThreads]; + + 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()) { + if (PR_IntervalToMilliseconds(PR_IntervalNow() - start) > 10000) { + MOZ_ASSERT(false, "Timed out waiting for all timers to pop"); + return NS_ERROR_FAILURE; + } + PR_Sleep(PR_MillisecondsToInterval(10)); + } + } + + return NS_OK; +} + int main(int argc, char** argv) { ScopedXPCOM xpcom("TestTimers"); @@ -175,7 +479,8 @@ int main(int argc, char** argv) static TestFuncPtr testsToRun[] = { TestTargetedTimers, - TestTimerWithStoppedTarget + TestTimerWithStoppedTarget, + FuzzTestTimers }; static uint32_t testCount = sizeof(testsToRun) / sizeof(testsToRun[0]);