~ubuntu-branches/ubuntu/precise/kompozer/precise

« back to all changes in this revision

Viewing changes to mozilla/xpcom/threads/TimerThread.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Anthony Yarusso
  • Date: 2007-08-27 01:11:03 UTC
  • Revision ID: james.westby@ubuntu.com-20070827011103-2jgf4s6532gqu2ka
Tags: upstream-0.7.10
ImportĀ upstreamĀ versionĀ 0.7.10

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 * The contents of this file are subject to the Mozilla Public License Version
 
5
 * 1.1 (the "License"); you may not use this file except in compliance with
 
6
 * the License. You may obtain a copy of the License at
 
7
 * http://www.mozilla.org/MPL/
 
8
 *
 
9
 * Software distributed under the License is distributed on an "AS IS" basis,
 
10
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 
11
 * for the specific language governing rights and limitations under the
 
12
 * License.
 
13
 *
 
14
 * The Original Code is mozilla.org code.
 
15
 *
 
16
 * The Initial Developer of the Original Code is Netscape Communications
 
17
 * Corporation. Portions created by the Initial Developer are
 
18
 * Copyright (C) 2001 the Initial Developer. All Rights Reserved.
 
19
 *
 
20
 * Contributor(s):
 
21
 *   Stuart Parmenter <pavlov@netscape.com>
 
22
 *
 
23
 * Alternatively, the contents of this file may be used under the terms of
 
24
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 
25
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 
26
 * in which case the provisions of the GPL or the LGPL are applicable instead
 
27
 * of those above. If you wish to allow use of your version of this file only
 
28
 * under the terms of either the GPL or the LGPL, and not to allow others to
 
29
 * use your version of this file under the terms of the MPL, indicate your
 
30
 * decision by deleting the provisions above and replace them with the notice
 
31
 * and other provisions required by the GPL or the LGPL. If you do not delete
 
32
 * the provisions above, a recipient may use your version of this file under
 
33
 * the terms of any one of the MPL, the GPL or the LGPL.
 
34
 */
 
35
 
 
36
#include "nsTimerImpl.h"
 
37
#include "TimerThread.h"
 
38
 
 
39
#include "nsAutoLock.h"
 
40
#include "pratom.h"
 
41
 
 
42
#include "nsIObserverService.h"
 
43
#include "nsIServiceManager.h"
 
44
 
 
45
NS_IMPL_THREADSAFE_ISUPPORTS3(TimerThread, nsIRunnable, nsISupportsWeakReference, nsIObserver)
 
46
 
 
47
TimerThread::TimerThread() :
 
48
  mLock(nsnull),
 
49
  mCondVar(nsnull),
 
50
  mShutdown(PR_FALSE),
 
51
  mWaiting(PR_FALSE),
 
52
  mSleeping(PR_FALSE),
 
53
  mDelayLineCounter(0),
 
54
  mMinTimerPeriod(0),
 
55
  mTimeoutAdjustment(0)
 
56
{
 
57
}
 
58
 
 
59
TimerThread::~TimerThread()
 
60
{
 
61
  if (mCondVar)
 
62
    PR_DestroyCondVar(mCondVar);
 
63
  if (mLock)
 
64
    PR_DestroyLock(mLock);
 
65
 
 
66
  mThread = nsnull;
 
67
 
 
68
  PRInt32 n = mTimers.Count();
 
69
  while (--n >= 0) {
 
70
    nsTimerImpl *timer = NS_STATIC_CAST(nsTimerImpl *, mTimers[n]);
 
71
    NS_RELEASE(timer);
 
72
  }
 
73
 
 
74
  nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1");
 
75
  if (observerService) {
 
76
    observerService->RemoveObserver(this, "sleep_notification");
 
77
    observerService->RemoveObserver(this, "wake_notification");
 
78
  }
 
79
 
 
80
}
 
81
 
 
82
nsresult TimerThread::Init()
 
