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
3 : *
4 : * ***** BEGIN LICENSE BLOCK *****
5 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 : *
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/
11 : *
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.
16 : *
17 : * The Original Code is mozilla.org code.
18 : *
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.
23 : *
24 : * Contributor(s):
25 : * Stuart Parmenter <pavlov@netscape.com>
26 : *
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.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : #include "nsTimerImpl.h"
42 : #include "TimerThread.h"
43 :
44 : #include "nsThreadUtils.h"
45 : #include "pratom.h"
46 :
47 : #include "nsIObserverService.h"
48 : #include "nsIServiceManager.h"
49 : #include "mozilla/Services.h"
50 :
51 : #include <math.h>
52 :
53 : using namespace mozilla;
54 :
55 18173 : NS_IMPL_THREADSAFE_ISUPPORTS2(TimerThread, nsIRunnable, nsIObserver)
56 :
57 1365 : TimerThread::TimerThread() :
58 : mInitInProgress(0),
59 : mInitialized(false),
60 : mMonitor("TimerThread.mMonitor"),
61 : mShutdown(false),
62 : mWaiting(false),
63 : mSleeping(false),
64 : mDelayLineCounter(0),
65 1365 : mMinTimerPeriod(0)
66 : {
67 1365 : }
68 :
69 2730 : TimerThread::~TimerThread()
70 : {
71 1365 : mThread = nsnull;
72 :
73 1365 : NS_ASSERTION(mTimers.IsEmpty(), "Timers remain in TimerThread::~TimerThread");
74 1365 : }
75 :
76 : nsresult
77 1365 : TimerThread::InitLocks()
78 : {
79 1365 : return NS_OK;
80 : }
81 :
82 : namespace {
83 :
84 : class TimerObserverRunnable : public nsRunnable
85 4020 : {
86 : public:
87 1005 : TimerObserverRunnable(nsIObserver* observer)
88 1005 : : mObserver(observer)
89 1005 : { }
90 :
91 : NS_DECL_NSIRUNNABLE
92 :
93 : private:
94 : nsCOMPtr<nsIObserver> mObserver;
95 : };
96 :
97 : NS_IMETHODIMP
98 1005 : TimerObserverRunnable::Run()
99 : {
100 : nsCOMPtr<nsIObserverService> observerService =
101 2010 : mozilla::services::GetObserverService();
102 1005 : if (observerService) {
103 1005 : observerService->AddObserver(mObserver, "sleep_notification", PR_FALSE);
104 1005 : observerService->AddObserver(mObserver, "wake_notification", PR_FALSE);
105 : }
106 1005 : return NS_OK;
107 : }
108 :
109 : } // anonymous namespace
110 :
111 21337 : nsresult TimerThread::Init()
112 : {
113 21337 : PR_LOG(gTimerLog, PR_LOG_DEBUG, ("TimerThread::Init [%d]\n", mInitialized));
114 :
115 21337 : if (mInitialized) {
116 20332 : if (!mThread)
117 0 : return NS_ERROR_FAILURE;
118 :
119 20332 : return NS_OK;
120 : }
121 :
122 1005 : if (PR_ATOMIC_SET(&mInitInProgress, 1) == 0) {
123 : // We hold on to mThread to keep the thread alive.
124 1005 : nsresult rv = NS_NewThread(getter_AddRefs(mThread), this);
125 1005 : if (NS_FAILED(rv)) {
126 0 : mThread = nsnull;
127 : }
128 : else {
129 2010 : nsRefPtr<TimerObserverRunnable> r = new TimerObserverRunnable(this);
130 1005 : if (NS_IsMainThread()) {
131 1001 : r->Run();
132 : }
133 : else {
134 4 : NS_DispatchToMainThread(r);
135 : }
136 : }
137 :
138 : {
139 2010 : MonitorAutoLock lock(mMonitor);
140 1005 : mInitialized = true;
141 1005 : mMonitor.NotifyAll();
142 : }
143 : }
144 : else {
145 0 : MonitorAutoLock lock(mMonitor);
146 0 : while (!mInitialized) {
147 0 : mMonitor.Wait();
148 : }
149 : }
150 :
151 1005 : if (!mThread)
152 0 : return NS_ERROR_FAILURE;
153 :
154 1005 : return NS_OK;
155 : }
156 :
157 1365 : nsresult TimerThread::Shutdown()
158 : {
159 1365 : PR_LOG(gTimerLog, PR_LOG_DEBUG, ("TimerThread::Shutdown begin\n"));
160 :
161 1365 : if (!mThread)
162 360 : return NS_ERROR_NOT_INITIALIZED;
163 :
164 2010 : nsTArray<nsTimerImpl*> timers;
165 : { // lock scope
166 2010 : MonitorAutoLock lock(mMonitor);
167 :
168 1005 : mShutdown = true;
169 :
170 : // notify the cond var so that Run() can return
171 1005 : if (mWaiting)
172 987 : mMonitor.Notify();
173 :
174 : // Need to copy content of mTimers array to a local array
175 : // because call to timers' ReleaseCallback() (and release its self)
176 : // must not be done under the lock. Destructor of a callback
177 : // might potentially call some code reentering the same lock
178 : // that leads to unexpected behavior or deadlock.
179 : // See bug 422472.
180 1005 : timers.AppendElements(mTimers);
181 1005 : mTimers.Clear();
182 : }
183 :
184 1005 : PRUint32 timersCount = timers.Length();
185 1383 : for (PRUint32 i = 0; i < timersCount; i++) {
186 378 : nsTimerImpl *timer = timers[i];
187 378 : timer->ReleaseCallback();
188 378 : ReleaseTimerInternal(timer);
189 : }
190 :
191 1005 : mThread->Shutdown(); // wait for the thread to die
192 :
193 1005 : PR_LOG(gTimerLog, PR_LOG_DEBUG, ("TimerThread::Shutdown end\n"));
194 1005 : return NS_OK;
195 : }
196 :
197 : // Keep track of how early (positive slack) or late (negative slack) timers
198 : // are running, and use the filtered slack number to adaptively estimate how
199 : // early timers should fire to be "on time".
200 5190 : void TimerThread::UpdateFilter(PRUint32 aDelay, TimeStamp aTimeout,
201 : TimeStamp aNow)
202 : {
203 5190 : TimeDuration slack = aTimeout - aNow;
204 5190 : double smoothSlack = 0;
205 : PRUint32 i, filterLength;
206 : static TimeDuration kFilterFeedbackMaxTicks =
207 5190 : TimeDuration::FromMilliseconds(FILTER_FEEDBACK_MAX);
208 : static TimeDuration kFilterFeedbackMinTicks =
209 5190 : TimeDuration::FromMilliseconds(-FILTER_FEEDBACK_MAX);
210 :
211 5190 : if (slack > kFilterFeedbackMaxTicks)
212 8 : slack = kFilterFeedbackMaxTicks;
213 5182 : else if (slack < kFilterFeedbackMinTicks)
214 1155 : slack = kFilterFeedbackMinTicks;
215 :
216 5190 : mDelayLine[mDelayLineCounter & DELAY_LINE_LENGTH_MASK] =
217 5190 : slack.ToMilliseconds();
218 5190 : if (++mDelayLineCounter < DELAY_LINE_LENGTH) {
219 : // Startup mode: accumulate a full delay line before filtering.
220 3258 : PR_ASSERT(mTimeoutAdjustment.ToSeconds() == 0);
221 3258 : filterLength = 0;
222 : } else {
223 : // Past startup: compute number of filter taps based on mMinTimerPeriod.
224 1932 : if (mMinTimerPeriod == 0) {
225 24 : mMinTimerPeriod = (aDelay != 0) ? aDelay : 1;
226 1908 : } else if (aDelay != 0 && aDelay < mMinTimerPeriod) {
227 4 : mMinTimerPeriod = aDelay;
228 : }
229 :
230 1932 : filterLength = (PRUint32) (FILTER_DURATION / mMinTimerPeriod);
231 1932 : if (filterLength > DELAY_LINE_LENGTH)
232 1651 : filterLength = DELAY_LINE_LENGTH;
233 281 : else if (filterLength < 4)
234 179 : filterLength = 4;
235 :
236 57262 : for (i = 1; i <= filterLength; i++)
237 55330 : smoothSlack += mDelayLine[(mDelayLineCounter-i) & DELAY_LINE_LENGTH_MASK];
238 1932 : smoothSlack /= filterLength;
239 :
240 : // XXXbe do we need amplification? hacking a fudge factor, need testing...
241 1932 : mTimeoutAdjustment = TimeDuration::FromMilliseconds(smoothSlack * 1.5);
242 : }
243 :
244 : #ifdef DEBUG_TIMERS
245 5190 : PR_LOG(gTimerLog, PR_LOG_DEBUG,
246 : ("UpdateFilter: smoothSlack = %g, filterLength = %u\n",
247 : smoothSlack, filterLength));
248 : #endif
249 5190 : }
250 :
251 : /* void Run(); */
252 1005 : NS_IMETHODIMP TimerThread::Run()
253 : {
254 2010 : MonitorAutoLock lock(mMonitor);
255 :
256 : // We need to know how many microseconds give a positive PRIntervalTime. This
257 : // is platform-dependent, we calculate it at runtime now.
258 : // First we find a value such that PR_MicrosecondsToInterval(high) = 1
259 1005 : PRInt32 low = 0, high = 1;
260 11055 : while (PR_MicrosecondsToInterval(high) == 0)
261 9045 : high <<= 1;
262 : // We now have
263 : // PR_MicrosecondsToInterval(low) = 0
264 : // PR_MicrosecondsToInterval(high) = 1
265 : // and we can proceed to find the critical value using binary search
266 11055 : while (high-low > 1) {
267 9045 : PRInt32 mid = (high+low) >> 1;
268 9045 : if (PR_MicrosecondsToInterval(mid) == 0)
269 7035 : low = mid;
270 : else
271 2010 : high = mid;
272 : }
273 :
274 : // Half of the amount of microseconds needed to get positive PRIntervalTime.
275 : // We use this to decide how to round our wait times later
276 1005 : PRInt32 halfMicrosecondsIntervalResolution = high >> 1;
277 :
278 39066 : while (!mShutdown) {
279 : // Have to use PRIntervalTime here, since PR_WaitCondVar takes it
280 : PRIntervalTime waitFor;
281 :
282 37056 : if (mSleeping) {
283 : // Sleep for 0.1 seconds while not firing timers.
284 1 : waitFor = PR_MillisecondsToInterval(100);
285 : } else {
286 37055 : waitFor = PR_INTERVAL_NO_TIMEOUT;
287 37055 : TimeStamp now = TimeStamp::Now();
288 37055 : nsTimerImpl *timer = nsnull;
289 :
290 37055 : if (!mTimers.IsEmpty()) {
291 31152 : timer = mTimers[0];
292 :
293 31152 : if (now >= timer->mTimeout + mTimeoutAdjustment) {
294 : next:
295 : // NB: AddRef before the Release under RemoveTimerInternal to avoid
296 : // mRefCnt passing through zero, in case all other refs than the one
297 : // from mTimers have gone away (the last non-mTimers[i]-ref's Release
298 : // must be racing with us, blocked in gThread->RemoveTimer waiting
299 : // for TimerThread::mMonitor, under nsTimerImpl::Release.
300 :
301 5308 : NS_ADDREF(timer);
302 5308 : RemoveTimerInternal(timer);
303 :
304 : {
305 : // We release mMonitor around the Fire call to avoid deadlock.
306 10616 : MonitorAutoUnlock unlock(mMonitor);
307 :
308 : #ifdef DEBUG_TIMERS
309 5308 : if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
310 0 : PR_LOG(gTimerLog, PR_LOG_DEBUG,
311 : ("Timer thread woke up %fms from when it was supposed to\n",
312 : fabs((now - timer->mTimeout).ToMilliseconds())));
313 : }
314 : #endif
315 :
316 : // We are going to let the call to PostTimerEvent here handle the
317 : // release of the timer so that we don't end up releasing the timer
318 : // on the TimerThread instead of on the thread it targets.
319 5308 : if (NS_FAILED(timer->PostTimerEvent())) {
320 : nsrefcnt rc;
321 0 : NS_RELEASE2(timer, rc);
322 :
323 : // The nsITimer interface requires that its users keep a reference
324 : // to the timers they use while those timers are initialized but
325 : // have not yet fired. If this ever happens, it is a bug in the
326 : // code that created and used the timer.
327 : //
328 : // Further, note that this should never happen even with a
329 : // misbehaving user, because nsTimerImpl::Release checks for a
330 : // refcount of 1 with an armed timer (a timer whose only reference
331 : // is from the timer thread) and when it hits this will remove the
332 : // timer from the timer thread and thus destroy the last reference,
333 : // preventing this situation from occurring.
334 0 : NS_ASSERTION(rc != 0, "destroyed timer off its target thread!");
335 : }
336 5308 : timer = nsnull;
337 : }
338 :
339 5308 : if (mShutdown)
340 0 : break;
341 :
342 : // Update now, as PostTimerEvent plus the locking may have taken a
343 : // tick or two, and we may goto next below.
344 5308 : now = TimeStamp::Now();
345 : }
346 : }
347 :
348 37085 : if (!mTimers.IsEmpty()) {
349 30968 : timer = mTimers[0];
350 :
351 30968 : TimeStamp timeout = timer->mTimeout + mTimeoutAdjustment;
352 :
353 : // Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer
354 : // is due now or overdue.
355 : //
356 : // Note that we can only sleep for integer values of a certain
357 : // resolution. We use halfMicrosecondsIntervalResolution, calculated
358 : // before, to do the optimal rounding (i.e., of how to decide what
359 : // interval is so small we should not wait at all).
360 30968 : double microseconds = (timeout - now).ToMilliseconds()*1000;
361 30968 : if (microseconds < halfMicrosecondsIntervalResolution)
362 30 : goto next; // round down; execute event now
363 30938 : waitFor = PR_MicrosecondsToInterval(microseconds);
364 30938 : if (waitFor == 0)
365 7 : waitFor = 1; // round up, wait the minimum time we can wait
366 : }
367 :
368 : #ifdef DEBUG_TIMERS
369 37055 : if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
370 0 : if (waitFor == PR_INTERVAL_NO_TIMEOUT)
371 0 : PR_LOG(gTimerLog, PR_LOG_DEBUG,
372 : ("waiting for PR_INTERVAL_NO_TIMEOUT\n"));
373 : else
374 0 : PR_LOG(gTimerLog, PR_LOG_DEBUG,
375 : ("waiting for %u\n", PR_IntervalToMilliseconds(waitFor)));
376 : }
377 : #endif
378 : }
379 :
380 37056 : mWaiting = true;
381 37056 : mMonitor.Wait(waitFor);
382 37056 : mWaiting = false;
383 : }
384 :
385 1005 : return NS_OK;
386 : }
387 :
388 23975 : nsresult TimerThread::AddTimer(nsTimerImpl *aTimer)
389 : {
390 47950 : MonitorAutoLock lock(mMonitor);
391 :
392 : // Add the timer to our list.
393 23975 : PRInt32 i = AddTimerInternal(aTimer);
394 23975 : if (i < 0)
395 0 : return NS_ERROR_OUT_OF_MEMORY;
396 :
397 : // Awaken the timer thread.
398 23975 : if (mWaiting && i == 0)
399 15454 : mMonitor.Notify();
400 :
401 23975 : return NS_OK;
402 : }
403 :
404 3651 : nsresult TimerThread::TimerDelayChanged(nsTimerImpl *aTimer)
405 : {
406 7302 : MonitorAutoLock lock(mMonitor);
407 :
408 : // Our caller has a strong ref to aTimer, so it can't go away here under
409 : // ReleaseTimerInternal.
410 3651 : RemoveTimerInternal(aTimer);
411 :
412 3651 : PRInt32 i = AddTimerInternal(aTimer);
413 3651 : if (i < 0)
414 0 : return NS_ERROR_OUT_OF_MEMORY;
415 :
416 : // Awaken the timer thread.
417 3651 : if (mWaiting && i == 0)
418 398 : mMonitor.Notify();
419 :
420 3651 : return NS_OK;
421 : }
422 :
423 20040 : nsresult TimerThread::RemoveTimer(nsTimerImpl *aTimer)
424 : {
425 40080 : MonitorAutoLock lock(mMonitor);
426 :
427 : // Remove the timer from our array. Tell callers that aTimer was not found
428 : // by returning NS_ERROR_NOT_AVAILABLE. Unlike the TimerDelayChanged case
429 : // immediately above, our caller may be passing a (now-)weak ref in via the
430 : // aTimer param, specifically when nsTimerImpl::Release loses a race with
431 : // TimerThread::Run, must wait for the mMonitor auto-lock here, and during the
432 : // wait Run drops the only remaining ref to aTimer via RemoveTimerInternal.
433 :
434 20040 : if (!RemoveTimerInternal(aTimer))
435 1736 : return NS_ERROR_NOT_AVAILABLE;
436 :
437 : // Awaken the timer thread.
438 18304 : if (mWaiting)
439 17946 : mMonitor.Notify();
440 :
441 18304 : return NS_OK;
442 : }
443 :
444 : // This function must be called from within a lock
445 27626 : PRInt32 TimerThread::AddTimerInternal(nsTimerImpl *aTimer)
446 : {
447 27626 : if (mShutdown)
448 0 : return -1;
449 :
450 27626 : TimeStamp now = TimeStamp::Now();
451 27626 : PRUint32 count = mTimers.Length();
452 27626 : PRUint32 i = 0;
453 47467 : for (; i < count; i++) {
454 34334 : nsTimerImpl *timer = mTimers[i];
455 :
456 : // Don't break till we have skipped any overdue timers.
457 :
458 : // XXXbz why? Given our definition of overdue in terms of
459 : // mTimeoutAdjustment, aTimer might be overdue already! Why not
460 : // just fire timers in order?
461 :
462 : // XXX does this hold for TYPE_REPEATING_PRECISE? /be
463 :
464 68638 : if (now < timer->mTimeout + mTimeoutAdjustment &&
465 34304 : aTimer->mTimeout < timer->mTimeout) {
466 14493 : break;
467 : }
468 : }
469 :
470 27626 : if (!mTimers.InsertElementAt(i, aTimer))
471 0 : return -1;
472 :
473 27626 : aTimer->mArmed = true;
474 27626 : NS_ADDREF(aTimer);
475 27626 : return i;
476 : }
477 :
478 28999 : bool TimerThread::RemoveTimerInternal(nsTimerImpl *aTimer)
479 : {
480 28999 : if (!mTimers.RemoveElement(aTimer))
481 1751 : return false;
482 :
483 27248 : ReleaseTimerInternal(aTimer);
484 27248 : return true;
485 : }
486 :
487 27626 : void TimerThread::ReleaseTimerInternal(nsTimerImpl *aTimer)
488 : {
489 : // Order is crucial here -- see nsTimerImpl::Release.
490 27626 : aTimer->mArmed = false;
491 27626 : NS_RELEASE(aTimer);
492 27626 : }
493 :
494 1 : void TimerThread::DoBeforeSleep()
495 : {
496 1 : mSleeping = true;
497 1 : }
498 :
499 1 : void TimerThread::DoAfterSleep()
500 : {
501 1 : mSleeping = true; // wake may be notified without preceding sleep notification
502 5 : for (PRUint32 i = 0; i < mTimers.Length(); i ++) {
503 4 : nsTimerImpl *timer = mTimers[i];
504 : // get and set the delay to cause its timeout to be recomputed
505 : PRUint32 delay;
506 4 : timer->GetDelay(&delay);
507 4 : timer->SetDelay(delay);
508 : }
509 :
510 : // nuke the stored adjustments, so they get recalibrated
511 1 : mTimeoutAdjustment = TimeDuration(0);
512 1 : mDelayLineCounter = 0;
513 1 : mSleeping = false;
514 1 : }
515 :
516 :
517 : /* void observe (in nsISupports aSubject, in string aTopic, in wstring aData); */
518 : NS_IMETHODIMP
519 2 : TimerThread::Observe(nsISupports* /* aSubject */, const char *aTopic, const PRUnichar* /* aData */)
520 : {
521 2 : if (strcmp(aTopic, "sleep_notification") == 0)
522 1 : DoBeforeSleep();
523 1 : else if (strcmp(aTopic, "wake_notification") == 0)
524 1 : DoAfterSleep();
525 :
526 2 : return NS_OK;
527 4188 : }
|