On x86 compilers without fastcall, simulate it when invoking traces and un-simulate...
[wine-gecko.git] / xpcom / threads / nsTimerImpl.cpp
blobd2f9177331f83ab535d0243c32d7a9597fdcac48
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 "nsVoidArray.h"
46 #include "nsThreadManager.h"
47 #include "nsThreadUtils.h"
48 #include "prmem.h"
50 static PRInt32 gGenerator = 0;
51 static TimerThread* gThread = nsnull;
53 #ifdef DEBUG_TIMERS
54 #include <math.h>
56 double nsTimerImpl::sDeltaSumSquared = 0;
57 double nsTimerImpl::sDeltaSum = 0;
58 double nsTimerImpl::sDeltaNum = 0;
60 static void
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)
69 var = 0.0;
70 else
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;
75 *meanResult = mean;
76 *stdDevResult = stdDev;
78 #endif
80 NS_IMPL_THREADSAFE_QUERY_INTERFACE1(nsTimerImpl, nsITimer)
81 NS_IMPL_THREADSAFE_ADDREF(nsTimerImpl)
83 NS_IMETHODIMP_(nsrefcnt) nsTimerImpl::Release(void)
85 nsrefcnt count;
87 NS_PRECONDITION(0 != mRefCnt, "dup release");
88 count = PR_AtomicDecrement((PRInt32 *)&mRefCnt);
89 NS_LOG_RELEASE(this, count, "nsTimerImpl");
90 if (count == 0) {
91 mRefCnt = 1; /* stabilize */
93 /* enable this to find non-threadsafe destructors: */
94 /* NS_ASSERT_OWNINGTHREAD(nsTimerImpl); */
95 NS_DELETEXPCOM(this);
96 return 0;
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) {
129 mCanceled = PR_TRUE;
131 NS_ASSERTION(gThread, "An armed timer exists after the thread timer stopped.");
132 if (NS_SUCCEEDED(gThread->RemoveTimer(this)))
133 return 0;
136 return count;
139 nsTimerImpl::nsTimerImpl() :
140 mClosure(nsnull),
141 mCallbackType(CALLBACK_TYPE_UNKNOWN),
142 mFiring(PR_FALSE),
143 mArmed(PR_FALSE),
144 mCanceled(PR_FALSE),
145 mGeneration(0),
146 mDelay(0),
147 mTimeout(0)
149 // XXXbsmedberg: shouldn't this be in Init()?
150 mEventTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
152 mCallback.c = nsnull;
154 #ifdef DEBUG_TIMERS
155 mStart = 0;
156 mStart2 = 0;
157 #endif
160 nsTimerImpl::~nsTimerImpl()
162 ReleaseCallback();
165 //static
166 nsresult
167 nsTimerImpl::Startup()
169 nsresult rv;
171 gThread = new TimerThread();
172 if (!gThread) return NS_ERROR_OUT_OF_MEMORY;
174 NS_ADDREF(gThread);
175 rv = gThread->InitLocks();
177 if (NS_FAILED(rv)) {
178 NS_RELEASE(gThread);
181 return rv;
184 void nsTimerImpl::Shutdown()
186 #ifdef DEBUG_TIMERS
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));
194 #endif
196 if (!gThread)
197 return;
199 gThread->Shutdown();
200 NS_RELEASE(gThread);
204 nsresult nsTimerImpl::InitCommon(PRUint32 aType, PRUint32 aDelay)
206 nsresult rv;
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.
227 if (mArmed)
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,
239 void *aClosure,
240 PRUint32 aDelay,
241 PRUint32 aType)
243 NS_ENSURE_ARG_POINTER(aFunc);
245 ReleaseCallback();
246 mCallbackType = CALLBACK_TYPE_FUNC;
247 mCallback.c = aFunc;
248 mClosure = aClosure;
250 return InitCommon(aType, aDelay);
253 NS_IMETHODIMP nsTimerImpl::InitWithCallback(nsITimerCallback *aCallback,
254 PRUint32 aDelay,
255 PRUint32 aType)
257 NS_ENSURE_ARG_POINTER(aCallback);
259 ReleaseCallback();
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,
268 PRUint32 aDelay,
269 PRUint32 aType)
271 NS_ENSURE_ARG_POINTER(aObserver);
273 ReleaseCallback();
274 mCallbackType = CALLBACK_TYPE_OBSERVER;
275 mCallback.o = aObserver;
276 NS_ADDREF(mCallback.o);
278 return InitCommon(aType, aDelay);
281 NS_IMETHODIMP nsTimerImpl::Cancel()
283 mCanceled = PR_TRUE;
285 if (gThread)
286 gThread->RemoveTimer(this);
288 ReleaseCallback();
290 return NS_OK;
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);
305 return NS_OK;
308 NS_IMETHODIMP nsTimerImpl::GetDelay(PRUint32* aDelay)
310 *aDelay = mDelay;
311 return NS_OK;
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.
320 return NS_OK;
323 NS_IMETHODIMP nsTimerImpl::GetType(PRUint32* aType)
325 *aType = mType;
326 return NS_OK;
330 NS_IMETHODIMP nsTimerImpl::GetClosure(void** aClosure)
332 *aClosure = mClosure;
333 return NS_OK;
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);
343 else
344 *aCallback = nsnull;
346 return NS_OK;
350 NS_IMETHODIMP nsTimerImpl::GetTarget(nsIEventTarget** aTarget)
352 NS_IF_ADDREF(*aTarget = mEventTarget);
353 return NS_OK;
357 NS_IMETHODIMP nsTimerImpl::SetTarget(nsIEventTarget* aTarget)
359 NS_ENSURE_TRUE(mCallbackType == CALLBACK_TYPE_UNKNOWN,
360 NS_ERROR_ALREADY_INITIALIZED);
362 if (aTarget)
363 mEventTarget = aTarget;
364 else
365 mEventTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
366 return NS_OK;
370 void nsTimerImpl::Fire()
372 if (mCanceled)
373 return;
375 PRIntervalTime now = PR_IntervalNow();
376 #ifdef DEBUG_TIMERS
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
381 sDeltaSum += d;
382 sDeltaSumSquared += double(d) * double(d);
383 sDeltaNum++;
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));
390 mStart = mStart2;
391 mStart2 = 0;
393 #endif
395 PRIntervalTime timeout = mTimeout;
396 if (mType == TYPE_REPEATING_PRECISE) {
397 // Precise repeating timers advance mTimeout by mDelay without fail before
398 // calling Fire().
399 timeout -= PR_MillisecondsToInterval(mDelay);
401 if (gThread)
402 gThread->UpdateFilter(mDelay, timeout, now);
404 if (mCallbackType == CALLBACK_TYPE_INTERFACE)
405 mTimerCallbackWhileFiring = mCallback.i;
406 mFiring = PR_TRUE;
408 // Handle callbacks that re-init the timer, but avoid leaking.
409 // See bug 330128.
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);
416 ReleaseCallback();
418 switch (callbackType) {
419 case CALLBACK_TYPE_FUNC:
420 callback.c(this, mClosure);
421 break;
422 case CALLBACK_TYPE_INTERFACE:
423 callback.i->Notify(this);
424 break;
425 case CALLBACK_TYPE_OBSERVER:
426 callback.o->Observe(static_cast<nsITimer*>(this),
427 NS_TIMER_CALLBACK_TOPIC,
428 nsnull);
429 break;
430 default:;
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;
439 } else {
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);
447 mFiring = PR_FALSE;
448 mTimerCallbackWhileFiring = nsnull;
450 #ifdef DEBUG_TIMERS
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)));
456 #endif
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.
462 if (gThread)
463 gThread->AddTimer(this);
468 class nsTimerEvent : public nsRunnable {
469 public:
470 NS_IMETHOD Run();
472 nsTimerEvent(nsTimerImpl *timer, PRInt32 generation)
473 : mTimer(timer), mGeneration(generation) {
474 // timer is already addref'd for us
475 MOZ_COUNT_CTOR(nsTimerEvent);
478 #ifdef DEBUG_TIMERS
479 PRIntervalTime mInitTime;
480 #endif
482 private:
483 ~nsTimerEvent() {
484 #ifdef DEBUG
485 if (mTimer)
486 NS_WARNING("leaking reference to nsTimerImpl");
487 #endif
488 MOZ_COUNT_DTOR(nsTimerEvent);
491 nsTimerImpl *mTimer;
492 PRInt32 mGeneration;
495 NS_IMETHODIMP nsTimerEvent::Run()
497 nsRefPtr<nsTimerImpl> timer;
498 timer.swap(mTimer);
500 if (mGeneration != timer->GetGeneration())
501 return NS_OK;
503 #ifdef DEBUG_TIMERS
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)));
510 #endif
512 timer->Fire();
514 return NS_OK;
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);
527 if (!event)
528 return NS_ERROR_OUT_OF_MEMORY;
530 #ifdef DEBUG_TIMERS
531 if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
532 event->mInitTime = PR_IntervalNow();
534 #endif
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);
540 if (gThread) {
541 nsresult rv = gThread->AddTimer(this);
542 if (NS_FAILED(rv))
543 return rv;
547 nsresult rv = mEventTarget->Dispatch(event, NS_DISPATCH_NORMAL);
548 if (NS_FAILED(rv) && gThread)
549 gThread->RemoveTimer(this);
550 return rv;
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);
561 mDelay = aDelay;
563 PRIntervalTime now = PR_IntervalNow();
564 if (mTimeout == 0 || mType != TYPE_REPEATING_PRECISE)
565 mTimeout = now;
567 mTimeout += delayInterval;
569 #ifdef DEBUG_TIMERS
570 if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
571 if (mStart == 0)
572 mStart = now;
573 else
574 mStart2 = now;
576 #endif
579 // NOT FOR PUBLIC CONSUMPTION!
580 nsresult
581 NS_NewTimer(nsITimer* *aResult, nsTimerCallbackFunc aCallback, void *aClosure,
582 PRUint32 aDelay, PRUint32 aType)
584 nsTimerImpl* timer = new nsTimerImpl();
585 if (timer == nsnull)
586 return NS_ERROR_OUT_OF_MEMORY;
587 NS_ADDREF(timer);
589 nsresult rv = timer->InitWithFuncCallback(aCallback, aClosure,
590 aDelay, aType);
591 if (NS_FAILED(rv)) {
592 NS_RELEASE(timer);
593 return rv;
596 *aResult = timer;
597 return NS_OK;