83
{
 
84
  if (mThread)
 
85
    return NS_OK;
 
86
 
 
87
  mLock = PR_NewLock();
 
88
  if (!mLock)
 
89
    return NS_ERROR_OUT_OF_MEMORY;
 
90
 
 
91
  mCondVar = PR_NewCondVar(mLock);
 
92
  if (!mCondVar)
 
93
    return NS_ERROR_OUT_OF_MEMORY;
 
94
 
 
95
  nsresult rv;
 
96
  mEventQueueService = do_GetService("@mozilla.org/event-queue-service;1", &rv);
 
97
  if (NS_FAILED(rv))
 
98
    return rv;
 
99
 
 
100
  // We hold on to mThread to keep the thread alive.
 
101
  rv = NS_NewThread(getter_AddRefs(mThread),
 
102
                    NS_STATIC_CAST(nsIRunnable*, this),
 
103
                    0,
 
104
                    PR_JOINABLE_THREAD,
 
105
                    PR_PRIORITY_NORMAL,
 
106
                    PR_GLOBAL_THREAD);
 
107
  if (NS_FAILED(rv))
 
108
    return rv;
 
109
 
 
110
  nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1", &rv);
 
111
  if (NS_FAILED(rv))
 
112
    return rv;
 
113
  
 
114
  observerService->AddObserver(this, "sleep_notification", PR_TRUE);
 
115
  observerService->AddObserver(this, "wake_notification", PR_TRUE);
 
116
  
 
117
  return rv;
 
118
}
 
119
 
 
120
nsresult TimerThread::Shutdown()
 
121
{
 
122
  if (!mThread)
 
123
    return NS_ERROR_NOT_INITIALIZED;
 
124
 
 
125
  {   // lock scope
 
126
    nsAutoLock lock(mLock);
 
127
 
 
128
    mShutdown = PR_TRUE;
 
129
 
 
130
    // notify the cond var so that Run() can return
 
131
    if (mCondVar && mWaiting)
 
132
      PR_NotifyCondVar(mCondVar);
 
133
 
 
134
    nsTimerImpl *timer;
 
135
    for (PRInt32 i = mTimers.Count() - 1; i >= 0; i--) {
 
136
      timer = NS_STATIC_CAST(nsTimerImpl*, mTimers[i]);
 
137
      RemoveTimerInternal(timer);
 
138
    }
 
139
  }
 
140
 
 
141
  mThread->Join();    // wait for the thread to die
 
142
  return NS_OK;
 
143
}
 
144
 
 
145
// Keep track of how early (positive slack) or late (negative slack) timers
 
146
// are running, and use the filtered slack number to adaptively estimate how
 
147
// early timers should fire to be "on time".
 
148
void TimerThread::UpdateFilter(PRUint32 aDelay, PRIntervalTime aTimeout,
 
149
                               PRIntervalTime aNow)
 
