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
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.
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 "nsVoidArray.h"
46 #include "nsThreadManager.h"
47 #include "nsThreadUtils.h"
50 static PRInt32 gGenerator
= 0;
51 static TimerThread
* gThread
= nsnull
;
56 double nsTimerImpl::sDeltaSumSquared
= 0;
57 double nsTimerImpl::sDeltaSum
= 0;
58 double nsTimerImpl::sDeltaNum
= 0;
61 myNS_MeanAndStdDev(double n
, double sumOfValues
, double sumOfSquaredValues
,
62 double *meanResult
, double *stdDevResult
)
64 double mean
= 0.0, var
= 0.0, stdDev
= 0.0;
65 if (n
> 0.0 && sumOfValues
>= 0) {
66 mean
= sumOfValues
/ n
;
67 double temp
= (n
* sumOfSquaredValues
) - (sumOfValues
* sumOfValues
);
68 if (temp
< 0.0 || n
<= 1)
71 var
= temp
/ (n
* (n
- 1));
72 // for some reason, Windows says sqrt(0.0) is "-1.#J" (?!) so do this:
73 stdDev
= var
!= 0.0 ? sqrt(var
) : 0.0;
76 *stdDevResult
= stdDev
;
80 NS_IMPL_THREADSAFE_QUERY_INTERFACE1(nsTimerImpl
, nsITimer
)
81 NS_IMPL_THREADSAFE_ADDREF(nsTimerImpl
)
83 NS_IMETHODIMP_(nsrefcnt
) nsTimerImpl::Release(void)
87 NS_PRECONDITION(0 != mRefCnt
, "dup release");
88 count
= PR_AtomicDecrement((PRInt32
*)&mRefCnt
);
89 NS_LOG_RELEASE(this, count
, "nsTimerImpl");
91 mRefCnt
= 1; /* stabilize */
93 /* enable this to find non-threadsafe destructors: */
94 /* NS_ASSERT_OWNINGTHREAD(nsTimerImpl); */
99 // If only one reference remains, and mArmed is set, then the ref must be
100 // from the TimerThread::mTimers array, so we Cancel this timer to remove
101 // the mTimers element, and return 0 if Cancel in fact disarmed the timer.
103 // We use an inlined version of nsTimerImpl::Cancel here to check for the
104 // NS_ERROR_NOT_AVAILABLE code returned by gThread->RemoveTimer when this
105 // timer is not found in the mTimers array -- i.e., when the timer was not
106 // in fact armed once we acquired TimerThread::mLock, in spite of mArmed
107 // being true here. That can happen if the armed timer is being fired by
108 // TimerThread::Run as we race and test mArmed just before it is cleared by
109 // the timer thread. If the RemoveTimer call below doesn't find this timer
110 // in the mTimers array, then the last ref to this timer is held manually
111 // and temporarily by the TimerThread, so we should fall through to the
112 // final return and return 1, not 0.
114 // The original version of this thread-based timer code kept weak refs from
115 // TimerThread::mTimers, removing this timer's weak ref in the destructor,
116 // but that leads to double-destructions in the race described above, and
117 // adding mArmed doesn't help, because destructors can't be deferred, once
118 // begun. But by combining reference-counting and a specialized Release
119 // method with "is this timer still in the mTimers array once we acquire
120 // the TimerThread's lock" testing, we defer destruction until we're sure
121 // that only one thread has its hot little hands on this timer.
123 // Note that both approaches preclude a timer creator, and everyone else
124 // except the TimerThread who might have a strong ref, from dropping all
125 // their strong refs without implicitly canceling the timer. Timers need
126 // non-mTimers-element strong refs to stay alive.
128 if (count
== 1 && mArmed
) {
131 NS_ASSERTION(gThread
, "An armed timer exists after the thread timer stopped.");
132 if (NS_SUCCEEDED(gThread
->RemoveTimer(this)))
139 nsTimerImpl::nsTimerImpl() :
141 mCallbackType(CALLBACK_TYPE_UNKNOWN
),
149 // XXXbsmedberg: shouldn't this be in Init()?
150 mEventTarget
= static_cast<nsIEventTarget
*>(NS_GetCurrentThread());
152 mCallback
.c
= nsnull
;
160 nsTimerImpl::~nsTimerImpl()
167 nsTimerImpl::Startup()
171 gThread
= new TimerThread();
172 if (!gThread
) return NS_ERROR_OUT_OF_MEMORY
;
175 rv
= gThread
->InitLocks();
184 void nsTimerImpl::Shutdown()
187 if (PR_LOG_TEST(gTimerLog
, PR_LOG_DEBUG
)) {
188 double mean
= 0, stddev
= 0;
189 myNS_MeanAndStdDev(sDeltaNum
, sDeltaSum
, sDeltaSumSquared
, &mean
, &stddev
);
191 PR_LOG(gTimerLog
, PR_LOG_DEBUG
, ("sDeltaNum = %f, sDeltaSum = %f, sDeltaSumSquared = %f\n", sDeltaNum
, sDeltaSum
, sDeltaSumSquared
));
192 PR_LOG(gTimerLog
, PR_LOG_DEBUG
, ("mean: %fms, stddev: %fms\n", mean
, stddev
));
204 nsresult
nsTimerImpl::InitCommon(PRUint32 aType
, PRUint32 aDelay
)
208 NS_ENSURE_TRUE(gThread
, NS_ERROR_NOT_INITIALIZED
);
210 rv
= gThread
->Init();
211 NS_ENSURE_SUCCESS(rv
, rv
);
214 * In case of re-Init, both with and without a preceding Cancel, clear the
215 * mCanceled flag and assign a new mGeneration. But first, remove any armed
216 * timer from the timer thread's list.
218 * If we are racing with the timer thread to remove this timer and we lose,
219 * the RemoveTimer call made here will fail to find this timer in the timer
220 * thread's list, and will return false harmlessly. We test mArmed here to
221 * avoid the small overhead in RemoveTimer of locking the timer thread and
222 * checking its list for this timer. It's safe to test mArmed even though
223 * it might be cleared on another thread in the next cycle (or even already
224 * be cleared by another CPU whose store hasn't reached our CPU's cache),
225 * because RemoveTimer is idempotent.
228 gThread
->RemoveTimer(this);
229 mCanceled
= PR_FALSE
;
230 mGeneration
= PR_AtomicIncrement(&gGenerator
);
232 mType
= (PRUint8
)aType
;
233 SetDelayInternal(aDelay
);
235 return gThread
->AddTimer(this);
238 NS_IMETHODIMP
nsTimerImpl::InitWithFuncCallback(nsTimerCallbackFunc aFunc
,
243 NS_ENSURE_ARG_POINTER(aFunc
);
246 mCallbackType
= CALLBACK_TYPE_FUNC
;
250 return InitCommon(aType
, aDelay
);
253 NS_IMETHODIMP
nsTimerImpl::InitWithCallback(nsITimerCallback
*aCallback
,
257 NS_ENSURE_ARG_POINTER(aCallback
);
260 mCallbackType
= CALLBACK_TYPE_INTERFACE
;
261 mCallback
.i
= aCallback
;
262 NS_ADDREF(mCallback
.i
);
264 return InitCommon(aType
, aDelay
);
267 NS_IMETHODIMP
nsTimerImpl::Init(nsIObserver
*aObserver
,
271 NS_ENSURE_ARG_POINTER(aObserver
);
274 mCallbackType
= CALLBACK_TYPE_OBSERVER
;
275 mCallback
.o
= aObserver
;
276 NS_ADDREF(mCallback
.o
);
278 return InitCommon(aType
, aDelay
);
281 NS_IMETHODIMP
nsTimerImpl::Cancel()
286 gThread
->RemoveTimer(this);
293 NS_IMETHODIMP
nsTimerImpl::SetDelay(PRUint32 aDelay
)
295 // If we're already repeating precisely, update mTimeout now so that the
296 // new delay takes effect in the future.
297 if (mTimeout
!= 0 && mType
== TYPE_REPEATING_PRECISE
)
298 mTimeout
= PR_IntervalNow();
300 SetDelayInternal(aDelay
);
302 if (!mFiring
&& gThread
)
303 gThread
->TimerDelayChanged(this);
308 NS_IMETHODIMP
nsTimerImpl::GetDelay(PRUint32
* aDelay
)
314 NS_IMETHODIMP
nsTimerImpl::SetType(PRUint32 aType
)
316 mType
= (PRUint8
)aType
;
317 // XXX if this is called, we should change the actual type.. this could effect
318 // repeating timers. we need to ensure in Fire() that if mType has changed
319 // during the callback that we don't end up with the timer in the queue twice.
323 NS_IMETHODIMP
nsTimerImpl::GetType(PRUint32
* aType
)
330 NS_IMETHODIMP
nsTimerImpl::GetClosure(void** aClosure
)
332 *aClosure
= mClosure
;
337 NS_IMETHODIMP
nsTimerImpl::GetCallback(nsITimerCallback
**aCallback
)
339 if (mCallbackType
== CALLBACK_TYPE_INTERFACE
)
340 NS_IF_ADDREF(*aCallback
= mCallback
.i
);
341 else if (mTimerCallbackWhileFiring
)
342 NS_ADDREF(*aCallback
= mTimerCallbackWhileFiring
);
350 NS_IMETHODIMP
nsTimerImpl::GetTarget(nsIEventTarget
** aTarget
)
352 NS_IF_ADDREF(*aTarget
= mEventTarget
);
357 NS_IMETHODIMP
nsTimerImpl::SetTarget(nsIEventTarget
* aTarget
)
359 NS_ENSURE_TRUE(mCallbackType
== CALLBACK_TYPE_UNKNOWN
,
360 NS_ERROR_ALREADY_INITIALIZED
);
363 mEventTarget
= aTarget
;
365 mEventTarget
= static_cast<nsIEventTarget
*>(NS_GetCurrentThread());
370 void nsTimerImpl::Fire()
375 PRIntervalTime now
= PR_IntervalNow();
377 if (PR_LOG_TEST(gTimerLog
, PR_LOG_DEBUG
)) {
378 PRIntervalTime a
= now
- mStart
; // actual delay in intervals
379 PRUint32 b
= PR_MillisecondsToInterval(mDelay
); // expected delay in intervals
380 PRUint32 d
= PR_IntervalToMilliseconds((a
> b
) ? a
- b
: b
- a
); // delta in ms
382 sDeltaSumSquared
+= double(d
) * double(d
);
385 PR_LOG(gTimerLog
, PR_LOG_DEBUG
, ("[this=%p] expected delay time %4dms\n", this, mDelay
));
386 PR_LOG(gTimerLog
, PR_LOG_DEBUG
, ("[this=%p] actual delay time %4dms\n", this, PR_IntervalToMilliseconds(a
)));
387 PR_LOG(gTimerLog
, PR_LOG_DEBUG
, ("[this=%p] (mType is %d) -------\n", this, mType
));
388 PR_LOG(gTimerLog
, PR_LOG_DEBUG
, ("[this=%p] delta %4dms\n", this, (a
> b
) ? (PRInt32
)d
: -(PRInt32
)d
));
395 PRIntervalTime timeout
= mTimeout
;
396 if (mType
== TYPE_REPEATING_PRECISE
) {
397 // Precise repeating timers advance mTimeout by mDelay without fail before
399 timeout
-= PR_MillisecondsToInterval(mDelay
);
402 gThread
->UpdateFilter(mDelay
, timeout
, now
);
404 if (mCallbackType
== CALLBACK_TYPE_INTERFACE
)
405 mTimerCallbackWhileFiring
= mCallback
.i
;
408 // Handle callbacks that re-init the timer, but avoid leaking.
410 CallbackUnion callback
= mCallback
;
411 PRUintn callbackType
= mCallbackType
;
412 if (callbackType
== CALLBACK_TYPE_INTERFACE
)
413 NS_ADDREF(callback
.i
);
414 else if (callbackType
== CALLBACK_TYPE_OBSERVER
)
415 NS_ADDREF(callback
.o
);
418 switch (callbackType
) {
419 case CALLBACK_TYPE_FUNC
:
420 callback
.c(this, mClosure
);
422 case CALLBACK_TYPE_INTERFACE
:
423 callback
.i
->Notify(this);
425 case CALLBACK_TYPE_OBSERVER
:
426 callback
.o
->Observe(static_cast<nsITimer
*>(this),
427 NS_TIMER_CALLBACK_TOPIC
,
433 // If the callback didn't re-init the timer, and it's not a one-shot timer,
434 // restore the callback state.
435 if (mCallbackType
== CALLBACK_TYPE_UNKNOWN
&&
436 mType
!= TYPE_ONE_SHOT
&& !mCanceled
) {
437 mCallback
= callback
;
438 mCallbackType
= callbackType
;
440 // The timer was a one-shot, or the callback was reinitialized.
441 if (callbackType
== CALLBACK_TYPE_INTERFACE
)
442 NS_RELEASE(callback
.i
);
443 else if (callbackType
== CALLBACK_TYPE_OBSERVER
)
444 NS_RELEASE(callback
.o
);
448 mTimerCallbackWhileFiring
= nsnull
;
451 if (PR_LOG_TEST(gTimerLog
, PR_LOG_DEBUG
)) {
452 PR_LOG(gTimerLog
, PR_LOG_DEBUG
,
453 ("[this=%p] Took %dms to fire timer callback\n",
454 this, PR_IntervalToMilliseconds(PR_IntervalNow() - now
)));
458 // Reschedule REPEATING_SLACK timers, but make sure that we aren't armed
459 // already (which can happen if the callback reinitialized the timer).
460 if (mType
== TYPE_REPEATING_SLACK
&& !mArmed
) {
461 SetDelayInternal(mDelay
); // force mTimeout to be recomputed.
463 gThread
->AddTimer(this);
468 class nsTimerEvent
: public nsRunnable
{
472 nsTimerEvent(nsTimerImpl
*timer
, PRInt32 generation
)
473 : mTimer(timer
), mGeneration(generation
) {
474 // timer is already addref'd for us
475 MOZ_COUNT_CTOR(nsTimerEvent
);
479 PRIntervalTime mInitTime
;
486 NS_WARNING("leaking reference to nsTimerImpl");
488 MOZ_COUNT_DTOR(nsTimerEvent
);
495 NS_IMETHODIMP
nsTimerEvent::Run()
497 nsRefPtr
<nsTimerImpl
> timer
;
500 if (mGeneration
!= timer
->GetGeneration())
504 if (PR_LOG_TEST(gTimerLog
, PR_LOG_DEBUG
)) {
505 PRIntervalTime now
= PR_IntervalNow();
506 PR_LOG(gTimerLog
, PR_LOG_DEBUG
,
507 ("[this=%p] time between PostTimerEvent() and Fire(): %dms\n",
508 this, PR_IntervalToMilliseconds(now
- mInitTime
)));
517 nsresult
nsTimerImpl::PostTimerEvent()
519 // XXX we may want to reuse this nsTimerEvent in the case of repeating timers.
521 // Since TimerThread addref'd 'this' for us, we don't need to addref here.
522 // We will release in destroyMyEvent. We need to copy the generation number
523 // from this timer into the event, so we can avoid firing a timer that was
524 // re-initialized after being canceled.
526 nsRefPtr
<nsTimerEvent
> event
= new nsTimerEvent(this, mGeneration
);
528 return NS_ERROR_OUT_OF_MEMORY
;
531 if (PR_LOG_TEST(gTimerLog
, PR_LOG_DEBUG
)) {
532 event
->mInitTime
= PR_IntervalNow();
536 // If this is a repeating precise timer, we need to calculate the time for
537 // the next timer to fire before we make the callback.
538 if (mType
== TYPE_REPEATING_PRECISE
) {
539 SetDelayInternal(mDelay
);
541 nsresult rv
= gThread
->AddTimer(this);
547 nsresult rv
= mEventTarget
->Dispatch(event
, NS_DISPATCH_NORMAL
);
548 if (NS_FAILED(rv
) && gThread
)
549 gThread
->RemoveTimer(this);
553 void nsTimerImpl::SetDelayInternal(PRUint32 aDelay
)
555 PRIntervalTime delayInterval
= PR_MillisecondsToInterval(aDelay
);
556 if (delayInterval
> DELAY_INTERVAL_MAX
) {
557 delayInterval
= DELAY_INTERVAL_MAX
;
558 aDelay
= PR_IntervalToMilliseconds(delayInterval
);
563 PRIntervalTime now
= PR_IntervalNow();
564 if (mTimeout
== 0 || mType
!= TYPE_REPEATING_PRECISE
)
567 mTimeout
+= delayInterval
;
570 if (PR_LOG_TEST(gTimerLog
, PR_LOG_DEBUG
)) {
579 // NOT FOR PUBLIC CONSUMPTION!
581 NS_NewTimer(nsITimer
* *aResult
, nsTimerCallbackFunc aCallback
, void *aClosure
,
582 PRUint32 aDelay
, PRUint32 aType
)
584 nsTimerImpl
* timer
= new nsTimerImpl();
586 return NS_ERROR_OUT_OF_MEMORY
;
589 nsresult rv
= timer
->InitWithFuncCallback(aCallback
, aClosure
,