|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "nsTimerImpl.h" |
|
8 #include "TimerThread.h" |
|
9 #include "nsAutoPtr.h" |
|
10 #include "nsThreadManager.h" |
|
11 #include "nsThreadUtils.h" |
|
12 #include "plarena.h" |
|
13 #include "pratom.h" |
|
14 #include "GeckoProfiler.h" |
|
15 #include "mozilla/Atomics.h" |
|
16 |
|
17 using mozilla::Atomic; |
|
18 using mozilla::TimeDuration; |
|
19 using mozilla::TimeStamp; |
|
20 |
|
21 static Atomic<int32_t> gGenerator; |
|
22 static TimerThread* gThread = nullptr; |
|
23 |
|
24 #ifdef DEBUG_TIMERS |
|
25 |
|
26 PRLogModuleInfo* |
|
27 GetTimerLog() |
|
28 { |
|
29 static PRLogModuleInfo *sLog; |
|
30 if (!sLog) |
|
31 sLog = PR_NewLogModule("nsTimerImpl"); |
|
32 return sLog; |
|
33 } |
|
34 |
|
35 #include <math.h> |
|
36 |
|
37 double nsTimerImpl::sDeltaSumSquared = 0; |
|
38 double nsTimerImpl::sDeltaSum = 0; |
|
39 double nsTimerImpl::sDeltaNum = 0; |
|
40 |
|
41 static void |
|
42 myNS_MeanAndStdDev(double n, double sumOfValues, double sumOfSquaredValues, |
|
43 double *meanResult, double *stdDevResult) |
|
44 { |
|
45 double mean = 0.0, var = 0.0, stdDev = 0.0; |
|
46 if (n > 0.0 && sumOfValues >= 0) { |
|
47 mean = sumOfValues / n; |
|
48 double temp = (n * sumOfSquaredValues) - (sumOfValues * sumOfValues); |
|
49 if (temp < 0.0 || n <= 1) |
|
50 var = 0.0; |
|
51 else |
|
52 var = temp / (n * (n - 1)); |
|
53 // for some reason, Windows says sqrt(0.0) is "-1.#J" (?!) so do this: |
|
54 stdDev = var != 0.0 ? sqrt(var) : 0.0; |
|
55 } |
|
56 *meanResult = mean; |
|
57 *stdDevResult = stdDev; |
|
58 } |
|
59 #endif |
|
60 |
|
61 namespace { |
|
62 |
|
63 // TimerEventAllocator is a thread-safe allocator used only for nsTimerEvents. |
|
64 // It's needed to avoid contention over the default allocator lock when |
|
65 // firing timer events (see bug 733277). The thread-safety is required because |
|
66 // nsTimerEvent objects are allocated on the timer thread, and freed on another |
|
67 // thread. Because TimerEventAllocator has its own lock, contention over that |
|
68 // lock is limited to the allocation and deallocation of nsTimerEvent objects. |
|
69 // |
|
70 // Because this allocator is layered over PLArenaPool, it never shrinks -- even |
|
71 // "freed" nsTimerEvents aren't truly freed, they're just put onto a free-list |
|
72 // for later recycling. So the amount of memory consumed will always be equal |
|
73 // to the high-water mark consumption. But nsTimerEvents are small and it's |
|
74 // unusual to have more than a few hundred of them, so this shouldn't be a |
|
75 // problem in practice. |
|
76 |
|
77 class TimerEventAllocator |
|
78 { |
|
79 private: |
|
80 struct FreeEntry { |
|
81 FreeEntry* mNext; |
|
82 }; |
|
83 |
|
84 PLArenaPool mPool; |
|
85 FreeEntry* mFirstFree; |
|
86 mozilla::Monitor mMonitor; |
|
87 |
|
88 public: |
|
89 TimerEventAllocator() |
|
90 : mFirstFree(nullptr), |
|
91 mMonitor("TimerEventAllocator") |
|
92 { |
|
93 PL_InitArenaPool(&mPool, "TimerEventPool", 4096, /* align = */ 0); |
|
94 } |
|
95 |
|
96 ~TimerEventAllocator() |
|
97 { |
|
98 PL_FinishArenaPool(&mPool); |
|
99 } |
|
100 |
|
101 void* Alloc(size_t aSize); |
|
102 void Free(void* aPtr); |
|
103 }; |
|
104 |
|
105 } // anonymous namespace |
|
106 |
|
107 class nsTimerEvent : public nsRunnable { |
|
108 public: |
|
109 NS_IMETHOD Run(); |
|
110 |
|
111 nsTimerEvent() |
|
112 : mTimer() |
|
113 , mGeneration(0) |
|
114 { |
|
115 MOZ_COUNT_CTOR(nsTimerEvent); |
|
116 |
|
117 MOZ_ASSERT(gThread->IsOnTimerThread(), |
|
118 "nsTimer must always be allocated on the timer thread"); |
|
119 |
|
120 sAllocatorUsers++; |
|
121 } |
|
122 |
|
123 #ifdef DEBUG_TIMERS |
|
124 TimeStamp mInitTime; |
|
125 #endif |
|
126 |
|
127 static void Init(); |
|
128 static void Shutdown(); |
|
129 static void DeleteAllocatorIfNeeded(); |
|
130 |
|
131 static void* operator new(size_t size) CPP_THROW_NEW { |
|
132 return sAllocator->Alloc(size); |
|
133 } |
|
134 void operator delete(void* p) { |
|
135 sAllocator->Free(p); |
|
136 DeleteAllocatorIfNeeded(); |
|
137 } |
|
138 |
|
139 already_AddRefed<nsTimerImpl> ForgetTimer() |
|
140 { |
|
141 return mTimer.forget(); |
|
142 } |
|
143 |
|
144 void SetTimer(already_AddRefed<nsTimerImpl> aTimer) |
|
145 { |
|
146 mTimer = aTimer; |
|
147 mGeneration = mTimer->GetGeneration(); |
|
148 } |
|
149 |
|
150 private: |
|
151 ~nsTimerEvent() { |
|
152 MOZ_COUNT_DTOR(nsTimerEvent); |
|
153 |
|
154 MOZ_ASSERT(!sCanDeleteAllocator || sAllocatorUsers > 0, |
|
155 "This will result in us attempting to deallocate the nsTimerEvent allocator twice"); |
|
156 sAllocatorUsers--; |
|
157 } |
|
158 |
|
159 nsRefPtr<nsTimerImpl> mTimer; |
|
160 int32_t mGeneration; |
|
161 |
|
162 static TimerEventAllocator* sAllocator; |
|
163 static Atomic<int32_t> sAllocatorUsers; |
|
164 static bool sCanDeleteAllocator; |
|
165 }; |
|
166 |
|
167 TimerEventAllocator* nsTimerEvent::sAllocator = nullptr; |
|
168 Atomic<int32_t> nsTimerEvent::sAllocatorUsers; |
|
169 bool nsTimerEvent::sCanDeleteAllocator = false; |
|
170 |
|
171 namespace { |
|
172 |
|
173 void* TimerEventAllocator::Alloc(size_t aSize) |
|
174 { |
|
175 MOZ_ASSERT(aSize == sizeof(nsTimerEvent)); |
|
176 |
|
177 mozilla::MonitorAutoLock lock(mMonitor); |
|
178 |
|
179 void* p; |
|
180 if (mFirstFree) { |
|
181 p = mFirstFree; |
|
182 mFirstFree = mFirstFree->mNext; |
|
183 } |
|
184 else { |
|
185 PL_ARENA_ALLOCATE(p, &mPool, aSize); |
|
186 if (!p) |
|
187 return nullptr; |
|
188 } |
|
189 |
|
190 return p; |
|
191 } |
|
192 |
|
193 void TimerEventAllocator::Free(void* aPtr) |
|
194 { |
|
195 mozilla::MonitorAutoLock lock(mMonitor); |
|
196 |
|
197 FreeEntry* entry = reinterpret_cast<FreeEntry*>(aPtr); |
|
198 |
|
199 entry->mNext = mFirstFree; |
|
200 mFirstFree = entry; |
|
201 } |
|
202 |
|
203 } // anonymous namespace |
|
204 |
|
205 NS_IMPL_QUERY_INTERFACE(nsTimerImpl, nsITimer) |
|
206 NS_IMPL_ADDREF(nsTimerImpl) |
|
207 |
|
208 NS_IMETHODIMP_(MozExternalRefCountType) nsTimerImpl::Release(void) |
|
209 { |
|
210 nsrefcnt count; |
|
211 |
|
212 MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); |
|
213 count = --mRefCnt; |
|
214 NS_LOG_RELEASE(this, count, "nsTimerImpl"); |
|
215 if (count == 0) { |
|
216 mRefCnt = 1; /* stabilize */ |
|
217 |
|
218 /* enable this to find non-threadsafe destructors: */ |
|
219 /* NS_ASSERT_OWNINGTHREAD(nsTimerImpl); */ |
|
220 delete this; |
|
221 return 0; |
|
222 } |
|
223 |
|
224 // If only one reference remains, and mArmed is set, then the ref must be |
|
225 // from the TimerThread::mTimers array, so we Cancel this timer to remove |
|
226 // the mTimers element, and return 0 if Cancel in fact disarmed the timer. |
|
227 // |
|
228 // We use an inlined version of nsTimerImpl::Cancel here to check for the |
|
229 // NS_ERROR_NOT_AVAILABLE code returned by gThread->RemoveTimer when this |
|
230 // timer is not found in the mTimers array -- i.e., when the timer was not |
|
231 // in fact armed once we acquired TimerThread::mLock, in spite of mArmed |
|
232 // being true here. That can happen if the armed timer is being fired by |
|
233 // TimerThread::Run as we race and test mArmed just before it is cleared by |
|
234 // the timer thread. If the RemoveTimer call below doesn't find this timer |
|
235 // in the mTimers array, then the last ref to this timer is held manually |
|
236 // and temporarily by the TimerThread, so we should fall through to the |
|
237 // final return and return 1, not 0. |
|
238 // |
|
239 // The original version of this thread-based timer code kept weak refs from |
|
240 // TimerThread::mTimers, removing this timer's weak ref in the destructor, |
|
241 // but that leads to double-destructions in the race described above, and |
|
242 // adding mArmed doesn't help, because destructors can't be deferred, once |
|
243 // begun. But by combining reference-counting and a specialized Release |
|
244 // method with "is this timer still in the mTimers array once we acquire |
|
245 // the TimerThread's lock" testing, we defer destruction until we're sure |
|
246 // that only one thread has its hot little hands on this timer. |
|
247 // |
|
248 // Note that both approaches preclude a timer creator, and everyone else |
|
249 // except the TimerThread who might have a strong ref, from dropping all |
|
250 // their strong refs without implicitly canceling the timer. Timers need |
|
251 // non-mTimers-element strong refs to stay alive. |
|
252 |
|
253 if (count == 1 && mArmed) { |
|
254 mCanceled = true; |
|
255 |
|
256 MOZ_ASSERT(gThread, "Armed timer exists after the thread timer stopped."); |
|
257 if (NS_SUCCEEDED(gThread->RemoveTimer(this))) |
|
258 return 0; |
|
259 } |
|
260 |
|
261 return count; |
|
262 } |
|
263 |
|
264 nsTimerImpl::nsTimerImpl() : |
|
265 mClosure(nullptr), |
|
266 mCallbackType(CALLBACK_TYPE_UNKNOWN), |
|
267 mFiring(false), |
|
268 mArmed(false), |
|
269 mCanceled(false), |
|
270 mGeneration(0), |
|
271 mDelay(0) |
|
272 { |
|
273 // XXXbsmedberg: shouldn't this be in Init()? |
|
274 mEventTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread()); |
|
275 |
|
276 mCallback.c = nullptr; |
|
277 } |
|
278 |
|
279 nsTimerImpl::~nsTimerImpl() |
|
280 { |
|
281 ReleaseCallback(); |
|
282 } |
|
283 |
|
284 //static |
|
285 nsresult |
|
286 nsTimerImpl::Startup() |
|
287 { |
|
288 nsresult rv; |
|
289 |
|
290 nsTimerEvent::Init(); |
|
291 |
|
292 gThread = new TimerThread(); |
|
293 if (!gThread) return NS_ERROR_OUT_OF_MEMORY; |
|
294 |
|
295 NS_ADDREF(gThread); |
|
296 rv = gThread->InitLocks(); |
|
297 |
|
298 if (NS_FAILED(rv)) { |
|
299 NS_RELEASE(gThread); |
|
300 } |
|
301 |
|
302 return rv; |
|
303 } |
|
304 |
|
305 void nsTimerImpl::Shutdown() |
|
306 { |
|
307 #ifdef DEBUG_TIMERS |
|
308 if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { |
|
309 double mean = 0, stddev = 0; |
|
310 myNS_MeanAndStdDev(sDeltaNum, sDeltaSum, sDeltaSumSquared, &mean, &stddev); |
|
311 |
|
312 PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("sDeltaNum = %f, sDeltaSum = %f, sDeltaSumSquared = %f\n", sDeltaNum, sDeltaSum, sDeltaSumSquared)); |
|
313 PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("mean: %fms, stddev: %fms\n", mean, stddev)); |
|
314 } |
|
315 #endif |
|
316 |
|
317 if (!gThread) |
|
318 return; |
|
319 |
|
320 gThread->Shutdown(); |
|
321 NS_RELEASE(gThread); |
|
322 |
|
323 nsTimerEvent::Shutdown(); |
|
324 } |
|
325 |
|
326 |
|
327 nsresult nsTimerImpl::InitCommon(uint32_t aType, uint32_t aDelay) |
|
328 { |
|
329 nsresult rv; |
|
330 |
|
331 if (NS_WARN_IF(!gThread)) |
|
332 return NS_ERROR_NOT_INITIALIZED; |
|
333 if (!mEventTarget) { |
|
334 NS_ERROR("mEventTarget is NULL"); |
|
335 return NS_ERROR_NOT_INITIALIZED; |
|
336 } |
|
337 |
|
338 rv = gThread->Init(); |
|
339 if (NS_WARN_IF(NS_FAILED(rv))) |
|
340 return rv; |
|
341 |
|
342 /** |
|
343 * In case of re-Init, both with and without a preceding Cancel, clear the |
|
344 * mCanceled flag and assign a new mGeneration. But first, remove any armed |
|
345 * timer from the timer thread's list. |
|
346 * |
|
347 * If we are racing with the timer thread to remove this timer and we lose, |
|
348 * the RemoveTimer call made here will fail to find this timer in the timer |
|
349 * thread's list, and will return false harmlessly. We test mArmed here to |
|
350 * avoid the small overhead in RemoveTimer of locking the timer thread and |
|
351 * checking its list for this timer. It's safe to test mArmed even though |
|
352 * it might be cleared on another thread in the next cycle (or even already |
|
353 * be cleared by another CPU whose store hasn't reached our CPU's cache), |
|
354 * because RemoveTimer is idempotent. |
|
355 */ |
|
356 if (mArmed) |
|
357 gThread->RemoveTimer(this); |
|
358 mCanceled = false; |
|
359 mTimeout = TimeStamp(); |
|
360 mGeneration = gGenerator++; |
|
361 |
|
362 mType = (uint8_t)aType; |
|
363 SetDelayInternal(aDelay); |
|
364 |
|
365 return gThread->AddTimer(this); |
|
366 } |
|
367 |
|
368 NS_IMETHODIMP nsTimerImpl::InitWithFuncCallback(nsTimerCallbackFunc aFunc, |
|
369 void *aClosure, |
|
370 uint32_t aDelay, |
|
371 uint32_t aType) |
|
372 { |
|
373 if (NS_WARN_IF(!aFunc)) |
|
374 return NS_ERROR_INVALID_ARG; |
|
375 |
|
376 ReleaseCallback(); |
|
377 mCallbackType = CALLBACK_TYPE_FUNC; |
|
378 mCallback.c = aFunc; |
|
379 mClosure = aClosure; |
|
380 |
|
381 return InitCommon(aType, aDelay); |
|
382 } |
|
383 |
|
384 NS_IMETHODIMP nsTimerImpl::InitWithCallback(nsITimerCallback *aCallback, |
|
385 uint32_t aDelay, |
|
386 uint32_t aType) |
|
387 { |
|
388 if (NS_WARN_IF(!aCallback)) |
|
389 return NS_ERROR_INVALID_ARG; |
|
390 |
|
391 ReleaseCallback(); |
|
392 mCallbackType = CALLBACK_TYPE_INTERFACE; |
|
393 mCallback.i = aCallback; |
|
394 NS_ADDREF(mCallback.i); |
|
395 |
|
396 return InitCommon(aType, aDelay); |
|
397 } |
|
398 |
|
399 NS_IMETHODIMP nsTimerImpl::Init(nsIObserver *aObserver, |
|
400 uint32_t aDelay, |
|
401 uint32_t aType) |
|
402 { |
|
403 if (NS_WARN_IF(!aObserver)) |
|
404 return NS_ERROR_INVALID_ARG; |
|
405 |
|
406 ReleaseCallback(); |
|
407 mCallbackType = CALLBACK_TYPE_OBSERVER; |
|
408 mCallback.o = aObserver; |
|
409 NS_ADDREF(mCallback.o); |
|
410 |
|
411 return InitCommon(aType, aDelay); |
|
412 } |
|
413 |
|
414 NS_IMETHODIMP nsTimerImpl::Cancel() |
|
415 { |
|
416 mCanceled = true; |
|
417 |
|
418 if (gThread) |
|
419 gThread->RemoveTimer(this); |
|
420 |
|
421 ReleaseCallback(); |
|
422 |
|
423 return NS_OK; |
|
424 } |
|
425 |
|
426 NS_IMETHODIMP nsTimerImpl::SetDelay(uint32_t aDelay) |
|
427 { |
|
428 if (mCallbackType == CALLBACK_TYPE_UNKNOWN && mType == TYPE_ONE_SHOT) { |
|
429 // This may happen if someone tries to re-use a one-shot timer |
|
430 // by re-setting delay instead of reinitializing the timer. |
|
431 NS_ERROR("nsITimer->SetDelay() called when the " |
|
432 "one-shot timer is not set up."); |
|
433 return NS_ERROR_NOT_INITIALIZED; |
|
434 } |
|
435 |
|
436 // If we're already repeating precisely, update mTimeout now so that the |
|
437 // new delay takes effect in the future. |
|
438 if (!mTimeout.IsNull() && mType == TYPE_REPEATING_PRECISE) |
|
439 mTimeout = TimeStamp::Now(); |
|
440 |
|
441 SetDelayInternal(aDelay); |
|
442 |
|
443 if (!mFiring && gThread) |
|
444 gThread->TimerDelayChanged(this); |
|
445 |
|
446 return NS_OK; |
|
447 } |
|
448 |
|
449 NS_IMETHODIMP nsTimerImpl::GetDelay(uint32_t* aDelay) |
|
450 { |
|
451 *aDelay = mDelay; |
|
452 return NS_OK; |
|
453 } |
|
454 |
|
455 NS_IMETHODIMP nsTimerImpl::SetType(uint32_t aType) |
|
456 { |
|
457 mType = (uint8_t)aType; |
|
458 // XXX if this is called, we should change the actual type.. this could effect |
|
459 // repeating timers. we need to ensure in Fire() that if mType has changed |
|
460 // during the callback that we don't end up with the timer in the queue twice. |
|
461 return NS_OK; |
|
462 } |
|
463 |
|
464 NS_IMETHODIMP nsTimerImpl::GetType(uint32_t* aType) |
|
465 { |
|
466 *aType = mType; |
|
467 return NS_OK; |
|
468 } |
|
469 |
|
470 |
|
471 NS_IMETHODIMP nsTimerImpl::GetClosure(void** aClosure) |
|
472 { |
|
473 *aClosure = mClosure; |
|
474 return NS_OK; |
|
475 } |
|
476 |
|
477 |
|
478 NS_IMETHODIMP nsTimerImpl::GetCallback(nsITimerCallback **aCallback) |
|
479 { |
|
480 if (mCallbackType == CALLBACK_TYPE_INTERFACE) |
|
481 NS_IF_ADDREF(*aCallback = mCallback.i); |
|
482 else if (mTimerCallbackWhileFiring) |
|
483 NS_ADDREF(*aCallback = mTimerCallbackWhileFiring); |
|
484 else |
|
485 *aCallback = nullptr; |
|
486 |
|
487 return NS_OK; |
|
488 } |
|
489 |
|
490 |
|
491 NS_IMETHODIMP nsTimerImpl::GetTarget(nsIEventTarget** aTarget) |
|
492 { |
|
493 NS_IF_ADDREF(*aTarget = mEventTarget); |
|
494 return NS_OK; |
|
495 } |
|
496 |
|
497 |
|
498 NS_IMETHODIMP nsTimerImpl::SetTarget(nsIEventTarget* aTarget) |
|
499 { |
|
500 if (NS_WARN_IF(mCallbackType != CALLBACK_TYPE_UNKNOWN)) |
|
501 return NS_ERROR_ALREADY_INITIALIZED; |
|
502 |
|
503 if (aTarget) |
|
504 mEventTarget = aTarget; |
|
505 else |
|
506 mEventTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread()); |
|
507 return NS_OK; |
|
508 } |
|
509 |
|
510 |
|
511 void nsTimerImpl::Fire() |
|
512 { |
|
513 if (mCanceled) |
|
514 return; |
|
515 |
|
516 PROFILER_LABEL("Timer", "Fire"); |
|
517 |
|
518 #ifdef MOZ_TASK_TRACER |
|
519 mozilla::tasktracer::AutoRunFakeTracedTask runTracedTask(mTracedTask); |
|
520 #endif |
|
521 |
|
522 #ifdef DEBUG_TIMERS |
|
523 TimeStamp now = TimeStamp::Now(); |
|
524 if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { |
|
525 TimeDuration a = now - mStart; // actual delay in intervals |
|
526 TimeDuration b = TimeDuration::FromMilliseconds(mDelay); // expected delay in intervals |
|
527 TimeDuration delta = (a > b) ? a - b : b - a; |
|
528 uint32_t d = delta.ToMilliseconds(); // delta in ms |
|
529 sDeltaSum += d; |
|
530 sDeltaSumSquared += double(d) * double(d); |
|
531 sDeltaNum++; |
|
532 |
|
533 PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("[this=%p] expected delay time %4ums\n", this, mDelay)); |
|
534 PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("[this=%p] actual delay time %fms\n", this, a.ToMilliseconds())); |
|
535 PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("[this=%p] (mType is %d) -------\n", this, mType)); |
|
536 PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("[this=%p] delta %4dms\n", this, (a > b) ? (int32_t)d : -(int32_t)d)); |
|
537 |
|
538 mStart = mStart2; |
|
539 mStart2 = TimeStamp(); |
|
540 } |
|
541 #endif |
|
542 |
|
543 TimeStamp timeout = mTimeout; |
|
544 if (IsRepeatingPrecisely()) { |
|
545 // Precise repeating timers advance mTimeout by mDelay without fail before |
|
546 // calling Fire(). |
|
547 timeout -= TimeDuration::FromMilliseconds(mDelay); |
|
548 } |
|
549 |
|
550 if (mCallbackType == CALLBACK_TYPE_INTERFACE) |
|
551 mTimerCallbackWhileFiring = mCallback.i; |
|
552 mFiring = true; |
|
553 |
|
554 // Handle callbacks that re-init the timer, but avoid leaking. |
|
555 // See bug 330128. |
|
556 CallbackUnion callback = mCallback; |
|
557 unsigned callbackType = mCallbackType; |
|
558 if (callbackType == CALLBACK_TYPE_INTERFACE) |
|
559 NS_ADDREF(callback.i); |
|
560 else if (callbackType == CALLBACK_TYPE_OBSERVER) |
|
561 NS_ADDREF(callback.o); |
|
562 ReleaseCallback(); |
|
563 |
|
564 switch (callbackType) { |
|
565 case CALLBACK_TYPE_FUNC: |
|
566 callback.c(this, mClosure); |
|
567 break; |
|
568 case CALLBACK_TYPE_INTERFACE: |
|
569 callback.i->Notify(this); |
|
570 break; |
|
571 case CALLBACK_TYPE_OBSERVER: |
|
572 callback.o->Observe(static_cast<nsITimer*>(this), |
|
573 NS_TIMER_CALLBACK_TOPIC, |
|
574 nullptr); |
|
575 break; |
|
576 default:; |
|
577 } |
|
578 |
|
579 // If the callback didn't re-init the timer, and it's not a one-shot timer, |
|
580 // restore the callback state. |
|
581 if (mCallbackType == CALLBACK_TYPE_UNKNOWN && |
|
582 mType != TYPE_ONE_SHOT && !mCanceled) { |
|
583 mCallback = callback; |
|
584 mCallbackType = callbackType; |
|
585 } else { |
|
586 // The timer was a one-shot, or the callback was reinitialized. |
|
587 if (callbackType == CALLBACK_TYPE_INTERFACE) |
|
588 NS_RELEASE(callback.i); |
|
589 else if (callbackType == CALLBACK_TYPE_OBSERVER) |
|
590 NS_RELEASE(callback.o); |
|
591 } |
|
592 |
|
593 mFiring = false; |
|
594 mTimerCallbackWhileFiring = nullptr; |
|
595 |
|
596 #ifdef DEBUG_TIMERS |
|
597 if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { |
|
598 PR_LOG(GetTimerLog(), PR_LOG_DEBUG, |
|
599 ("[this=%p] Took %fms to fire timer callback\n", |
|
600 this, (TimeStamp::Now() - now).ToMilliseconds())); |
|
601 } |
|
602 #endif |
|
603 |
|
604 // Reschedule repeating timers, except REPEATING_PRECISE which already did |
|
605 // that in PostTimerEvent, but make sure that we aren't armed already (which |
|
606 // can happen if the callback reinitialized the timer). |
|
607 if (IsRepeating() && mType != TYPE_REPEATING_PRECISE && !mArmed) { |
|
608 if (mType == TYPE_REPEATING_SLACK) |
|
609 SetDelayInternal(mDelay); // force mTimeout to be recomputed. For |
|
610 // REPEATING_PRECISE_CAN_SKIP timers this has |
|
611 // already happened. |
|
612 if (gThread) |
|
613 gThread->AddTimer(this); |
|
614 } |
|
615 } |
|
616 |
|
617 void nsTimerEvent::Init() |
|
618 { |
|
619 sAllocator = new TimerEventAllocator(); |
|
620 } |
|
621 |
|
622 void nsTimerEvent::Shutdown() |
|
623 { |
|
624 sCanDeleteAllocator = true; |
|
625 DeleteAllocatorIfNeeded(); |
|
626 } |
|
627 |
|
628 void nsTimerEvent::DeleteAllocatorIfNeeded() |
|
629 { |
|
630 if (sCanDeleteAllocator && sAllocatorUsers == 0) { |
|
631 delete sAllocator; |
|
632 sAllocator = nullptr; |
|
633 } |
|
634 } |
|
635 |
|
636 NS_IMETHODIMP nsTimerEvent::Run() |
|
637 { |
|
638 if (mGeneration != mTimer->GetGeneration()) |
|
639 return NS_OK; |
|
640 |
|
641 #ifdef DEBUG_TIMERS |
|
642 if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { |
|
643 TimeStamp now = TimeStamp::Now(); |
|
644 PR_LOG(GetTimerLog(), PR_LOG_DEBUG, |
|
645 ("[this=%p] time between PostTimerEvent() and Fire(): %fms\n", |
|
646 this, (now - mInitTime).ToMilliseconds())); |
|
647 } |
|
648 #endif |
|
649 |
|
650 mTimer->Fire(); |
|
651 // Since nsTimerImpl is not thread-safe, we should release |mTimer| |
|
652 // here in the target thread to avoid race condition. Otherwise, |
|
653 // ~nsTimerEvent() which calls nsTimerImpl::Release() could run in the |
|
654 // timer thread and result in race condition. |
|
655 mTimer = nullptr; |
|
656 |
|
657 return NS_OK; |
|
658 } |
|
659 |
|
660 already_AddRefed<nsTimerImpl> |
|
661 nsTimerImpl::PostTimerEvent(already_AddRefed<nsTimerImpl> aTimerRef) |
|
662 { |
|
663 nsRefPtr<nsTimerImpl> timer(aTimerRef); |
|
664 if (!timer->mEventTarget) { |
|
665 NS_ERROR("Attempt to post timer event to NULL event target"); |
|
666 return timer.forget(); |
|
667 } |
|
668 |
|
669 // XXX we may want to reuse this nsTimerEvent in the case of repeating timers. |
|
670 |
|
671 // Since TimerThread addref'd 'timer' for us, we don't need to addref here. |
|
672 // We will release either in ~nsTimerEvent(), or pass the reference back to |
|
673 // the caller. We need to copy the generation number from this timer into the |
|
674 // event, so we can avoid firing a timer that was re-initialized after being |
|
675 // canceled. |
|
676 |
|
677 // Note: We override operator new for this class, and the override is |
|
678 // fallible! |
|
679 nsRefPtr<nsTimerEvent> event = new nsTimerEvent; |
|
680 if (!event) |
|
681 return timer.forget(); |
|
682 |
|
683 #ifdef DEBUG_TIMERS |
|
684 if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { |
|
685 event->mInitTime = TimeStamp::Now(); |
|
686 } |
|
687 #endif |
|
688 |
|
689 // If this is a repeating precise timer, we need to calculate the time for |
|
690 // the next timer to fire before we make the callback. |
|
691 if (timer->IsRepeatingPrecisely()) { |
|
692 timer->SetDelayInternal(timer->mDelay); |
|
693 |
|
694 // But only re-arm REPEATING_PRECISE timers. |
|
695 if (gThread && timer->mType == TYPE_REPEATING_PRECISE) { |
|
696 nsresult rv = gThread->AddTimer(timer); |
|
697 if (NS_FAILED(rv)) { |
|
698 return timer.forget(); |
|
699 } |
|
700 } |
|
701 } |
|
702 |
|
703 nsIEventTarget* target = timer->mEventTarget; |
|
704 event->SetTimer(timer.forget()); |
|
705 |
|
706 nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL); |
|
707 if (NS_FAILED(rv)) { |
|
708 timer = event->ForgetTimer(); |
|
709 if (gThread) { |
|
710 gThread->RemoveTimer(timer); |
|
711 } |
|
712 return timer.forget(); |
|
713 } |
|
714 |
|
715 return nullptr; |
|
716 } |
|
717 |
|
718 void nsTimerImpl::SetDelayInternal(uint32_t aDelay) |
|
719 { |
|
720 TimeDuration delayInterval = TimeDuration::FromMilliseconds(aDelay); |
|
721 |
|
722 mDelay = aDelay; |
|
723 |
|
724 TimeStamp now = TimeStamp::Now(); |
|
725 if (mTimeout.IsNull() || mType != TYPE_REPEATING_PRECISE) |
|
726 mTimeout = now; |
|
727 |
|
728 mTimeout += delayInterval; |
|
729 |
|
730 #ifdef DEBUG_TIMERS |
|
731 if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { |
|
732 if (mStart.IsNull()) |
|
733 mStart = now; |
|
734 else |
|
735 mStart2 = now; |
|
736 } |
|
737 #endif |
|
738 } |
|
739 |
|
740 // NOT FOR PUBLIC CONSUMPTION! |
|
741 nsresult |
|
742 NS_NewTimer(nsITimer* *aResult, nsTimerCallbackFunc aCallback, void *aClosure, |
|
743 uint32_t aDelay, uint32_t aType) |
|
744 { |
|
745 nsTimerImpl* timer = new nsTimerImpl(); |
|
746 if (timer == nullptr) |
|
747 return NS_ERROR_OUT_OF_MEMORY; |
|
748 NS_ADDREF(timer); |
|
749 |
|
750 nsresult rv = timer->InitWithFuncCallback(aCallback, aClosure, |
|
751 aDelay, aType); |
|
752 if (NS_FAILED(rv)) { |
|
753 NS_RELEASE(timer); |
|
754 return rv; |
|
755 } |
|
756 |
|
757 *aResult = timer; |
|
758 return NS_OK; |
|
759 } |