150
{
 
151
  PRInt32 slack = (PRInt32) (aTimeout - aNow);
 
152
  double smoothSlack = 0;
 
153
  PRUint32 i, filterLength;
 
154
  static PRIntervalTime kFilterFeedbackMaxTicks =
 
155
    PR_MillisecondsToInterval(FILTER_FEEDBACK_MAX);
 
156
 
 
157
  if (slack > 0) {
 
158
    if (slack > (PRInt32)kFilterFeedbackMaxTicks)
 
159
      slack = kFilterFeedbackMaxTicks;
 
160
  } else {
 
161
    if (slack < -(PRInt32)kFilterFeedbackMaxTicks)
 
162
      slack = -(PRInt32)kFilterFeedbackMaxTicks;
 
163
  }
 
164
  mDelayLine[mDelayLineCounter & DELAY_LINE_LENGTH_MASK] = slack;
 
165
  if (++mDelayLineCounter < DELAY_LINE_LENGTH) {
 
166
    // Startup mode: accumulate a full delay line before filtering.
 
167
    PR_ASSERT(mTimeoutAdjustment == 0);
 
168
    filterLength = 0;
 
169
  } else {
 
170
    // Past startup: compute number of filter taps based on mMinTimerPeriod.
 
171
    if (mMinTimerPeriod == 0) {
 
172
      mMinTimerPeriod = (aDelay != 0) ? aDelay : 1;
 
173
    } else if (aDelay != 0 && aDelay < mMinTimerPeriod) {
 
174
      mMinTimerPeriod = aDelay;
 
175
    }
 
176
 
 
177
    filterLength = (PRUint32) (FILTER_DURATION / mMinTimerPeriod);
 
178
    if (filterLength > DELAY_LINE_LENGTH)
 
179
      filterLength = DELAY_LINE_LENGTH;
 
180
    else if (filterLength < 4)
 
181
      filterLength = 4;
 
182
 
 
183
    for (i = 1; i <= filterLength; i++)
 
184
      smoothSlack += mDelayLine[(mDelayLineCounter-i) & DELAY_LINE_LENGTH_MASK];
 
185
    smoothSlack /= filterLength;
 
186
 
 
187
    // XXXbe do we need amplification?  hacking a fudge factor, need testing...
 
188
    mTimeoutAdjustment = (PRInt32) (smoothSlack * 1.5);
 
189
  }
 
190
 
 
191
#ifdef DEBUG_TIMERS
 
192
  if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
 
193
    PR_LOG(gTimerLog, PR_LOG_DEBUG,
 
194
           ("UpdateFilter: smoothSlack = %g, filterLength = %u\n",
 
195
            smoothSlack, filterLength));
 
196
  }
 
197
#endif
 
198
}
 
199
 
 
200
/* void Run(); */
 
201
NS_IMETHODIMP TimerThread::Run()
 
202
{
 
203
  nsAutoLock lock(mLock);
 
204
 
 
205
  while (!mShutdown) {
 
206
    PRIntervalTime waitFor;
 
207
 
 
208
    if (mSleeping) {
 
209
      // Sleep for 0.1 seconds while not firing timers.
 
210
      waitFor = PR_MillisecondsToInterval(100);
 
211
    } else {
 
212
      waitFor = PR_INTERVAL_NO_TIMEOUT;
 
213
      PRIntervalTime now = PR_IntervalNow();
 
214
      nsTimerImpl *timer = nsnull;
 
215
 
 
216
      if (mTimers.Count() > 0) {
 
217
        timer = NS_STATIC_CAST(nsTimerImpl*, mTimers[0]);
 
218
 
 
219
        if (!TIMER_LESS_THAN(now, timer->mTimeout + mTimeoutAdjustment)) {
 
220
    next:
 
221
          // NB: AddRef before the Release under RemoveTimerInternal to avoid
 
222
          // mRefCnt passing through zero, in case all other refs than the one
 
223
          // from mTimers have gone away (the last non-mTimers[i]-ref's Release
 
224
          // must be racing with us, blocked in gThread->RemoveTimer waiting
 
225
          // for TimerThread::mLock, under nsTimerImpl::Release.
 
226
 
 
227
          NS_ADDREF(timer);
 
228
          RemoveTimerInternal(timer);
 
229
 
 
230
          // We release mLock around the Fire call to avoid deadlock.
 
231
          lock.unlock();
 
232
 
 
233
#ifdef DEBUG_TIMERS
 
234
          if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
 
235
            PR_LOG(gTimerLog, PR_LOG_DEBUG,
 
236
                   ("Timer thread woke up %dms from when it was supposed to\n",
 
237
                    (now >= timer->mTimeout)
 
238
                    ? PR_IntervalToMilliseconds(now - timer->mTimeout)
 
239
                    : -(PRInt32)PR_IntervalToMilliseconds(timer->mTimeout-now))
 
240
                  );
 
241
          }
 
242
#endif
 
243
 
 
244
          // We are going to let the call to PostTimerEvent here handle the
 
245
          // release of the timer so that we don't end up releasing the timer
 
246
          // on the TimerThread instead of on the thread it targets.
 
247
          timer->PostTimerEvent();
 
248
          timer = nsnull;
 
249
 
 
250
          lock.lock();
 
251
          if (mShutdown)
 
252
            break;
 
253
 
 
254
          // Update now, as PostTimerEvent plus the locking may have taken a
 
255
          // tick or two, and we may goto next below.
 
256
          now = PR_IntervalNow();
 
257
        }
 
258
      }
 
259
 
 
260
      if (mTimers.Count() > 0) {
 
261
        timer = NS_STATIC_CAST(nsTimerImpl *, mTimers[0]);
 
262
 
 
263
        PRIntervalTime timeout = timer->mTimeout + mTimeoutAdjustment;
 
264
 
 
265
        // Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer
 
266
        // is due now or overdue.
 
267
        if (!TIMER_LESS_THAN(now, timeout))
 
268
          goto next;
 
269
        waitFor = timeout - now;
 
270
      }
 
271
 
 
272
#ifdef DEBUG_TIMERS
 
273
      if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
 
274
        if (waitFor == PR_INTERVAL_NO_TIMEOUT)
 
275
          PR_LOG(gTimerLog, PR_LOG_DEBUG,
 
276
                 ("waiting for PR_INTERVAL_NO_TIMEOUT\n"));
 
277
        else
 
278
          PR_LOG(gTimerLog, PR_LOG_DEBUG,
 
279
                 ("waiting for %u\n", PR_IntervalToMilliseconds(waitFor)));
 
280
      }
 
281
#endif
 
282
    }
 
