Bug 575870 - Enable the firefox button on xp themed, classic, and aero basic. r=dao...
[mozilla-central.git] / xpcom / threads / TimerThread.cpp
blob5f15394795a75c1f06e4da50b0b979b1064f0bdc
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"
44 #include "nsAutoLock.h"
45 #include "nsThreadUtils.h"
46 #include "pratom.h"
48 #include "nsIObserverService.h"
49 #include "nsIServiceManager.h"
50 #include "nsIProxyObjectManager.h"
51 #include "mozilla/Services.h"
53 #include <math.h>
55 NS_IMPL_THREADSAFE_ISUPPORTS2(TimerThread, nsIRunnable, nsIObserver)
57 TimerThread::TimerThread() :
58 mInitInProgress(0),
59 mInitialized(PR_FALSE),
60 mLock(nsnull),
61 mCondVar(nsnull),
62 mShutdown(PR_FALSE),
63 mWaiting(PR_FALSE),
64 mSleeping(PR_FALSE),
65 mDelayLineCounter(0),
66 mMinTimerPeriod(0)
70 TimerThread::~TimerThread()
72 if (mCondVar)
73 PR_DestroyCondVar(mCondVar);
74 if (mLock)
75 PR_DestroyLock(mLock);
77 mThread = nsnull;
79 NS_ASSERTION(mTimers.IsEmpty(), "Timers remain in TimerThread::~TimerThread");
82 nsresult
83 TimerThread::InitLocks()
85 NS_ASSERTION(!mLock, "InitLocks called twice?");
86 mLock = PR_NewLock();
87 if (!mLock)
88 return NS_ERROR_OUT_OF_MEMORY;
90 mCondVar = PR_NewCondVar(mLock);
91 if (!mCondVar)
92 return NS_ERROR_OUT_OF_MEMORY;
94 return NS_OK;
97 nsresult TimerThread::Init()
99 PR_LOG(gTimerLog, PR_LOG_DEBUG, ("TimerThread::Init [%d]\n", mInitialized));
101 if (mInitialized) {
102 if (!mThread)
103 return NS_ERROR_FAILURE;
105 return NS_OK;
108 if (PR_AtomicSet(&mInitInProgress, 1) == 0) {
109 // We hold on to mThread to keep the thread alive.
110 nsresult rv = NS_NewThread(getter_AddRefs(mThread), this);
111 if (NS_FAILED(rv)) {
112 mThread = nsnull;
114 else {
115 nsCOMPtr<nsIObserverService> observerService =
116 mozilla::services::GetObserverService();
117 // We must not use the observer service from a background thread!
118 if (observerService && !NS_IsMainThread()) {
119 nsCOMPtr<nsIObserverService> result = nsnull;
120 NS_GetProxyForObject(NS_PROXY_TO_MAIN_THREAD,
121 NS_GET_IID(nsIObserverService),
122 observerService, NS_PROXY_ASYNC,
123 getter_AddRefs(result));
124 observerService.swap(result);
126 // We'll be released at xpcom shutdown
127 if (observerService) {
128 observerService->AddObserver(this, "sleep_notification", PR_FALSE);
129 observerService->AddObserver(this, "wake_notification", PR_FALSE);
133 PR_Lock(mLock);
134 mInitialized = PR_TRUE;
135 PR_NotifyAllCondVar(mCondVar);
136 PR_Unlock(mLock);
138 else {
139 PR_Lock(mLock);
140 while (!mInitialized) {
141 PR_WaitCondVar(mCondVar, PR_INTERVAL_NO_TIMEOUT);
143 PR_Unlock(mLock);
146 if (!mThread)
147 return NS_ERROR_FAILURE;
149 return NS_OK;
152 nsresult TimerThread::Shutdown()
154 PR_LOG(gTimerLog, PR_LOG_DEBUG, ("TimerThread::Shutdown begin\n"));
156 if (!mThread)
157 return NS_ERROR_NOT_INITIALIZED;
159 nsTArray<nsTimerImpl*> timers;
160 { // lock scope
161 nsAutoLock lock(mLock);
163 mShutdown = PR_TRUE;
165 // notify the cond var so that Run() can return
166 if (mCondVar && mWaiting)
167 PR_NotifyCondVar(mCondVar);
169 // Need to copy content of mTimers array to a local array
170 // because call to timers' ReleaseCallback() (and release its self)
171 // must not be done under the lock. Destructor of a callback
172 // might potentially call some code reentering the same lock
173 // that leads to unexpected behavior or deadlock.
174 // See bug 422472.
175 timers.AppendElements(mTimers);
176 mTimers.Clear();
179 PRUint32 timersCount = timers.Length();
180 for (PRUint32 i = 0; i < timersCount; i++) {
181 nsTimerImpl *timer = timers[i];
182 timer->ReleaseCallback();
183 ReleaseTimerInternal(timer);
186 mThread->Shutdown(); // wait for the thread to die
188 PR_LOG(gTimerLog, PR_LOG_DEBUG, ("TimerThread::Shutdown end\n"));
189 return NS_OK;
192 // Keep track of how early (positive slack) or late (negative slack) timers
193 // are running, and use the filtered slack number to adaptively estimate how
194 // early timers should fire to be "on time".
195 void TimerThread::UpdateFilter(PRUint32 aDelay, TimeStamp aTimeout,
196 TimeStamp aNow)
198 TimeDuration slack = aTimeout - aNow;
199 double smoothSlack = 0;
200 PRUint32 i, filterLength;
201 static TimeDuration kFilterFeedbackMaxTicks =
202 TimeDuration::FromMilliseconds(FILTER_FEEDBACK_MAX);
203 static TimeDuration kFilterFeedbackMinTicks =
204 TimeDuration::FromMilliseconds(-FILTER_FEEDBACK_MAX);
206 if (slack > kFilterFeedbackMaxTicks)
207 slack = kFilterFeedbackMaxTicks;
208 else if (slack < kFilterFeedbackMinTicks)
209 slack = kFilterFeedbackMinTicks;
211 mDelayLine[mDelayLineCounter & DELAY_LINE_LENGTH_MASK] =
212 slack.ToMilliseconds();
213 if (++mDelayLineCounter < DELAY_LINE_LENGTH) {
214 // Startup mode: accumulate a full delay line before filtering.
215 PR_ASSERT(mTimeoutAdjustment.ToSeconds() == 0);
216 filterLength = 0;
217 } else {
218 // Past startup: compute number of filter taps based on mMinTimerPeriod.
219 if (mMinTimerPeriod == 0) {
220 mMinTimerPeriod = (aDelay != 0) ? aDelay : 1;
221 } else if (aDelay != 0 && aDelay < mMinTimerPeriod) {
222 mMinTimerPeriod = aDelay;
225 filterLength = (PRUint32) (FILTER_DURATION / mMinTimerPeriod);
226 if (filterLength > DELAY_LINE_LENGTH)
227 filterLength = DELAY_LINE_LENGTH;
228 else if (filterLength < 4)
229 filterLength = 4;
231 for (i = 1; i <= filterLength; i++)
232 smoothSlack += mDelayLine[(mDelayLineCounter-i) & DELAY_LINE_LENGTH_MASK];
233 smoothSlack /= filterLength;
235 // XXXbe do we need amplification? hacking a fudge factor, need testing...
236 mTimeoutAdjustment = TimeDuration::FromMilliseconds(smoothSlack * 1.5);
239 #ifdef DEBUG_TIMERS
240 PR_LOG(gTimerLog, PR_LOG_DEBUG,
241 ("UpdateFilter: smoothSlack = %g, filterLength = %u\n",
242 smoothSlack, filterLength));
243 #endif
246 /* void Run(); */
247 NS_IMETHODIMP TimerThread::Run()
249 nsAutoLock lock(mLock);
251 while (!mShutdown) {
252 // Have to use PRIntervalTime here, since PR_WaitCondVar takes it
253 PRIntervalTime waitFor;
255 if (mSleeping) {
256 // Sleep for 0.1 seconds while not firing timers.
257 waitFor = PR_MillisecondsToInterval(100);
258 } else {
259 waitFor = PR_INTERVAL_NO_TIMEOUT;
260 TimeStamp now = TimeStamp::Now();
261 nsTimerImpl *timer = nsnull;
263 if (!mTimers.IsEmpty()) {
264 timer = mTimers[0];
266 if (now >= timer->mTimeout + mTimeoutAdjustment) {
267 next:
268 // NB: AddRef before the Release under RemoveTimerInternal to avoid
269 // mRefCnt passing through zero, in case all other refs than the one
270 // from mTimers have gone away (the last non-mTimers[i]-ref's Release
271 // must be racing with us, blocked in gThread->RemoveTimer waiting
272 // for TimerThread::mLock, under nsTimerImpl::Release.
274 NS_ADDREF(timer);
275 RemoveTimerInternal(timer);
277 // We release mLock around the Fire call to avoid deadlock.
278 lock.unlock();
280 #ifdef DEBUG_TIMERS
281 if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
282 PR_LOG(gTimerLog, PR_LOG_DEBUG,
283 ("Timer thread woke up %fms from when it was supposed to\n",
284 fabs((now - timer->mTimeout).ToMilliseconds())));
286 #endif
288 // We are going to let the call to PostTimerEvent here handle the
289 // release of the timer so that we don't end up releasing the timer
290 // on the TimerThread instead of on the thread it targets.
291 if (NS_FAILED(timer->PostTimerEvent())) {
292 nsrefcnt rc;
293 NS_RELEASE2(timer, rc);
295 // The nsITimer interface requires that its users keep a reference
296 // to the timers they use while those timers are initialized but
297 // have not yet fired. If this ever happens, it is a bug in the
298 // code that created and used the timer.
300 // Further, note that this should never happen even with a
301 // misbehaving user, because nsTimerImpl::Release checks for a
302 // refcount of 1 with an armed timer (a timer whose only reference
303 // is from the timer thread) and when it hits this will remove the
304 // timer from the timer thread and thus destroy the last reference,
305 // preventing this situation from occurring.
306 NS_ASSERTION(rc != 0, "destroyed timer off its target thread!");
308 timer = nsnull;
310 lock.lock();
311 if (mShutdown)
312 break;
314 // Update now, as PostTimerEvent plus the locking may have taken a
315 // tick or two, and we may goto next below.
316 now = TimeStamp::Now();
320 if (!mTimers.IsEmpty()) {
321 timer = mTimers[0];
323 TimeStamp timeout = timer->mTimeout + mTimeoutAdjustment;
325 // Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer
326 // is due now or overdue.
327 if (now >= timeout)
328 goto next;
329 waitFor = PR_MillisecondsToInterval((timeout - now).ToMilliseconds());
332 #ifdef DEBUG_TIMERS
333 if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
334 if (waitFor == PR_INTERVAL_NO_TIMEOUT)
335 PR_LOG(gTimerLog, PR_LOG_DEBUG,
336 ("waiting for PR_INTERVAL_NO_TIMEOUT\n"));
337 else
338 PR_LOG(gTimerLog, PR_LOG_DEBUG,
339 ("waiting for %u\n", PR_IntervalToMilliseconds(waitFor)));
341 #endif
344 mWaiting = PR_TRUE;
345 PR_WaitCondVar(mCondVar, waitFor);
346 mWaiting = PR_FALSE;
349 return NS_OK;
352 nsresult TimerThread::AddTimer(nsTimerImpl *aTimer)
354 nsAutoLock lock(mLock);
356 // Add the timer to our list.
357 PRInt32 i = AddTimerInternal(aTimer);
358 if (i < 0)
359 return NS_ERROR_OUT_OF_MEMORY;
361 // Awaken the timer thread.
362 if (mCondVar && mWaiting && i == 0)
363 PR_NotifyCondVar(mCondVar);
365 return NS_OK;
368 nsresult TimerThread::TimerDelayChanged(nsTimerImpl *aTimer)
370 nsAutoLock lock(mLock);
372 // Our caller has a strong ref to aTimer, so it can't go away here under
373 // ReleaseTimerInternal.
374 RemoveTimerInternal(aTimer);
376 PRInt32 i = AddTimerInternal(aTimer);
377 if (i < 0)
378 return NS_ERROR_OUT_OF_MEMORY;
380 // Awaken the timer thread.
381 if (mCondVar && mWaiting && i == 0)
382 PR_NotifyCondVar(mCondVar);
384 return NS_OK;
387 nsresult TimerThread::RemoveTimer(nsTimerImpl *aTimer)
389 nsAutoLock lock(mLock);
391 // Remove the timer from our array. Tell callers that aTimer was not found
392 // by returning NS_ERROR_NOT_AVAILABLE. Unlike the TimerDelayChanged case
393 // immediately above, our caller may be passing a (now-)weak ref in via the
394 // aTimer param, specifically when nsTimerImpl::Release loses a race with
395 // TimerThread::Run, must wait for the mLock auto-lock here, and during the
396 // wait Run drops the only remaining ref to aTimer via RemoveTimerInternal.
398 if (!RemoveTimerInternal(aTimer))
399 return NS_ERROR_NOT_AVAILABLE;
401 // Awaken the timer thread.
402 if (mCondVar && mWaiting)
403 PR_NotifyCondVar(mCondVar);
405 return NS_OK;
408 // This function must be called from within a lock
409 PRInt32 TimerThread::AddTimerInternal(nsTimerImpl *aTimer)
411 if (mShutdown)
412 return -1;
414 TimeStamp now = TimeStamp::Now();
415 PRUint32 count = mTimers.Length();
416 PRUint32 i = 0;
417 for (; i < count; i++) {
418 nsTimerImpl *timer = mTimers[i];
420 // Don't break till we have skipped any overdue timers.
422 // XXXbz why? Given our definition of overdue in terms of
423 // mTimeoutAdjustment, aTimer might be overdue already! Why not
424 // just fire timers in order?
426 // XXX does this hold for TYPE_REPEATING_PRECISE? /be
428 if (now < timer->mTimeout + mTimeoutAdjustment &&
429 aTimer->mTimeout < timer->mTimeout) {
430 break;
434 if (!mTimers.InsertElementAt(i, aTimer))
435 return -1;
437 aTimer->mArmed = PR_TRUE;
438 NS_ADDREF(aTimer);
439 return i;
442 PRBool TimerThread::RemoveTimerInternal(nsTimerImpl *aTimer)
444 if (!mTimers.RemoveElement(aTimer))
445 return PR_FALSE;
447 ReleaseTimerInternal(aTimer);
448 return PR_TRUE;
451 void TimerThread::ReleaseTimerInternal(nsTimerImpl *aTimer)
453 // Order is crucial here -- see nsTimerImpl::Release.
454 aTimer->mArmed = PR_FALSE;
455 NS_RELEASE(aTimer);
458 void TimerThread::DoBeforeSleep()
460 mSleeping = PR_TRUE;
463 void TimerThread::DoAfterSleep()
465 mSleeping = PR_TRUE; // wake may be notified without preceding sleep notification
466 for (PRUint32 i = 0; i < mTimers.Length(); i ++) {
467 nsTimerImpl *timer = mTimers[i];
468 // get and set the delay to cause its timeout to be recomputed
469 PRUint32 delay;
470 timer->GetDelay(&delay);
471 timer->SetDelay(delay);
474 // nuke the stored adjustments, so they get recalibrated
475 mTimeoutAdjustment = TimeDuration(0);
476 mDelayLineCounter = 0;
477 mSleeping = PR_FALSE;
481 /* void observe (in nsISupports aSubject, in string aTopic, in wstring aData); */
482 NS_IMETHODIMP
483 TimerThread::Observe(nsISupports* /* aSubject */, const char *aTopic, const PRUnichar* /* aData */)
485 if (strcmp(aTopic, "sleep_notification") == 0)
486 DoBeforeSleep();
487 else if (strcmp(aTopic, "wake_notification") == 0)
488 DoAfterSleep();
490 return NS_OK;