/src/mozilla-central/xpcom/tests/gtest/TestTimers.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "nsIThread.h" |
7 | | #include "nsITimer.h" |
8 | | |
9 | | #include "nsCOMPtr.h" |
10 | | #include "nsComponentManagerUtils.h" |
11 | | #include "nsServiceManagerUtils.h" |
12 | | #include "nsThreadUtils.h" |
13 | | #include "prinrval.h" |
14 | | #include "prmon.h" |
15 | | #include "prthread.h" |
16 | | #include "mozilla/Attributes.h" |
17 | | |
18 | | #include "mozilla/ReentrantMonitor.h" |
19 | | |
20 | | #include <list> |
21 | | #include <vector> |
22 | | |
23 | | #include "gtest/gtest.h" |
24 | | |
25 | | using namespace mozilla; |
26 | | |
27 | | typedef nsresult(*TestFuncPtr)(); |
28 | | |
29 | | class AutoTestThread |
30 | | { |
31 | | public: |
32 | 0 | AutoTestThread() { |
33 | 0 | nsCOMPtr<nsIThread> newThread; |
34 | 0 | nsresult rv = NS_NewNamedThread("AutoTestThread", getter_AddRefs(newThread)); |
35 | 0 | if (NS_FAILED(rv)) |
36 | 0 | return; |
37 | 0 | |
38 | 0 | newThread.swap(mThread); |
39 | 0 | } |
40 | | |
41 | 0 | ~AutoTestThread() { |
42 | 0 | mThread->Shutdown(); |
43 | 0 | } |
44 | | |
45 | 0 | operator nsIThread*() const { |
46 | 0 | return mThread; |
47 | 0 | } |
48 | | |
49 | 0 | nsIThread* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { |
50 | 0 | return mThread; |
51 | 0 | } |
52 | | |
53 | | private: |
54 | | nsCOMPtr<nsIThread> mThread; |
55 | | }; |
56 | | |
57 | | class AutoCreateAndDestroyReentrantMonitor |
58 | | { |
59 | | public: |
60 | 0 | AutoCreateAndDestroyReentrantMonitor() { |
61 | 0 | mReentrantMonitor = new ReentrantMonitor("TestTimers::AutoMon"); |
62 | 0 | MOZ_RELEASE_ASSERT(mReentrantMonitor, "Out of memory!"); |
63 | 0 | } |
64 | | |
65 | 0 | ~AutoCreateAndDestroyReentrantMonitor() { |
66 | 0 | delete mReentrantMonitor; |
67 | 0 | } |
68 | | |
69 | 0 | operator ReentrantMonitor* () const { |
70 | 0 | return mReentrantMonitor; |
71 | 0 | } |
72 | | |
73 | | private: |
74 | | ReentrantMonitor* mReentrantMonitor; |
75 | | }; |
76 | | |
77 | | class TimerCallback final : public nsITimerCallback |
78 | | { |
79 | | public: |
80 | | NS_DECL_THREADSAFE_ISUPPORTS |
81 | | |
82 | | TimerCallback(nsIThread** aThreadPtr, ReentrantMonitor* aReentrantMonitor) |
83 | 0 | : mThreadPtr(aThreadPtr), mReentrantMonitor(aReentrantMonitor) { } |
84 | | |
85 | 0 | NS_IMETHOD Notify(nsITimer* aTimer) override { |
86 | 0 | MOZ_RELEASE_ASSERT(mThreadPtr, "Callback was not supposed to be called!"); |
87 | 0 | nsCOMPtr<nsIThread> current(do_GetCurrentThread()); |
88 | 0 |
|
89 | 0 | ReentrantMonitorAutoEnter mon(*mReentrantMonitor); |
90 | 0 |
|
91 | 0 | MOZ_RELEASE_ASSERT(!*mThreadPtr, "Timer called back more than once!"); |
92 | 0 | *mThreadPtr = current; |
93 | 0 |
|
94 | 0 | mon.Notify(); |
95 | 0 |
|
96 | 0 | return NS_OK; |
97 | 0 | } |
98 | | private: |
99 | 0 | ~TimerCallback() {} |
100 | | |
101 | | nsIThread** mThreadPtr; |
102 | | ReentrantMonitor* mReentrantMonitor; |
103 | | }; |
104 | | |
105 | | NS_IMPL_ISUPPORTS(TimerCallback, nsITimerCallback) |
106 | | |
107 | | TEST(Timers, TargetedTimers) |
108 | 0 | { |
109 | 0 | AutoCreateAndDestroyReentrantMonitor newMon; |
110 | 0 | ASSERT_TRUE(newMon); |
111 | 0 |
|
112 | 0 | AutoTestThread testThread; |
113 | 0 | ASSERT_TRUE(testThread); |
114 | 0 |
|
115 | 0 | nsIThread* notifiedThread = nullptr; |
116 | 0 |
|
117 | 0 | nsCOMPtr<nsITimerCallback> callback = |
118 | 0 | new TimerCallback(¬ifiedThread, newMon); |
119 | 0 | ASSERT_TRUE(callback); |
120 | 0 |
|
121 | 0 | nsIEventTarget* target = static_cast<nsIEventTarget*>(testThread); |
122 | 0 |
|
123 | 0 | nsCOMPtr<nsITimer> timer; |
124 | 0 | nsresult rv = NS_NewTimerWithCallback(getter_AddRefs(timer), |
125 | 0 | callback, 2000, nsITimer::TYPE_ONE_SHOT, |
126 | 0 | target); |
127 | 0 | ASSERT_TRUE(NS_SUCCEEDED(rv)); |
128 | 0 |
|
129 | 0 | ReentrantMonitorAutoEnter mon(*newMon); |
130 | 0 | while (!notifiedThread) { |
131 | 0 | mon.Wait(); |
132 | 0 | } |
133 | 0 | ASSERT_EQ(notifiedThread, testThread); |
134 | 0 | } |
135 | | |
136 | | TEST(Timers, TimerWithStoppedTarget) |
137 | 0 | { |
138 | 0 | AutoTestThread testThread; |
139 | 0 | ASSERT_TRUE(testThread); |
140 | 0 |
|
141 | 0 | nsIEventTarget* target = static_cast<nsIEventTarget*>(testThread); |
142 | 0 |
|
143 | 0 | // If this is called, we'll assert |
144 | 0 | nsCOMPtr<nsITimerCallback> callback = |
145 | 0 | new TimerCallback(nullptr, nullptr); |
146 | 0 | ASSERT_TRUE(callback); |
147 | 0 |
|
148 | 0 | nsCOMPtr<nsITimer> timer; |
149 | 0 | nsresult rv = NS_NewTimerWithCallback(getter_AddRefs(timer), |
150 | 0 | callback, 100, nsITimer::TYPE_ONE_SHOT, |
151 | 0 | target); |
152 | 0 | ASSERT_TRUE(NS_SUCCEEDED(rv)); |
153 | 0 |
|
154 | 0 | testThread->Shutdown(); |
155 | 0 |
|
156 | 0 | PR_Sleep(400); |
157 | 0 | } |
158 | | |
159 | | // gtest on 32bit Win7 debug build is unstable and somehow this test |
160 | | // makes it even worse. |
161 | | #if !defined(XP_WIN) || !defined(DEBUG) || defined(HAVE_64BIT_BUILD) |
162 | | |
163 | | class FindExpirationTimeState final |
164 | | { |
165 | | public: |
166 | | // We'll offset the timers 10 seconds into the future to assure that they won't fire |
167 | | const uint32_t kTimerOffset = 10 * 1000; |
168 | | // And we'll set the timers spaced by 5 seconds. |
169 | | const uint32_t kTimerInterval = 5 * 1000; |
170 | | // We'll use 20 timers |
171 | | const uint32_t kNumTimers = 20; |
172 | | |
173 | | TimeStamp mBefore; |
174 | | TimeStamp mMiddle; |
175 | | |
176 | | std::list<nsCOMPtr<nsITimer>> mTimers; |
177 | | |
178 | | ~FindExpirationTimeState() |
179 | 0 | { |
180 | 0 | while (!mTimers.empty()) { |
181 | 0 | nsCOMPtr<nsITimer> t = mTimers.front().get(); |
182 | 0 | mTimers.pop_front(); |
183 | 0 | t->Cancel(); |
184 | 0 | } |
185 | 0 | } |
186 | | |
187 | | // Create timers, with aNumLowPriority low priority timers first in the queue |
188 | | void InitTimers(uint32_t aNumLowPriority, uint32_t aType) |
189 | 0 | { |
190 | 0 | // aType is just for readability. |
191 | 0 | MOZ_ASSERT(aType == nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY); |
192 | 0 | InitTimers(aNumLowPriority, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, nullptr); |
193 | 0 | } |
194 | | |
195 | | // Create timers, with aNumDifferentTarget timers with target aTarget first in the queue |
196 | | void InitTimers(uint32_t aNumDifferentTarget, nsIEventTarget* aTarget) |
197 | 0 | { |
198 | 0 | InitTimers(aNumDifferentTarget, nsITimer::TYPE_ONE_SHOT, aTarget); |
199 | 0 | } |
200 | | |
201 | | void InitTimers(uint32_t aNumDifferingTimers, uint32_t aType, nsIEventTarget* aTarget) |
202 | 0 | { |
203 | 0 | do { |
204 | 0 | TimeStamp clearUntil = |
205 | 0 | TimeStamp::Now() + |
206 | 0 | TimeDuration::FromMilliseconds(kTimerOffset + |
207 | 0 | kNumTimers * kTimerInterval); |
208 | 0 |
|
209 | 0 | // NS_GetTimerDeadlineHintOnCurrentThread returns clearUntil if there are |
210 | 0 | // no pending timers before clearUntil. |
211 | 0 | TimeStamp t = NS_GetTimerDeadlineHintOnCurrentThread(clearUntil, 100); |
212 | 0 | if (t >= clearUntil) { |
213 | 0 | break; |
214 | 0 | } |
215 | 0 | |
216 | 0 | // Clear whatever random timers there might be pending. |
217 | 0 | uint32_t waitTime = 10; |
218 | 0 | if (t > TimeStamp::Now()) { |
219 | 0 | waitTime = uint32_t((t - TimeStamp::Now()).ToMilliseconds()); |
220 | 0 | } |
221 | 0 | PR_Sleep(PR_MillisecondsToInterval(waitTime)); |
222 | 0 | } while(true); |
223 | 0 |
|
224 | 0 | mBefore = TimeStamp::Now(); |
225 | 0 | mMiddle = mBefore + TimeDuration::FromMilliseconds( |
226 | 0 | kTimerOffset + kTimerInterval * kNumTimers / 2); |
227 | 0 | for (uint32_t i = 0; i < kNumTimers; ++i) { |
228 | 0 | nsCOMPtr<nsITimer> timer = NS_NewTimer(); |
229 | 0 | ASSERT_TRUE(timer); |
230 | 0 |
|
231 | 0 | if (i < aNumDifferingTimers) { |
232 | 0 | if (aTarget) { |
233 | 0 | timer->SetTarget(aTarget); |
234 | 0 | } |
235 | 0 |
|
236 | 0 | timer->InitWithNamedFuncCallback(&UnusedCallbackFunc, |
237 | 0 | nullptr, |
238 | 0 | kTimerOffset + kTimerInterval * i, |
239 | 0 | aType, |
240 | 0 | "FindExpirationTimeState::InitTimers"); |
241 | 0 | } else { |
242 | 0 | timer->InitWithNamedFuncCallback(&UnusedCallbackFunc, |
243 | 0 | nullptr, |
244 | 0 | kTimerOffset + kTimerInterval * i, |
245 | 0 | nsITimer::TYPE_ONE_SHOT, |
246 | 0 | "FindExpirationTimeState::InitTimers"); |
247 | 0 | } |
248 | 0 | mTimers.push_front(timer.get()); |
249 | 0 | } |
250 | 0 | } |
251 | | |
252 | | static void UnusedCallbackFunc(nsITimer* aTimer, void* aClosure) |
253 | 0 | { |
254 | 0 | FAIL() << "Timer shouldn't fire."; |
255 | 0 | } |
256 | | }; |
257 | | |
258 | | TEST(Timers, FindExpirationTime) |
259 | 0 | { |
260 | 0 | { |
261 | 0 | FindExpirationTimeState state; |
262 | 0 | // 0 low priority timers |
263 | 0 | state.InitTimers(0, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY); |
264 | 0 | TimeStamp before = state.mBefore; |
265 | 0 | TimeStamp middle = state.mMiddle; |
266 | 0 |
|
267 | 0 | TimeStamp t; |
268 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0); |
269 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
270 | 0 | EXPECT_EQ(t, before) << "Found time should be equal to default"; |
271 | 0 |
|
272 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20); |
273 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
274 | 0 | EXPECT_EQ(t, before) << "Found time should be equal to default"; |
275 | 0 |
|
276 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0); |
277 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
278 | 0 | EXPECT_LT(t, middle) << "Found time should be less than default"; |
279 | 0 |
|
280 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10); |
281 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
282 | 0 | EXPECT_LT(t, middle) << "Found time should be less than default"; |
283 | 0 |
|
284 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20); |
285 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
286 | 0 | EXPECT_LT(t, middle) << "Found time should be less than default"; |
287 | 0 | } |
288 | 0 |
|
289 | 0 | { |
290 | 0 | FindExpirationTimeState state; |
291 | 0 | // 5 low priority timers |
292 | 0 | state.InitTimers(5, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY); |
293 | 0 | TimeStamp before = state.mBefore; |
294 | 0 | TimeStamp middle = state.mMiddle; |
295 | 0 |
|
296 | 0 | TimeStamp t; |
297 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0); |
298 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
299 | 0 | EXPECT_EQ(t, before) << "Found time should be equal to default"; |
300 | 0 |
|
301 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20); |
302 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
303 | 0 | EXPECT_EQ(t, before) << "Found time should be equal to default"; |
304 | 0 |
|
305 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0); |
306 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
307 | 0 | EXPECT_LT(t, middle) << "Found time should be less than default"; |
308 | 0 |
|
309 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10); |
310 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
311 | 0 | EXPECT_LT(t, middle) << "Found time should be less than default"; |
312 | 0 |
|
313 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20); |
314 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
315 | 0 | EXPECT_LT(t, middle) << "Found time should be less than default"; |
316 | 0 | } |
317 | 0 |
|
318 | 0 | { |
319 | 0 | FindExpirationTimeState state; |
320 | 0 | // 15 low priority timers |
321 | 0 | state.InitTimers(15, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY); |
322 | 0 | TimeStamp before = state.mBefore; |
323 | 0 | TimeStamp middle = state.mMiddle; |
324 | 0 |
|
325 | 0 | TimeStamp t; |
326 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0); |
327 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
328 | 0 | EXPECT_EQ(t, before) << "Found time should be equal to default"; |
329 | 0 |
|
330 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20); |
331 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
332 | 0 | EXPECT_EQ(t, before) << "Found time should be equal to default"; |
333 | 0 |
|
334 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0); |
335 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
336 | 0 | EXPECT_LT(t, middle) << "Found time should be equal to default"; |
337 | 0 |
|
338 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10); |
339 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
340 | 0 | EXPECT_EQ(t, middle) << "Found time should be equal to default"; |
341 | 0 |
|
342 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20); |
343 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
344 | 0 | EXPECT_EQ(t, middle) << "Found time should be equal to default"; |
345 | 0 | } |
346 | 0 |
|
347 | 0 | { |
348 | 0 | AutoTestThread testThread; |
349 | 0 | FindExpirationTimeState state; |
350 | 0 | // 5 other targets |
351 | 0 | state.InitTimers(5, static_cast<nsIEventTarget*>(testThread)); |
352 | 0 | TimeStamp before = state.mBefore; |
353 | 0 | TimeStamp middle = state.mMiddle; |
354 | 0 |
|
355 | 0 | TimeStamp t; |
356 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0); |
357 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
358 | 0 | EXPECT_EQ(t, before) << "Found time should be equal to default"; |
359 | 0 |
|
360 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20); |
361 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
362 | 0 | EXPECT_EQ(t, before) << "Found time should be equal to default"; |
363 | 0 |
|
364 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0); |
365 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
366 | 0 | EXPECT_LT(t, middle) << "Found time should be less than default"; |
367 | 0 |
|
368 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10); |
369 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
370 | 0 | EXPECT_LT(t, middle) << "Found time should be less than default"; |
371 | 0 |
|
372 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20); |
373 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
374 | 0 | EXPECT_LT(t, middle) << "Found time should be less than default"; |
375 | 0 | } |
376 | 0 |
|
377 | 0 | { |
378 | 0 | AutoTestThread testThread; |
379 | 0 | FindExpirationTimeState state; |
380 | 0 | // 15 other targets |
381 | 0 | state.InitTimers(15, static_cast<nsIEventTarget*>(testThread)); |
382 | 0 | TimeStamp before = state.mBefore; |
383 | 0 | TimeStamp middle = state.mMiddle; |
384 | 0 |
|
385 | 0 | TimeStamp t; |
386 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0); |
387 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
388 | 0 | EXPECT_EQ(t, before) << "Found time should be equal to default"; |
389 | 0 |
|
390 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20); |
391 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
392 | 0 | EXPECT_EQ(t, before) << "Found time should be equal to default"; |
393 | 0 |
|
394 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0); |
395 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
396 | 0 | EXPECT_LT(t, middle) << "Found time should be less than default"; |
397 | 0 |
|
398 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10); |
399 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
400 | 0 | EXPECT_EQ(t, middle) << "Found time should be equal to default"; |
401 | 0 |
|
402 | 0 | t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20); |
403 | 0 | EXPECT_TRUE(t) << "We should find a time"; |
404 | 0 | EXPECT_EQ(t, middle) << "Found time should be equal to default"; |
405 | 0 | } |
406 | 0 | } |
407 | | |
408 | | #endif |
409 | | |
410 | 0 | #define FUZZ_MAX_TIMEOUT 9 |
411 | | class FuzzTestThreadState final : public nsITimerCallback { |
412 | | public: |
413 | | NS_DECL_THREADSAFE_ISUPPORTS |
414 | | |
415 | | explicit FuzzTestThreadState(nsIThread* thread) : |
416 | | mThread(thread), |
417 | | mStopped(false) |
418 | 0 | {} |
419 | | |
420 | | class StartRunnable final : public mozilla::Runnable { |
421 | | public: |
422 | | explicit StartRunnable(FuzzTestThreadState* threadState) |
423 | | : mozilla::Runnable("FuzzTestThreadState::StartRunnable") |
424 | | , mThreadState(threadState) |
425 | 0 | {} |
426 | | |
427 | | NS_IMETHOD Run() override |
428 | 0 | { |
429 | 0 | mThreadState->ScheduleOrCancelTimers(); |
430 | 0 | return NS_OK; |
431 | 0 | } |
432 | | |
433 | | private: |
434 | | RefPtr<FuzzTestThreadState> mThreadState; |
435 | | }; |
436 | | |
437 | | void Start() |
438 | 0 | { |
439 | 0 | nsCOMPtr<nsIRunnable> runnable = new StartRunnable(this); |
440 | 0 | nsresult rv = mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); |
441 | 0 | MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to dispatch StartRunnable."); |
442 | 0 | } |
443 | | |
444 | | void Stop() |
445 | 0 | { |
446 | 0 | mStopped = true; |
447 | 0 | } |
448 | | |
449 | | NS_IMETHOD Notify(nsITimer* aTimer) override |
450 | 0 | { |
451 | 0 | bool onCorrectThread; |
452 | 0 | nsresult rv = mThread->IsOnCurrentThread(&onCorrectThread); |
453 | 0 | MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to perform thread check."); |
454 | 0 | MOZ_RELEASE_ASSERT(onCorrectThread, "Notify invoked on wrong thread."); |
455 | 0 |
|
456 | 0 | uint32_t delay; |
457 | 0 | rv = aTimer->GetDelay(&delay); |
458 | 0 | MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "GetDelay failed."); |
459 | 0 |
|
460 | 0 | MOZ_RELEASE_ASSERT(delay <= FUZZ_MAX_TIMEOUT, |
461 | 0 | "Delay was an invalid value for this test."); |
462 | 0 |
|
463 | 0 | uint32_t type; |
464 | 0 | rv = aTimer->GetType(&type); |
465 | 0 | MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to get timer type."); |
466 | 0 | MOZ_RELEASE_ASSERT(type <= nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP); |
467 | 0 |
|
468 | 0 | if (type == nsITimer::TYPE_ONE_SHOT) { |
469 | 0 | MOZ_RELEASE_ASSERT(!mOneShotTimersByDelay[delay].empty(), |
470 | 0 | "Unexpected one-shot timer."); |
471 | 0 |
|
472 | 0 | MOZ_RELEASE_ASSERT(mOneShotTimersByDelay[delay].front().get() == aTimer, |
473 | 0 | "One-shot timers have been reordered."); |
474 | 0 |
|
475 | 0 | mOneShotTimersByDelay[delay].pop_front(); |
476 | 0 | --mTimersOutstanding; |
477 | 0 | } else if (mStopped) { |
478 | 0 | CancelRepeatingTimer(aTimer); |
479 | 0 | } |
480 | 0 |
|
481 | 0 | ScheduleOrCancelTimers(); |
482 | 0 | RescheduleSomeTimers(); |
483 | 0 | return NS_OK; |
484 | 0 | } |
485 | | |
486 | | bool HasTimersOutstanding() const |
487 | 0 | { |
488 | 0 | return !!mTimersOutstanding; |
489 | 0 | } |
490 | | |
491 | | private: |
492 | | ~FuzzTestThreadState() |
493 | 0 | { |
494 | 0 | for (size_t i = 0; i <= FUZZ_MAX_TIMEOUT; ++i) { |
495 | 0 | MOZ_RELEASE_ASSERT(mOneShotTimersByDelay[i].empty(), |
496 | 0 | "Timers remain at end of test."); |
497 | 0 | } |
498 | 0 | } |
499 | | |
500 | | uint32_t GetRandomType() const |
501 | 0 | { |
502 | 0 | return rand() % (nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP + 1); |
503 | 0 | } |
504 | | |
505 | | size_t CountOneShotTimers() const |
506 | 0 | { |
507 | 0 | size_t count = 0; |
508 | 0 | for (size_t i = 0; i <= FUZZ_MAX_TIMEOUT; ++i) { |
509 | 0 | count += mOneShotTimersByDelay[i].size(); |
510 | 0 | } |
511 | 0 | return count; |
512 | 0 | } |
513 | | |
514 | | void ScheduleOrCancelTimers() |
515 | 0 | { |
516 | 0 | if (mStopped) { |
517 | 0 | return; |
518 | 0 | } |
519 | 0 | |
520 | 0 | const size_t numTimersDesired = (rand() % 100) + 100; |
521 | 0 | MOZ_RELEASE_ASSERT(numTimersDesired >= 100); |
522 | 0 | MOZ_RELEASE_ASSERT(numTimersDesired < 200); |
523 | 0 | int adjustment = numTimersDesired - mTimersOutstanding; |
524 | 0 |
|
525 | 0 | while (adjustment > 0) { |
526 | 0 | CreateRandomTimer(); |
527 | 0 | --adjustment; |
528 | 0 | } |
529 | 0 |
|
530 | 0 | while (adjustment < 0) { |
531 | 0 | CancelRandomTimer(); |
532 | 0 | ++adjustment; |
533 | 0 | } |
534 | 0 |
|
535 | 0 | MOZ_RELEASE_ASSERT(numTimersDesired == mTimersOutstanding); |
536 | 0 | } |
537 | | |
538 | | void RescheduleSomeTimers() |
539 | 0 | { |
540 | 0 | if (mStopped) { |
541 | 0 | return; |
542 | 0 | } |
543 | 0 | |
544 | 0 | static const size_t kNumRescheduled = 40; |
545 | 0 |
|
546 | 0 | // Reschedule some timers with a Cancel first. |
547 | 0 | for (size_t i = 0; i < kNumRescheduled; ++i) { |
548 | 0 | InitRandomTimer(CancelRandomTimer().get()); |
549 | 0 | } |
550 | 0 | // Reschedule some timers without a Cancel first. |
551 | 0 | for (size_t i = 0; i < kNumRescheduled; ++i) { |
552 | 0 | InitRandomTimer(RemoveRandomTimer().get()); |
553 | 0 | } |
554 | 0 | } |
555 | | |
556 | | void CreateRandomTimer() |
557 | 0 | { |
558 | 0 | nsCOMPtr<nsITimer> timer = NS_NewTimer(static_cast<nsIEventTarget*>(mThread.get())); |
559 | 0 | MOZ_RELEASE_ASSERT(timer, "Failed to create timer."); |
560 | 0 |
|
561 | 0 | InitRandomTimer(timer.get()); |
562 | 0 | } |
563 | | |
564 | | nsCOMPtr<nsITimer> CancelRandomTimer() |
565 | 0 | { |
566 | 0 | nsCOMPtr<nsITimer> timer(RemoveRandomTimer()); |
567 | 0 | timer->Cancel(); |
568 | 0 | return timer; |
569 | 0 | } |
570 | | |
571 | | nsCOMPtr<nsITimer> RemoveRandomTimer() |
572 | 0 | { |
573 | 0 | MOZ_RELEASE_ASSERT(mTimersOutstanding); |
574 | 0 |
|
575 | 0 | if ((GetRandomType() == nsITimer::TYPE_ONE_SHOT && CountOneShotTimers()) |
576 | 0 | || mRepeatingTimers.empty()) { |
577 | 0 | uint32_t delayToRemove = rand() % (FUZZ_MAX_TIMEOUT + 1); |
578 | 0 | while (mOneShotTimersByDelay[delayToRemove].empty()) { |
579 | 0 | // ++delayToRemove mod FUZZ_MAX_TIMEOUT + 1 |
580 | 0 | delayToRemove = (delayToRemove + 1) % (FUZZ_MAX_TIMEOUT + 1); |
581 | 0 | } |
582 | 0 |
|
583 | 0 | uint32_t indexToRemove = |
584 | 0 | rand() % mOneShotTimersByDelay[delayToRemove].size(); |
585 | 0 |
|
586 | 0 | for (auto it = mOneShotTimersByDelay[delayToRemove].begin(); |
587 | 0 | it != mOneShotTimersByDelay[delayToRemove].end(); |
588 | 0 | ++it) { |
589 | 0 | if (indexToRemove) { |
590 | 0 | --indexToRemove; |
591 | 0 | continue; |
592 | 0 | } |
593 | 0 | |
594 | 0 | nsCOMPtr<nsITimer> removed = *it; |
595 | 0 | mOneShotTimersByDelay[delayToRemove].erase(it); |
596 | 0 | --mTimersOutstanding; |
597 | 0 | return removed; |
598 | 0 | } |
599 | 0 | } else { |
600 | 0 | size_t indexToRemove = rand() % mRepeatingTimers.size(); |
601 | 0 | nsCOMPtr<nsITimer> removed(mRepeatingTimers[indexToRemove]); |
602 | 0 | mRepeatingTimers.erase(mRepeatingTimers.begin() + indexToRemove); |
603 | 0 | --mTimersOutstanding; |
604 | 0 | return removed; |
605 | 0 | } |
606 | 0 | |
607 | 0 | MOZ_CRASH("Unable to remove a timer"); |
608 | 0 | } |
609 | | |
610 | | void InitRandomTimer(nsITimer* aTimer) |
611 | 0 | { |
612 | 0 | // Between 0 and FUZZ_MAX_TIMEOUT |
613 | 0 | uint32_t delay = rand() % (FUZZ_MAX_TIMEOUT + 1); |
614 | 0 | uint32_t type = GetRandomType(); |
615 | 0 | nsresult rv = aTimer->InitWithCallback(this, delay, type); |
616 | 0 | MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to set timer."); |
617 | 0 |
|
618 | 0 | if (type == nsITimer::TYPE_ONE_SHOT) { |
619 | 0 | mOneShotTimersByDelay[delay].push_back(aTimer); |
620 | 0 | } else { |
621 | 0 | mRepeatingTimers.push_back(aTimer); |
622 | 0 | } |
623 | 0 | ++mTimersOutstanding; |
624 | 0 | } |
625 | | |
626 | | void CancelRepeatingTimer(nsITimer* aTimer) |
627 | 0 | { |
628 | 0 | for (auto it = mRepeatingTimers.begin(); |
629 | 0 | it != mRepeatingTimers.end(); |
630 | 0 | ++it) { |
631 | 0 | if (it->get() == aTimer) { |
632 | 0 | mRepeatingTimers.erase(it); |
633 | 0 | aTimer->Cancel(); |
634 | 0 | --mTimersOutstanding; |
635 | 0 | return; |
636 | 0 | } |
637 | 0 | } |
638 | 0 | } |
639 | | |
640 | | nsCOMPtr<nsIThread> mThread; |
641 | | // Scheduled timers, indexed by delay between 0-9 ms, in lists |
642 | | // with most recently scheduled last. |
643 | | std::list<nsCOMPtr<nsITimer>> mOneShotTimersByDelay[FUZZ_MAX_TIMEOUT + 1]; |
644 | | std::vector<nsCOMPtr<nsITimer>> mRepeatingTimers; |
645 | | Atomic<bool> mStopped; |
646 | | Atomic<size_t> mTimersOutstanding; |
647 | | }; |
648 | | |
649 | | NS_IMPL_ISUPPORTS(FuzzTestThreadState, nsITimerCallback) |
650 | | |
651 | | TEST(Timers, FuzzTestTimers) |
652 | 0 | { |
653 | 0 | static const size_t kNumThreads(10); |
654 | 0 | AutoTestThread threads[kNumThreads]; |
655 | 0 | RefPtr<FuzzTestThreadState> threadStates[kNumThreads]; |
656 | 0 |
|
657 | 0 | for (size_t i = 0; i < kNumThreads; ++i) { |
658 | 0 | threadStates[i] = new FuzzTestThreadState(&*threads[i]); |
659 | 0 | threadStates[i]->Start(); |
660 | 0 | } |
661 | 0 |
|
662 | 0 | PR_Sleep(PR_MillisecondsToInterval(20000)); |
663 | 0 |
|
664 | 0 | for (size_t i = 0; i < kNumThreads; ++i) { |
665 | 0 | threadStates[i]->Stop(); |
666 | 0 | } |
667 | 0 |
|
668 | 0 | // Wait at most 10 seconds for all outstanding timers to pop |
669 | 0 | PRIntervalTime start = PR_IntervalNow(); |
670 | 0 | for (auto& threadState : threadStates) { |
671 | 0 | while (threadState->HasTimersOutstanding()) { |
672 | 0 | uint32_t elapsedMs = PR_IntervalToMilliseconds(PR_IntervalNow() - start); |
673 | 0 | ASSERT_LE(elapsedMs, uint32_t(10000)) << "Timed out waiting for all timers to pop"; |
674 | 0 | PR_Sleep(PR_MillisecondsToInterval(10)); |
675 | 0 | } |
676 | 0 | } |
677 | 0 | } |