283
 
 
284
    mWaiting = PR_TRUE;
 
285
    PR_WaitCondVar(mCondVar, waitFor);
 
286
    mWaiting = PR_FALSE;
 
287
  }
 
288
 
 
289
  return NS_OK;
 
290
}
 
291
 
 
292
nsresult TimerThread::AddTimer(nsTimerImpl *aTimer)
 
293
{
 
294
  nsAutoLock lock(mLock);
 
295
 
 
296
  // Add the timer to our list.
 
297
  PRInt32 i = AddTimerInternal(aTimer);
 
298
  if (i < 0)
 
299
    return NS_ERROR_OUT_OF_MEMORY;
 
300
 
 
301
  // Awaken the timer thread.
 
302
  if (mCondVar && mWaiting && i == 0)
 
303
    PR_NotifyCondVar(mCondVar);
 
304
 
 
305
  return NS_OK;
 
306
}
 
307
 
 
308
nsresult TimerThread::TimerDelayChanged(nsTimerImpl *aTimer)
 
309
{
 
310
  nsAutoLock lock(mLock);
 
311
 
 
312
  // Our caller has a strong ref to aTimer, so it can't go away here under
 
313
  // ReleaseTimerInternal.
 
314
  RemoveTimerInternal(aTimer);
 
315
 
 
316
  PRInt32 i = AddTimerInternal(aTimer);
 
317
  if (i < 0)
 
318
    return NS_ERROR_OUT_OF_MEMORY;
 
319
 
 
320
  // Awaken the timer thread.
 
321
  if (mCondVar && mWaiting && i == 0)
 
322
    PR_NotifyCondVar(mCondVar);
 
323
 
 
324
  return NS_OK;
 
325
}
 
326
 
 
327
nsresult TimerThread::RemoveTimer(nsTimerImpl *aTimer)
 
328
{
 
329
  nsAutoLock lock(mLock);
 
330
 
 
331
  // Remove the timer from our array.  Tell callers that aTimer was not found
 
332
  // by returning NS_ERROR_NOT_AVAILABLE.  Unlike the TimerDelayChanged case
 
333
  // immediately above, our caller may be passing a (now-)weak ref in via the
 
334
  // aTimer param, specifically when nsTimerImpl::Release loses a race with
 
335
  // TimerThread::Run, must wait for the mLock auto-lock here, and during the
 
336
  // wait Run drops the only remaining ref to aTimer via RemoveTimerInternal.
 
337
 
 
338
  if (!RemoveTimerInternal(aTimer))
 
339
    return NS_ERROR_NOT_AVAILABLE;
 
340
 
 
341
  // Awaken the timer thread.
 
342
  if (mCondVar && mWaiting)
 
343
    PR_NotifyCondVar(mCondVar);
 
344
 
 
345
  return NS_OK;
 
346
}
 
