From fdc2d7dc320f19883b9830164d7e69cb7fc4efbc Mon Sep 17 00:00:00 2001 From: "pinkerton%netscape.com" Date: Fri, 31 Oct 2003 02:31:13 +0000 Subject: [PATCH] don't process timers after wakeup (r=sfraser, sr=bryner, bug 197863) --- xpcom/threads/TimerThread.cpp | 176 ++++++++++++++++++++++------------ xpcom/threads/TimerThread.h | 15 ++- 2 files changed, 129 insertions(+), 62 deletions(-) diff --git a/xpcom/threads/TimerThread.cpp b/xpcom/threads/TimerThread.cpp index 33094f5aaecd..120f921c35ad 100644 --- a/xpcom/threads/TimerThread.cpp +++ b/xpcom/threads/TimerThread.cpp @@ -39,15 +39,17 @@ #include "nsAutoLock.h" #include "pratom.h" +#include "nsIObserverService.h" #include "nsIServiceManager.h" -NS_IMPL_THREADSAFE_ISUPPORTS1(TimerThread, nsIRunnable) +NS_IMPL_THREADSAFE_ISUPPORTS3(TimerThread, nsIRunnable, nsISupportsWeakReference, nsIObserver) TimerThread::TimerThread() : mLock(nsnull), mCondVar(nsnull), mShutdown(PR_FALSE), mWaiting(PR_FALSE), + mSleeping(PR_FALSE), mDelayLineCounter(0), mMinTimerPeriod(0), mTimeoutAdjustment(0) @@ -68,6 +70,13 @@ TimerThread::~TimerThread() nsTimerImpl *timer = NS_STATIC_CAST(nsTimerImpl *, mTimers[n]); NS_RELEASE(timer); } + + nsCOMPtr observerService = do_GetService("@mozilla.org/observer-service;1"); + if (observerService) { + observerService->RemoveObserver(this, "sleep_notification"); + observerService->RemoveObserver(this, "wake_notification"); + } + } nsresult TimerThread::Init() @@ -95,7 +104,16 @@ nsresult TimerThread::Init() PR_JOINABLE_THREAD, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD); + if (NS_FAILED(rv)) + return rv; + nsCOMPtr observerService = do_GetService("@mozilla.org/observer-service;1", &rv); + if (NS_FAILED(rv)) + return rv; + + observerService->AddObserver(this, "sleep_notification", PR_TRUE); + observerService->AddObserver(this, "wake_notification", PR_TRUE); + return rv; } @@ -185,78 +203,83 @@ NS_IMETHODIMP TimerThread::Run() nsAutoLock lock(mLock); while (!mShutdown) { - PRIntervalTime now = PR_IntervalNow(); - nsTimerImpl *timer = nsnull; + PRIntervalTime waitFor; + + if (mSleeping) + waitFor = PR_MillisecondsToInterval(100); // sleep for 0.1 seconds while not firing timers + else { + waitFor = PR_INTERVAL_NO_TIMEOUT; + PRIntervalTime now = PR_IntervalNow(); + nsTimerImpl *timer = nsnull; - if (mTimers.Count() > 0) { - timer = NS_STATIC_CAST(nsTimerImpl*, mTimers[0]); + if (mTimers.Count() > 0) { + timer = NS_STATIC_CAST(nsTimerImpl*, mTimers[0]); - if (!TIMER_LESS_THAN(now, timer->mTimeout + mTimeoutAdjustment)) { - next: - // NB: AddRef before the Release under RemoveTimerInternal to avoid - // mRefCnt passing through zero, in case all other refs than the one - // from mTimers have gone away (the last non-mTimers[i]-ref's Release - // must be racing with us, blocked in gThread->RemoveTimer waiting - // for TimerThread::mLock, under nsTimerImpl::Release. + if (!TIMER_LESS_THAN(now, timer->mTimeout + mTimeoutAdjustment)) { + next: + // NB: AddRef before the Release under RemoveTimerInternal to avoid + // mRefCnt passing through zero, in case all other refs than the one + // from mTimers have gone away (the last non-mTimers[i]-ref's Release + // must be racing with us, blocked in gThread->RemoveTimer waiting + // for TimerThread::mLock, under nsTimerImpl::Release. - NS_ADDREF(timer); - RemoveTimerInternal(timer); + NS_ADDREF(timer); + RemoveTimerInternal(timer); - // We release mLock around the Fire call, of course, to avoid deadlock. - lock.unlock(); + // We release mLock around the Fire call, of course, to avoid deadlock. + lock.unlock(); #ifdef DEBUG_TIMERS - if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) { - PR_LOG(gTimerLog, PR_LOG_DEBUG, - ("Timer thread woke up %dms from when it was supposed to\n", - (now >= timer->mTimeout) - ? PR_IntervalToMilliseconds(now - timer->mTimeout) - : -(PRInt32)PR_IntervalToMilliseconds(timer->mTimeout - now)) - ); + if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) { + PR_LOG(gTimerLog, PR_LOG_DEBUG, + ("Timer thread woke up %dms from when it was supposed to\n", + (now >= timer->mTimeout) + ? PR_IntervalToMilliseconds(now - timer->mTimeout) + : -(PRInt32)PR_IntervalToMilliseconds(timer->mTimeout - now)) + ); + } +#endif + + // We are going to let the call to PostTimerEvent here handle the release of the + // timer so that we don't end up releasing the timer on the TimerThread + // instead of on the thread it targets. + timer->PostTimerEvent(); + timer = nsnull; + + lock.lock(); + if (mShutdown) + break; + + // Update now, as PostTimerEvent plus the locking may have taken a tick or two, + // and we may goto next below. + now = PR_IntervalNow(); } -#endif - - // We are going to let the call to PostTimerEvent here handle the release of the - // timer so that we don't end up releasing the timer on the TimerThread - // instead of on the thread it targets. - timer->PostTimerEvent(); - timer = nsnull; - - lock.lock(); - if (mShutdown) - break; - - // Update now, as PostTimerEvent plus the locking may have taken a tick or two, - // and we may goto next below. - now = PR_IntervalNow(); } - } - PRIntervalTime waitFor = PR_INTERVAL_NO_TIMEOUT; + if (mTimers.Count() > 0) { + timer = NS_STATIC_CAST(nsTimerImpl *, mTimers[0]); - if (mTimers.Count() > 0) { - timer = NS_STATIC_CAST(nsTimerImpl *, mTimers[0]); + PRIntervalTime timeout = timer->mTimeout + mTimeoutAdjustment; - PRIntervalTime timeout = timer->mTimeout + mTimeoutAdjustment; - - // Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer is - // due now or overdue. - if (!TIMER_LESS_THAN(now, timeout)) - goto next; - waitFor = timeout - now; - } + // Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer is + // due now or overdue. + if (!TIMER_LESS_THAN(now, timeout)) + goto next; + waitFor = timeout - now; + } #ifdef DEBUG_TIMERS - if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) { - if (waitFor == PR_INTERVAL_NO_TIMEOUT) - PR_LOG(gTimerLog, PR_LOG_DEBUG, - ("waiting for PR_INTERVAL_NO_TIMEOUT\n")); - else - PR_LOG(gTimerLog, PR_LOG_DEBUG, - ("waiting for %u\n", PR_IntervalToMilliseconds(waitFor))); - } + if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) { + if (waitFor == PR_INTERVAL_NO_TIMEOUT) + PR_LOG(gTimerLog, PR_LOG_DEBUG, + ("waiting for PR_INTERVAL_NO_TIMEOUT\n")); + else + PR_LOG(gTimerLog, PR_LOG_DEBUG, + ("waiting for %u\n", PR_IntervalToMilliseconds(waitFor))); + } #endif - + } + mWaiting = PR_TRUE; PR_WaitCondVar(mCondVar, waitFor); mWaiting = PR_FALSE; @@ -352,3 +375,38 @@ PRBool TimerThread::RemoveTimerInternal(nsTimerImpl *aTimer) NS_RELEASE(aTimer); return PR_TRUE; } + +void TimerThread::DoBeforeSleep() +{ + mSleeping = PR_TRUE; +} + +void TimerThread::DoAfterSleep() +{ + for (PRInt32 i = 0; i < mTimers.Count(); i ++) + { + nsTimerImpl *timer = NS_STATIC_CAST(nsTimerImpl*, mTimers[i]); + // get and set the delay to cause its timeout to be recomputed + PRUint32 delay; + timer->GetDelay(&delay); + timer->SetDelay(delay); + } + + // nuke the stored adjustments, so they get recalibrated + mTimeoutAdjustment = 0; + mDelayLineCounter = 0; + mSleeping = PR_FALSE; +} + + +/* void observe (in nsISupports aSubject, in string aTopic, in wstring aData); */ +NS_IMETHODIMP +TimerThread::Observe(nsISupports* /* aSubject */, const char *aTopic, const PRUnichar* /* aData */) +{ + if (strcmp(aTopic, "sleep_notification") == 0) + DoBeforeSleep(); + else if (strcmp(aTopic, "wake_notification") == 0) + DoAfterSleep(); + + return NS_OK; +} diff --git a/xpcom/threads/TimerThread.h b/xpcom/threads/TimerThread.h index 7103526f9209..4eaa89bcd48d 100644 --- a/xpcom/threads/TimerThread.h +++ b/xpcom/threads/TimerThread.h @@ -36,6 +36,8 @@ #ifndef TimerThread_h___ #define TimerThread_h___ +#include "nsWeakReference.h" + #include "nsIEventQueueService.h" #include "nsIObserver.h" #include "nsIRunnable.h" @@ -49,7 +51,9 @@ #include "prinrval.h" #include "prlock.h" -class TimerThread : public nsIRunnable +class TimerThread : public nsSupportsWeakReference, + public nsIRunnable, + public nsIObserver { public: TimerThread(); @@ -57,7 +61,8 @@ public: NS_DECL_ISUPPORTS NS_DECL_NSIRUNNABLE - + NS_DECL_NSIOBSERVER + nsresult Init(); nsresult Shutdown(); @@ -74,6 +79,9 @@ public: // For use by nsTimerImpl::Fire() nsCOMPtr mEventQueueService; + void DoBeforeSleep(); + void DoAfterSleep(); + private: // These two internal helper methods must be called while mLock is held. // AddTimerInternal returns the position where the timer was added in the @@ -87,7 +95,8 @@ private: PRPackedBool mShutdown; PRPackedBool mWaiting; - + PRPackedBool mSleeping; + nsVoidArray mTimers; #define DELAY_LINE_LENGTH_LOG2 5