Bug 575870 - Enable the firefox button on xp themed, classic, and aero basic. r=dao...
[mozilla-central.git] / xpcom / threads / nsTimerImpl.cpp
blob12ca72958d88a4db0923ae164303d04e8e46927d
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 * ***** BEGIN LICENSE BLOCK *****
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
15 * License.
17 * The Original Code is mozilla.org code.
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 2001
22 * the Initial Developer. All Rights Reserved.
24 * Contributor(s):
25 * Stuart Parmenter <pavlov@netscape.com>
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
41 #include "nsTimerImpl.h"
42 #include "TimerThread.h"
43 #include "nsAutoLock.h"
44 #include "nsAutoPtr.h"
45 #include "nsThreadManager.h"
46 #include "nsThreadUtils.h"
47 #include "prmem.h"
49 using mozilla::TimeDuration;
50 using mozilla::TimeStamp;
52 static PRInt32 gGenerator = 0;
53 static TimerThread* gThread = nsnull;
55 #ifdef DEBUG_TIMERS
56 #include <math.h>
58 double nsTimerImpl::sDeltaSumSquared = 0;
59 double nsTimerImpl::sDeltaSum = 0;
60 double nsTimerImpl::sDeltaNum = 0;
62 static void
63 myNS_MeanAndStdDev(double n, double sumOfValues, double sumOfSquaredValues,
64 double *meanResult, double *stdDevResult)
66 double mean = 0.0, var = 0.0, stdDev = 0.0;
67 if (n > 0.0 && sumOfValues >= 0) {
68 mean = sumOfValues / n;
69 double temp = (n * sumOfSquaredValues) - (sumOfValues * sumOfValues);
70 if (temp < 0.0 || n <= 1)
71 var = 0.0;
72 else
73 var = temp / (n * (n - 1));
74 // for some reason, Windows says sqrt(0.0) is "-1.#J" (?!) so do this:
75 stdDev = var != 0.0 ? sqrt(var) : 0.0;
77 *meanResult = mean;
78 *stdDevResult = stdDev;
80 #endif
82 NS_IMPL_THREADSAFE_QUERY_INTERFACE1(nsTimerImpl, nsITimer)
83 NS_IMPL_THREADSAFE_ADDREF(nsTimerImpl)
85 NS_IMETHODIMP_(nsrefcnt) nsTimerImpl::Release(void)
87 nsrefcnt count;
89 NS_PRECONDITION(0 != mRefCnt, "dup release");
90 count = PR_AtomicDecrement((PRInt32 *)&mRefCnt);
91 NS_LOG_RELEASE(this, count, "nsTimerImpl");
92 if (count == 0) {
93 mRefCnt = 1; /* stabilize */
95 /* enable this to find non-threadsafe destructors: */
96 /* NS_ASSERT_OWNINGTHREAD(nsTimerImpl); */
97 delete this;
98 return 0;
101 // If only one reference remains, and mArmed is set, then the ref must be
102 // from the TimerThread::mTimers array, so we Cancel this timer to remove
103 // the mTimers element, and return 0 if Cancel in fact disarmed the timer.
105 // We use an inlined version of nsTimerImpl::Cancel here to check for the
106 // NS_ERROR_NOT_AVAILABLE code returned by gThread->RemoveTimer when this
107 // timer is not found in the mTimers array -- i.e., when the timer was not
108 // in fact armed once we acquired TimerThread::mLock, in spite of mArmed
109 // being true here. That can happen if the armed timer is being fired by
110 // TimerThread::Run as we race and test mArmed just before it is cleared by
111 // the timer thread. If the RemoveTimer call below doesn't find this timer
112 // in the mTimers array, then the last ref to this timer is held manually
113 // and temporarily by the TimerThread, so we should fall through to the
114 // final return and return 1, not 0.
116 // The original version of this thread-based timer code kept weak refs from
117 // TimerThread::mTimers, removing this timer's weak ref in the destructor,
118 // but that leads to double-destructions in the race described above, and
119 // adding mArmed doesn't help, because destructors can't be deferred, once
120 // begun. But by combining reference-counting and a specialized Release
121 // method with "is this timer still in the mTimers array once we acquire
122 // the TimerThread's lock" testing, we defer destruction until we're sure
123 // that only one thread has its hot little hands on this timer.
125 // Note that both approaches preclude a timer creator, and everyone else
126 // except the TimerThread who might have a strong ref, from dropping all
127 // their strong refs without implicitly canceling the timer. Timers need
128 // non-mTimers-element strong refs to stay alive.
130 if (count == 1 && mArmed) {
131 mCanceled = PR_TRUE;
133 NS_ASSERTION(gThread, "An armed timer exists after the thread timer stopped.");
134 if (NS_SUCCEEDED(gThread->RemoveTimer(this)))
135 return 0;
138 return count;
141 nsTimerImpl::nsTimerImpl() :
142 mClosure(nsnull),
143 mCallbackType(CALLBACK_TYPE_UNKNOWN),
144 mFiring(PR_FALSE),
145 mArmed(PR_FALSE),
146 mCanceled(PR_FALSE),
147 mGeneration(0),
148 mDelay(0)
150 // XXXbsmedberg: shouldn't this be in Init()?
151 mEventTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
153 mCallback.c = nsnull;
156 nsTimerImpl::~nsTimerImpl()
158 ReleaseCallback();
161 //static
162 nsresult
163 nsTimerImpl::Startup()
165 nsresult rv;
167 gThread = new TimerThread();
168 if (!gThread) return NS_ERROR_OUT_OF_MEMORY;
170 NS_ADDREF(gThread);
171 rv = gThread->InitLocks();
173 if (NS_FAILED(rv)) {
174 NS_RELEASE(gThread);
177 return rv;
180 void nsTimerImpl::Shutdown()
182 #ifdef DEBUG_TIMERS
183 if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
184 double mean = 0, stddev = 0;
185 myNS_MeanAndStdDev(sDeltaNum, sDeltaSum, sDeltaSumSquared, &mean, &stddev);
187 PR_LOG(gTimerLog, PR_LOG_DEBUG, ("sDeltaNum = %f, sDeltaSum = %f, sDeltaSumSquared = %f\n", sDeltaNum, sDeltaSum, sDeltaSumSquared));
188 PR_LOG(gTimerLog, PR_LOG_DEBUG, ("mean: %fms, stddev: %fms\n", mean, stddev));
190 #endif
192 if (!gThread)
193 return;
195 gThread->Shutdown();
196 NS_RELEASE(gThread);
200 nsresult nsTimerImpl::InitCommon(PRUint32 aType, PRUint32 aDelay)
202 nsresult rv;
204 NS_ENSURE_TRUE(gThread, NS_ERROR_NOT_INITIALIZED);
206 rv = gThread->Init();
207 NS_ENSURE_SUCCESS(rv, rv);
210 * In case of re-Init, both with and without a preceding Cancel, clear the
211 * mCanceled flag and assign a new mGeneration. But first, remove any armed
212 * timer from the timer thread's list.
214 * If we are racing with the timer thread to remove this timer and we lose,
215 * the RemoveTimer call made here will fail to find this timer in the timer
216 * thread's list, and will return false harmlessly. We test mArmed here to
217 * avoid the small overhead in RemoveTimer of locking the timer thread and
218 * checking its list for this timer. It's safe to test mArmed even though
219 * it might be cleared on another thread in the next cycle (or even already
220 * be cleared by another CPU whose store hasn't reached our CPU's cache),
221 * because RemoveTimer is idempotent.
223 if (mArmed)
224 gThread->RemoveTimer(this);
225 mCanceled = PR_FALSE;
226 mGeneration = PR_AtomicIncrement(&gGenerator);
228 mType = (PRUint8)aType;
229 SetDelayInternal(aDelay);
231 return gThread->AddTimer(this);
234 NS_IMETHODIMP nsTimerImpl::InitWithFuncCallback(nsTimerCallbackFunc aFunc,
235 void *aClosure,
236 PRUint32 aDelay,
237 PRUint32 aType)
239 NS_ENSURE_ARG_POINTER(aFunc);
241 ReleaseCallback();
242 mCallbackType = CALLBACK_TYPE_FUNC;
243 mCallback.c = aFunc;
244 mClosure = aClosure;
246 return InitCommon(aType, aDelay);
249 NS_IMETHODIMP nsTimerImpl::InitWithCallback(nsITimerCallback *aCallback,
250 PRUint32 aDelay,
251 PRUint32 aType)
253 NS_ENSURE_ARG_POINTER(aCallback);
255 ReleaseCallback();
256 mCallbackType = CALLBACK_TYPE_INTERFACE;
257 mCallback.i = aCallback;
258 NS_ADDREF(mCallback.i);
260 return InitCommon(aType, aDelay);
263 NS_IMETHODIMP nsTimerImpl::Init(nsIObserver *aObserver,
264 PRUint32 aDelay,
265 PRUint32 aType)
267 NS_ENSURE_ARG_POINTER(aObserver);
269 ReleaseCallback();
270 mCallbackType = CALLBACK_TYPE_OBSERVER;
271 mCallback.o = aObserver;
272 NS_ADDREF(mCallback.o);
274 return InitCommon(aType, aDelay);
277 NS_IMETHODIMP nsTimerImpl::Cancel()
279 mCanceled = PR_TRUE;
281 if (gThread)
282 gThread->RemoveTimer(this);
284 ReleaseCallback();
286 return NS_OK;
289 NS_IMETHODIMP nsTimerImpl::SetDelay(PRUint32 aDelay)
291 if (mCallbackType == CALLBACK_TYPE_UNKNOWN && mType == TYPE_ONE_SHOT) {
292 // This may happen if someone tries to re-use a one-shot timer
293 // by re-setting delay instead of reinitializing the timer.
294 NS_ERROR("nsITimer->SetDelay() called when the "
295 "one-shot timer is not set up.");
296 return NS_ERROR_NOT_INITIALIZED;
299 // If we're already repeating precisely, update mTimeout now so that the
300 // new delay takes effect in the future.
301 if (!mTimeout.IsNull() && mType == TYPE_REPEATING_PRECISE)
302 mTimeout = TimeStamp::Now();
304 SetDelayInternal(aDelay);
306 if (!mFiring && gThread)
307 gThread->TimerDelayChanged(this);
309 return NS_OK;
312 NS_IMETHODIMP nsTimerImpl::GetDelay(PRUint32* aDelay)
314 *aDelay = mDelay;
315 return NS_OK;
318 NS_IMETHODIMP nsTimerImpl::SetType(PRUint32 aType)
320 mType = (PRUint8)aType;
321 // XXX if this is called, we should change the actual type.. this could effect
322 // repeating timers. we need to ensure in Fire() that if mType has changed
323 // during the callback that we don't end up with the timer in the queue twice.
324 return NS_OK;
327 NS_IMETHODIMP nsTimerImpl::GetType(PRUint32* aType)
329 *aType = mType;
330 return NS_OK;
334 NS_IMETHODIMP nsTimerImpl::GetClosure(void** aClosure)
336 *aClosure = mClosure;
337 return NS_OK;
341 NS_IMETHODIMP nsTimerImpl::GetCallback(nsITimerCallback **aCallback)
343 if (mCallbackType == CALLBACK_TYPE_INTERFACE)
344 NS_IF_ADDREF(*aCallback = mCallback.i);
345 else if (mTimerCallbackWhileFiring)
346 NS_ADDREF(*aCallback = mTimerCallbackWhileFiring);
347 else
348 *aCallback = nsnull;
350 return NS_OK;
354 NS_IMETHODIMP nsTimerImpl::GetTarget(nsIEventTarget** aTarget)
356 NS_IF_ADDREF(*aTarget = mEventTarget);
357 return NS_OK;
361 NS_IMETHODIMP nsTimerImpl::SetTarget(nsIEventTarget* aTarget)
363 NS_ENSURE_TRUE(mCallbackType == CALLBACK_TYPE_UNKNOWN,
364 NS_ERROR_ALREADY_INITIALIZED);
366 if (aTarget)
367 mEventTarget = aTarget;
368 else
369 mEventTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
370 return NS_OK;
374 void nsTimerImpl::Fire()
376 if (mCanceled)
377 return;
379 TimeStamp now = TimeStamp::Now();
380 #ifdef DEBUG_TIMERS
381 if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
382 TimeDuration a = now - mStart; // actual delay in intervals
383 TimeDuration b = TimeDuration::FromMilliseconds(mDelay); // expected delay in intervals
384 TimeDuration delta = (a > b) ? a - b : b - a;
385 PRUint32 d = delta.ToMilliseconds(); // delta in ms
386 sDeltaSum += d;
387 sDeltaSumSquared += double(d) * double(d);
388 sDeltaNum++;
390 PR_LOG(gTimerLog, PR_LOG_DEBUG, ("[this=%p] expected delay time %4ums\n", this, mDelay));
391 PR_LOG(gTimerLog, PR_LOG_DEBUG, ("[this=%p] actual delay time %fms\n", this, a.ToMilliseconds()));
392 PR_LOG(gTimerLog, PR_LOG_DEBUG, ("[this=%p] (mType is %d) -------\n", this, mType));
393 PR_LOG(gTimerLog, PR_LOG_DEBUG, ("[this=%p] delta %4dms\n", this, (a > b) ? (PRInt32)d : -(PRInt32)d));
395 mStart = mStart2;
396 mStart2 = TimeStamp();
398 #endif
400 TimeStamp timeout = mTimeout;
401 if (mType == TYPE_REPEATING_PRECISE) {
402 // Precise repeating timers advance mTimeout by mDelay without fail before
403 // calling Fire().
404 timeout -= TimeDuration::FromMilliseconds(mDelay);
406 if (gThread)
407 gThread->UpdateFilter(mDelay, timeout, now);
409 if (mCallbackType == CALLBACK_TYPE_INTERFACE)
410 mTimerCallbackWhileFiring = mCallback.i;
411 mFiring = PR_TRUE;
413 // Handle callbacks that re-init the timer, but avoid leaking.
414 // See bug 330128.
415 CallbackUnion callback = mCallback;
416 PRUintn callbackType = mCallbackType;
417 if (callbackType == CALLBACK_TYPE_INTERFACE)
418 NS_ADDREF(callback.i);
419 else if (callbackType == CALLBACK_TYPE_OBSERVER)
420 NS_ADDREF(callback.o);
421 ReleaseCallback();
423 switch (callbackType) {
424 case CALLBACK_TYPE_FUNC:
425 callback.c(this, mClosure);
426 break;
427 case CALLBACK_TYPE_INTERFACE:
428 callback.i->Notify(this);
429 break;
430 case CALLBACK_TYPE_OBSERVER:
431 callback.o->Observe(static_cast<nsITimer*>(this),
432 NS_TIMER_CALLBACK_TOPIC,
433 nsnull);
434 break;
435 default:;
438 // If the callback didn't re-init the timer, and it's not a one-shot timer,
439 // restore the callback state.
440 if (mCallbackType == CALLBACK_TYPE_UNKNOWN &&
441 mType != TYPE_ONE_SHOT && !mCanceled) {
442 mCallback = callback;
443 mCallbackType = callbackType;
444 } else {
445 // The timer was a one-shot, or the callback was reinitialized.
446 if (callbackType == CALLBACK_TYPE_INTERFACE)
447 NS_RELEASE(callback.i);
448 else if (callbackType == CALLBACK_TYPE_OBSERVER)
449 NS_RELEASE(callback.o);
452 mFiring = PR_FALSE;
453 mTimerCallbackWhileFiring = nsnull;
455 #ifdef DEBUG_TIMERS
456 if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
457 PR_LOG(gTimerLog, PR_LOG_DEBUG,
458 ("[this=%p] Took %fms to fire timer callback\n",
459 this, (TimeStamp::Now() - now).ToMilliseconds()));
461 #endif
463 // Reschedule REPEATING_SLACK timers, but make sure that we aren't armed
464 // already (which can happen if the callback reinitialized the timer).
465 if (mType == TYPE_REPEATING_SLACK && !mArmed) {
466 SetDelayInternal(mDelay); // force mTimeout to be recomputed.
467 if (gThread)
468 gThread->AddTimer(this);
473 class nsTimerEvent : public nsRunnable {
474 public:
475 NS_IMETHOD Run();
477 nsTimerEvent(nsTimerImpl *timer, PRInt32 generation)
478 : mTimer(timer), mGeneration(generation) {
479 // timer is already addref'd for us
480 MOZ_COUNT_CTOR(nsTimerEvent);
483 #ifdef DEBUG_TIMERS
484 TimeStamp mInitTime;
485 #endif
487 private:
488 ~nsTimerEvent() {
489 #ifdef DEBUG
490 if (mTimer)
491 NS_WARNING("leaking reference to nsTimerImpl");
492 #endif
493 MOZ_COUNT_DTOR(nsTimerEvent);
496 nsTimerImpl *mTimer;
497 PRInt32 mGeneration;
500 NS_IMETHODIMP nsTimerEvent::Run()
502 nsRefPtr<nsTimerImpl> timer;
503 timer.swap(mTimer);
505 if (mGeneration != timer->GetGeneration())
506 return NS_OK;
508 #ifdef DEBUG_TIMERS
509 if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
510 TimeStamp now = TimeStamp::Now();
511 PR_LOG(gTimerLog, PR_LOG_DEBUG,
512 ("[this=%p] time between PostTimerEvent() and Fire(): %fms\n",
513 this, (now - mInitTime).ToMilliseconds()));
515 #endif
517 timer->Fire();
519 return NS_OK;
522 nsresult nsTimerImpl::PostTimerEvent()
524 // XXX we may want to reuse this nsTimerEvent in the case of repeating timers.
526 // Since TimerThread addref'd 'this' for us, we don't need to addref here.
527 // We will release in destroyMyEvent. We need to copy the generation number
528 // from this timer into the event, so we can avoid firing a timer that was
529 // re-initialized after being canceled.
531 nsRefPtr<nsTimerEvent> event = new nsTimerEvent(this, mGeneration);
532 if (!event)
533 return NS_ERROR_OUT_OF_MEMORY;
535 #ifdef DEBUG_TIMERS
536 if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
537 event->mInitTime = TimeStamp::Now();
539 #endif
541 // If this is a repeating precise timer, we need to calculate the time for
542 // the next timer to fire before we make the callback.
543 if (mType == TYPE_REPEATING_PRECISE) {
544 SetDelayInternal(mDelay);
545 if (gThread) {
546 nsresult rv = gThread->AddTimer(this);
547 if (NS_FAILED(rv))
548 return rv;
552 nsresult rv = mEventTarget->Dispatch(event, NS_DISPATCH_NORMAL);
553 if (NS_FAILED(rv) && gThread)
554 gThread->RemoveTimer(this);
555 return rv;
558 void nsTimerImpl::SetDelayInternal(PRUint32 aDelay)
560 TimeDuration delayInterval = TimeDuration::FromMilliseconds(aDelay);
562 mDelay = aDelay;
564 TimeStamp now = TimeStamp::Now();
565 if (mTimeout.IsNull() || mType != TYPE_REPEATING_PRECISE)
566 mTimeout = now;
568 mTimeout += delayInterval;
570 #ifdef DEBUG_TIMERS
571 if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
572 if (mStart.IsNull())
573 mStart = now;
574 else
575 mStart2 = now;
577 #endif
580 // NOT FOR PUBLIC CONSUMPTION!
581 nsresult
582 NS_NewTimer(nsITimer* *aResult, nsTimerCallbackFunc aCallback, void *aClosure,
583 PRUint32 aDelay, PRUint32 aType)
585 nsTimerImpl* timer = new nsTimerImpl();
586 if (timer == nsnull)
587 return NS_ERROR_OUT_OF_MEMORY;
588 NS_ADDREF(timer);
590 nsresult rv = timer->InitWithFuncCallback(aCallback, aClosure,
591 aDelay, aType);
592 if (NS_FAILED(rv)) {
593 NS_RELEASE(timer);
594 return rv;
597 *aResult = timer;
598 return NS_OK;