347
 
 
348
// This function must be called from within a lock
 
349
PRInt32 TimerThread::AddTimerInternal(nsTimerImpl *aTimer)
 
350
{
 
351
  PRIntervalTime now = PR_IntervalNow();
 
352
  PRInt32 count = mTimers.Count();
 
353
  PRInt32 i = 0;
 
354
  for (; i < count; i++) {
 
355
    nsTimerImpl *timer = NS_STATIC_CAST(nsTimerImpl *, mTimers[i]);
 
356
 
 
357
    // Don't break till we have skipped any overdue timers.  Do not include
 
358
    // mTimeoutAdjustment here, because we are really trying to avoid calling
 
359
    // TIMER_LESS_THAN(t, u), where the t is now + DELAY_INTERVAL_MAX, u is
 
360
    // now - overdue, and DELAY_INTERVAL_MAX + overdue > DELAY_INTERVAL_LIMIT.
 
361
    // In other words, we want to use now-based time, now adjusted time, even
 
362
    // though "overdue" ultimately depends on adjusted time.
 
363
 
 
364
    // XXX does this hold for TYPE_REPEATING_PRECISE?  /be
 
365
 
 
366
    if (TIMER_LESS_THAN(now, timer->mTimeout) &&
 
367
        TIMER_LESS_THAN(aTimer->mTimeout, timer->mTimeout)) {
 
368
      break;
 
369
    }
 
370
  }
 
371
 
 
372
  if (!mTimers.InsertElementAt(aTimer, i))
 
373
    return -1;
 
374
 
 
375
  aTimer->mArmed = PR_TRUE;
 
376
  NS_ADDREF(aTimer);
 
377
  return i;
 
378
}
 
379
 
 
380
PRBool TimerThread::RemoveTimerInternal(nsTimerImpl *aTimer)
 
381
{
 
382
  if (!mTimers.RemoveElement(aTimer))
 
383
    return PR_FALSE;
 
384
 
 
385
  // Order is crucial here -- see nsTimerImpl::Release.
 
386
  aTimer->mArmed = PR_FALSE;
 
387
  NS_RELEASE(aTimer);
 
388
  return PR_TRUE;
 
389
}
 
390
 
 
391
void TimerThread::DoBeforeSleep()
 
392
{
 
393
  mSleeping = PR_TRUE;
 
394
}
 
395
 
 
396
void TimerThread::DoAfterSleep()
 
397
{
 
398
  for (PRInt32 i = 0; i < mTimers.Count(); i ++) {
 
399
    nsTimerImpl *timer = NS_STATIC_CAST(nsTimerImpl*, mTimers[i]);
 
400
    // get and set the delay to cause its timeout to be recomputed
 
401
    PRUint32 delay;
 
402
    timer->GetDelay(&delay);
 
403
    timer->SetDelay(delay);
 
404
  }
 
405
 
 
406
  // nuke the stored adjustments, so they get recalibrated
 
407
  mTimeoutAdjustment = 0;
 
408
  mDelayLineCounter = 0;
 
409
  mSleeping = PR_FALSE;
 
410
}
 
411
 
 
412
 
 
413
/* void observe (in nsISupports aSubject, in string aTopic, in wstring aData); */
 
414
NS_IMETHODIMP
 
415
TimerThread::Observe(nsISupports* /* aSubject */, const char *aTopic, const PRUnichar* /* aData */)
 
416
{
 
417
  if (strcmp(aTopic, "sleep_notification") == 0)
 
418
    DoBeforeSleep();
 
419
  else if (strcmp(aTopic, "wake_notification") == 0)
 
420
    DoAfterSleep();
 
421
 
 
422
  return NS_OK;
 
423
}