1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsTimerImpl.h"
8 #include "TimerThread.h"
10 #include "nsThreadUtils.h"
13 #include "nsIObserverService.h"
14 #include "nsIServiceManager.h"
15 #include "mozilla/Services.h"
16 #include "mozilla/ChaosMode.h"
17 #include "mozilla/ArrayUtils.h"
18 #include "mozilla/BinarySearch.h"
22 using namespace mozilla
;
24 NS_IMPL_ISUPPORTS(TimerThread
, nsIRunnable
, nsIObserver
)
26 TimerThread::TimerThread() :
27 mInitInProgress(false),
29 mMonitor("TimerThread.mMonitor"),
34 mLastTimerEventLoopRun(TimeStamp::Now())
38 TimerThread::~TimerThread()
42 NS_ASSERTION(mTimers
.IsEmpty(), "Timers remain in TimerThread::~TimerThread");
46 TimerThread::InitLocks()
53 class TimerObserverRunnable
: public nsRunnable
56 explicit TimerObserverRunnable(nsIObserver
* aObserver
)
57 : mObserver(aObserver
)
64 nsCOMPtr
<nsIObserver
> mObserver
;
68 TimerObserverRunnable::Run()
70 nsCOMPtr
<nsIObserverService
> observerService
=
71 mozilla::services::GetObserverService();
72 if (observerService
) {
73 observerService
->AddObserver(mObserver
, "sleep_notification", false);
74 observerService
->AddObserver(mObserver
, "wake_notification", false);
75 observerService
->AddObserver(mObserver
, "suspend_process_notification", false);
76 observerService
->AddObserver(mObserver
, "resume_process_notification", false);
81 } // anonymous namespace
86 PR_LOG(GetTimerLog(), PR_LOG_DEBUG
,
87 ("TimerThread::Init [%d]\n", mInitialized
));
91 return NS_ERROR_FAILURE
;
97 if (mInitInProgress
.exchange(true) == false) {
98 // We hold on to mThread to keep the thread alive.
99 nsresult rv
= NS_NewThread(getter_AddRefs(mThread
), this);
103 nsRefPtr
<TimerObserverRunnable
> r
= new TimerObserverRunnable(this);
104 if (NS_IsMainThread()) {
107 NS_DispatchToMainThread(r
);
112 MonitorAutoLock
lock(mMonitor
);
114 mMonitor
.NotifyAll();
117 MonitorAutoLock
lock(mMonitor
);
118 while (!mInitialized
) {
124 return NS_ERROR_FAILURE
;
131 TimerThread::Shutdown()
133 PR_LOG(GetTimerLog(), PR_LOG_DEBUG
, ("TimerThread::Shutdown begin\n"));
136 return NS_ERROR_NOT_INITIALIZED
;
139 nsTArray
<nsTimerImpl
*> timers
;
142 MonitorAutoLock
lock(mMonitor
);
146 // notify the cond var so that Run() can return
152 // Need to copy content of mTimers array to a local array
153 // because call to timers' ReleaseCallback() (and release its self)
154 // must not be done under the lock. Destructor of a callback
155 // might potentially call some code reentering the same lock
156 // that leads to unexpected behavior or deadlock.
158 timers
.AppendElements(mTimers
);
162 uint32_t timersCount
= timers
.Length();
163 for (uint32_t i
= 0; i
< timersCount
; i
++) {
164 nsTimerImpl
* timer
= timers
[i
];
165 timer
->ReleaseCallback();
166 ReleaseTimerInternal(timer
);
169 mThread
->Shutdown(); // wait for the thread to die
171 PR_LOG(GetTimerLog(), PR_LOG_DEBUG
, ("TimerThread::Shutdown end\n"));
175 #ifdef MOZ_NUWA_PROCESS
176 #include "ipc/Nuwa.h"
181 struct MicrosecondsToInterval
183 PRIntervalTime
operator[](size_t aMs
) const {
184 return PR_MicrosecondsToInterval(aMs
);
188 struct IntervalComparator
190 int operator()(PRIntervalTime aInterval
) const {
191 return (0 < aInterval
) ? -1 : 1;
201 PR_SetCurrentThreadName("Timer");
203 #ifdef MOZ_NUWA_PROCESS
204 if (IsNuwaProcess()) {
205 NuwaMarkCurrentThread(nullptr, nullptr);
209 MonitorAutoLock
lock(mMonitor
);
211 // We need to know how many microseconds give a positive PRIntervalTime. This
212 // is platform-dependent and we calculate it at runtime, finding a value |v|
213 // such that |PR_MicrosecondsToInterval(v) > 0| and then binary-searching in
214 // the range [0, v) to find the ms-to-interval scale.
215 uint32_t usForPosInterval
= 1;
216 while (PR_MicrosecondsToInterval(usForPosInterval
) == 0) {
217 usForPosInterval
<<= 1;
220 size_t usIntervalResolution
;
221 BinarySearchIf(MicrosecondsToInterval(), 0, usForPosInterval
, IntervalComparator(), &usIntervalResolution
);
222 MOZ_ASSERT(PR_MicrosecondsToInterval(usIntervalResolution
- 1) == 0);
223 MOZ_ASSERT(PR_MicrosecondsToInterval(usIntervalResolution
) == 1);
225 // Half of the amount of microseconds needed to get positive PRIntervalTime.
226 // We use this to decide how to round our wait times later
227 int32_t halfMicrosecondsIntervalResolution
= usIntervalResolution
/ 2;
228 bool forceRunNextTimer
= false;
231 // Have to use PRIntervalTime here, since PR_WaitCondVar takes it
232 PRIntervalTime waitFor
;
233 bool forceRunThisTimer
= forceRunNextTimer
;
234 forceRunNextTimer
= false;
237 // Sleep for 0.1 seconds while not firing timers.
238 uint32_t milliseconds
= 100;
239 if (ChaosMode::isActive(ChaosMode::TimerScheduling
)) {
240 milliseconds
= ChaosMode::randomUint32LessThan(200);
242 waitFor
= PR_MillisecondsToInterval(milliseconds
);
244 waitFor
= PR_INTERVAL_NO_TIMEOUT
;
245 TimeStamp now
= TimeStamp::Now();
246 mLastTimerEventLoopRun
= now
;
247 nsTimerImpl
* timer
= nullptr;
249 if (!mTimers
.IsEmpty()) {
252 if (now
>= timer
->mTimeout
|| forceRunThisTimer
) {
254 // NB: AddRef before the Release under RemoveTimerInternal to avoid
255 // mRefCnt passing through zero, in case all other refs than the one
256 // from mTimers have gone away (the last non-mTimers[i]-ref's Release
257 // must be racing with us, blocked in gThread->RemoveTimer waiting
258 // for TimerThread::mMonitor, under nsTimerImpl::Release.
260 nsRefPtr
<nsTimerImpl
> timerRef(timer
);
261 RemoveTimerInternal(timer
);
265 if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG
)) {
266 PR_LOG(GetTimerLog(), PR_LOG_DEBUG
,
267 ("Timer thread woke up %fms from when it was supposed to\n",
268 fabs((now
- timerRef
->mTimeout
).ToMilliseconds())));
273 // We release mMonitor around the Fire call to avoid deadlock.
274 MonitorAutoUnlock
unlock(mMonitor
);
276 // We are going to let the call to PostTimerEvent here handle the
277 // release of the timer so that we don't end up releasing the timer
278 // on the TimerThread instead of on the thread it targets.
279 timerRef
= nsTimerImpl::PostTimerEvent(timerRef
.forget());
283 // We got our reference back due to an error.
284 // Unhook the nsRefPtr, and release manually so we can get the
286 nsrefcnt rc
= timerRef
.forget().take()->Release();
289 // The nsITimer interface requires that its users keep a reference
290 // to the timers they use while those timers are initialized but
291 // have not yet fired. If this ever happens, it is a bug in the
292 // code that created and used the timer.
294 // Further, note that this should never happen even with a
295 // misbehaving user, because nsTimerImpl::Release checks for a
296 // refcount of 1 with an armed timer (a timer whose only reference
297 // is from the timer thread) and when it hits this will remove the
298 // timer from the timer thread and thus destroy the last reference,
299 // preventing this situation from occurring.
300 MOZ_ASSERT(rc
!= 0, "destroyed timer off its target thread!");
307 // Update now, as PostTimerEvent plus the locking may have taken a
308 // tick or two, and we may goto next below.
309 now
= TimeStamp::Now();
313 if (!mTimers
.IsEmpty()) {
316 TimeStamp timeout
= timer
->mTimeout
;
318 // Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer
319 // is due now or overdue.
321 // Note that we can only sleep for integer values of a certain
322 // resolution. We use halfMicrosecondsIntervalResolution, calculated
323 // before, to do the optimal rounding (i.e., of how to decide what
324 // interval is so small we should not wait at all).
325 double microseconds
= (timeout
- now
).ToMilliseconds() * 1000;
327 if (ChaosMode::isActive(ChaosMode::TimerScheduling
)) {
328 // The mean value of sFractions must be 1 to ensure that
329 // the average of a long sequence of timeouts converges to the
330 // actual sum of their times.
331 static const float sFractions
[] = {
332 0.0f
, 0.25f
, 0.5f
, 0.75f
, 1.0f
, 1.75f
, 2.75f
335 sFractions
[ChaosMode::randomUint32LessThan(ArrayLength(sFractions
))];
336 forceRunNextTimer
= true;
339 if (microseconds
< halfMicrosecondsIntervalResolution
) {
340 forceRunNextTimer
= false;
341 goto next
; // round down; execute event now
343 waitFor
= PR_MicrosecondsToInterval(
344 static_cast<uint32_t>(microseconds
)); // Floor is accurate enough.
346 waitFor
= 1; // round up, wait the minimum time we can wait
351 if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG
)) {
352 if (waitFor
== PR_INTERVAL_NO_TIMEOUT
)
353 PR_LOG(GetTimerLog(), PR_LOG_DEBUG
,
354 ("waiting for PR_INTERVAL_NO_TIMEOUT\n"));
356 PR_LOG(GetTimerLog(), PR_LOG_DEBUG
,
357 ("waiting for %u\n", PR_IntervalToMilliseconds(waitFor
)));
364 mMonitor
.Wait(waitFor
);
366 forceRunNextTimer
= false;
375 TimerThread::AddTimer(nsTimerImpl
* aTimer
)
377 MonitorAutoLock
lock(mMonitor
);
379 // Add the timer to our list.
380 int32_t i
= AddTimerInternal(aTimer
);
382 return NS_ERROR_OUT_OF_MEMORY
;
385 // Awaken the timer thread.
386 if (mWaiting
&& i
== 0) {
395 TimerThread::TimerDelayChanged(nsTimerImpl
* aTimer
)
397 MonitorAutoLock
lock(mMonitor
);
399 // Our caller has a strong ref to aTimer, so it can't go away here under
400 // ReleaseTimerInternal.
401 RemoveTimerInternal(aTimer
);
403 int32_t i
= AddTimerInternal(aTimer
);
405 return NS_ERROR_OUT_OF_MEMORY
;
408 // Awaken the timer thread.
409 if (mWaiting
&& i
== 0) {
418 TimerThread::RemoveTimer(nsTimerImpl
* aTimer
)
420 MonitorAutoLock
lock(mMonitor
);
422 // Remove the timer from our array. Tell callers that aTimer was not found
423 // by returning NS_ERROR_NOT_AVAILABLE. Unlike the TimerDelayChanged case
424 // immediately above, our caller may be passing a (now-)weak ref in via the
425 // aTimer param, specifically when nsTimerImpl::Release loses a race with
426 // TimerThread::Run, must wait for the mMonitor auto-lock here, and during the
427 // wait Run drops the only remaining ref to aTimer via RemoveTimerInternal.
429 if (!RemoveTimerInternal(aTimer
)) {
430 return NS_ERROR_NOT_AVAILABLE
;
433 // Awaken the timer thread.
442 // This function must be called from within a lock
444 TimerThread::AddTimerInternal(nsTimerImpl
* aTimer
)
446 mMonitor
.AssertCurrentThreadOwns();
451 TimeStamp now
= TimeStamp::Now();
453 TimerAdditionComparator
c(now
, aTimer
);
454 nsTimerImpl
** insertSlot
= mTimers
.InsertElementSorted(aTimer
, c
);
460 aTimer
->mArmed
= true;
463 #ifdef MOZ_TASK_TRACER
464 // Create a FakeTracedTask, and dispatch it here. This is the start point of
466 aTimer
->DispatchTracedTask();
469 return insertSlot
- mTimers
.Elements();
473 TimerThread::RemoveTimerInternal(nsTimerImpl
* aTimer
)
475 mMonitor
.AssertCurrentThreadOwns();
476 if (!mTimers
.RemoveElement(aTimer
)) {
480 ReleaseTimerInternal(aTimer
);
485 TimerThread::ReleaseTimerInternal(nsTimerImpl
* aTimer
)
488 // copied to a local array before releasing in shutdown
489 mMonitor
.AssertCurrentThreadOwns();
491 // Order is crucial here -- see nsTimerImpl::Release.
492 aTimer
->mArmed
= false;
497 TimerThread::DoBeforeSleep()
500 MonitorAutoLock
lock(mMonitor
);
501 mLastTimerEventLoopRun
= TimeStamp::Now();
505 // Note: wake may be notified without preceding sleep notification
507 TimerThread::DoAfterSleep()
510 TimeStamp now
= TimeStamp::Now();
512 MonitorAutoLock
lock(mMonitor
);
514 // an over-estimate of time slept, usually small
515 TimeDuration slept
= now
- mLastTimerEventLoopRun
;
517 // Adjust all old timers to expire roughly similar times in the future
518 // compared to when we went to sleep, by adding the time we slept to the
519 // target time. It's slightly possible a few will end up slightly in the
520 // past and fire immediately, but ordering should be preserved. All
521 // timers retain the exact same order (and relative times) as before
523 for (uint32_t i
= 0; i
< mTimers
.Length(); i
++) {
524 nsTimerImpl
* timer
= mTimers
[i
];
525 timer
->mTimeout
+= slept
;
528 mLastTimerEventLoopRun
= now
;
530 // Wake up the timer thread to process the updated array
536 /* void observe (in nsISupports aSubject, in string aTopic, in wstring aData); */
538 TimerThread::Observe(nsISupports
* /* aSubject */, const char* aTopic
,
539 const char16_t
* /* aData */)
541 if (strcmp(aTopic
, "sleep_notification") == 0 ||
542 strcmp(aTopic
, "suspend_process_notification") == 0) {
544 } else if (strcmp(aTopic
, "wake_notification") == 0 ||
545 strcmp(aTopic
, "resume_process_notification") == 0) {