Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsTimerImpl.h"
7 #include "TimerThread.h"
9 #include "nsThreadUtils.h"
10 #include "pratom.h"
12 #include "nsIObserverService.h"
13 #include "nsIServiceManager.h"
14 #include "mozilla/Services.h"
15 #include "mozilla/ChaosMode.h"
16 #include "mozilla/ArrayUtils.h"
18 #include <math.h>
20 using namespace mozilla;
22 NS_IMPL_ISUPPORTS(TimerThread, nsIRunnable, nsIObserver)
24 TimerThread::TimerThread() :
25 mInitInProgress(false),
26 mInitialized(false),
27 mMonitor("TimerThread.mMonitor"),
28 mShutdown(false),
29 mWaiting(false),
30 mNotified(false),
31 mSleeping(false)
32 {
33 }
35 TimerThread::~TimerThread()
36 {
37 mThread = nullptr;
39 NS_ASSERTION(mTimers.IsEmpty(), "Timers remain in TimerThread::~TimerThread");
40 }
42 nsresult
43 TimerThread::InitLocks()
44 {
45 return NS_OK;
46 }
48 namespace {
50 class TimerObserverRunnable : public nsRunnable
51 {
52 public:
53 TimerObserverRunnable(nsIObserver* observer)
54 : mObserver(observer)
55 { }
57 NS_DECL_NSIRUNNABLE
59 private:
60 nsCOMPtr<nsIObserver> mObserver;
61 };
63 NS_IMETHODIMP
64 TimerObserverRunnable::Run()
65 {
66 nsCOMPtr<nsIObserverService> observerService =
67 mozilla::services::GetObserverService();
68 if (observerService) {
69 observerService->AddObserver(mObserver, "sleep_notification", false);
70 observerService->AddObserver(mObserver, "wake_notification", false);
71 observerService->AddObserver(mObserver, "suspend_process_notification", false);
72 observerService->AddObserver(mObserver, "resume_process_notification", false);
73 }
74 return NS_OK;
75 }
77 } // anonymous namespace
79 nsresult TimerThread::Init()
80 {
81 PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("TimerThread::Init [%d]\n", mInitialized));
83 if (mInitialized) {
84 if (!mThread)
85 return NS_ERROR_FAILURE;
87 return NS_OK;
88 }
90 if (mInitInProgress.exchange(true) == false) {
91 // We hold on to mThread to keep the thread alive.
92 nsresult rv = NS_NewThread(getter_AddRefs(mThread), this);
93 if (NS_FAILED(rv)) {
94 mThread = nullptr;
95 }
96 else {
97 nsRefPtr<TimerObserverRunnable> r = new TimerObserverRunnable(this);
98 if (NS_IsMainThread()) {
99 r->Run();
100 }
101 else {
102 NS_DispatchToMainThread(r);
103 }
104 }
106 {
107 MonitorAutoLock lock(mMonitor);
108 mInitialized = true;
109 mMonitor.NotifyAll();
110 }
111 }
112 else {
113 MonitorAutoLock lock(mMonitor);
114 while (!mInitialized) {
115 mMonitor.Wait();
116 }
117 }
119 if (!mThread)
120 return NS_ERROR_FAILURE;
122 return NS_OK;
123 }
125 nsresult TimerThread::Shutdown()
126 {
127 PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("TimerThread::Shutdown begin\n"));
129 if (!mThread)
130 return NS_ERROR_NOT_INITIALIZED;
132 nsTArray<nsTimerImpl*> timers;
133 { // lock scope
134 MonitorAutoLock lock(mMonitor);
136 mShutdown = true;
138 // notify the cond var so that Run() can return
139 if (mWaiting) {
140 mNotified = true;
141 mMonitor.Notify();
142 }
144 // Need to copy content of mTimers array to a local array
145 // because call to timers' ReleaseCallback() (and release its self)
146 // must not be done under the lock. Destructor of a callback
147 // might potentially call some code reentering the same lock
148 // that leads to unexpected behavior or deadlock.
149 // See bug 422472.
150 timers.AppendElements(mTimers);
151 mTimers.Clear();
152 }
154 uint32_t timersCount = timers.Length();
155 for (uint32_t i = 0; i < timersCount; i++) {
156 nsTimerImpl *timer = timers[i];
157 timer->ReleaseCallback();
158 ReleaseTimerInternal(timer);
159 }
161 mThread->Shutdown(); // wait for the thread to die
163 PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("TimerThread::Shutdown end\n"));
164 return NS_OK;
165 }
167 #ifdef MOZ_NUWA_PROCESS
168 #include "ipc/Nuwa.h"
169 #endif
171 /* void Run(); */
172 NS_IMETHODIMP TimerThread::Run()
173 {
174 PR_SetCurrentThreadName("Timer");
176 #ifdef MOZ_NUWA_PROCESS
177 if (IsNuwaProcess()) {
178 NS_ASSERTION(NuwaMarkCurrentThread != nullptr,
179 "NuwaMarkCurrentThread is undefined!");
180 NuwaMarkCurrentThread(nullptr, nullptr);
181 }
182 #endif
184 MonitorAutoLock lock(mMonitor);
186 // We need to know how many microseconds give a positive PRIntervalTime. This
187 // is platform-dependent, we calculate it at runtime now.
188 // First we find a value such that PR_MicrosecondsToInterval(high) = 1
189 int32_t low = 0, high = 1;
190 while (PR_MicrosecondsToInterval(high) == 0)
191 high <<= 1;
192 // We now have
193 // PR_MicrosecondsToInterval(low) = 0
194 // PR_MicrosecondsToInterval(high) = 1
195 // and we can proceed to find the critical value using binary search
196 while (high-low > 1) {
197 int32_t mid = (high+low) >> 1;
198 if (PR_MicrosecondsToInterval(mid) == 0)
199 low = mid;
200 else
201 high = mid;
202 }
204 // Half of the amount of microseconds needed to get positive PRIntervalTime.
205 // We use this to decide how to round our wait times later
206 int32_t halfMicrosecondsIntervalResolution = high >> 1;
207 bool forceRunNextTimer = false;
209 while (!mShutdown) {
210 // Have to use PRIntervalTime here, since PR_WaitCondVar takes it
211 PRIntervalTime waitFor;
212 bool forceRunThisTimer = forceRunNextTimer;
213 forceRunNextTimer = false;
215 if (mSleeping) {
216 // Sleep for 0.1 seconds while not firing timers.
217 uint32_t milliseconds = 100;
218 if (ChaosMode::isActive()) {
219 milliseconds = ChaosMode::randomUint32LessThan(200);
220 }
221 waitFor = PR_MillisecondsToInterval(milliseconds);
222 } else {
223 waitFor = PR_INTERVAL_NO_TIMEOUT;
224 TimeStamp now = TimeStamp::Now();
225 nsTimerImpl *timer = nullptr;
227 if (!mTimers.IsEmpty()) {
228 timer = mTimers[0];
230 if (now >= timer->mTimeout || forceRunThisTimer) {
231 next:
232 // NB: AddRef before the Release under RemoveTimerInternal to avoid
233 // mRefCnt passing through zero, in case all other refs than the one
234 // from mTimers have gone away (the last non-mTimers[i]-ref's Release
235 // must be racing with us, blocked in gThread->RemoveTimer waiting
236 // for TimerThread::mMonitor, under nsTimerImpl::Release.
238 nsRefPtr<nsTimerImpl> timerRef(timer);
239 RemoveTimerInternal(timer);
240 timer = nullptr;
242 {
243 // We release mMonitor around the Fire call to avoid deadlock.
244 MonitorAutoUnlock unlock(mMonitor);
246 #ifdef DEBUG_TIMERS
247 if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) {
248 PR_LOG(GetTimerLog(), PR_LOG_DEBUG,
249 ("Timer thread woke up %fms from when it was supposed to\n",
250 fabs((now - timerRef->mTimeout).ToMilliseconds())));
251 }
252 #endif
254 // We are going to let the call to PostTimerEvent here handle the
255 // release of the timer so that we don't end up releasing the timer
256 // on the TimerThread instead of on the thread it targets.
257 timerRef = nsTimerImpl::PostTimerEvent(timerRef.forget());
259 if (timerRef) {
260 // We got our reference back due to an error.
261 // Unhook the nsRefPtr, and release manually so we can get the
262 // refcount.
263 nsrefcnt rc = timerRef.forget().take()->Release();
264 (void)rc;
266 // The nsITimer interface requires that its users keep a reference
267 // to the timers they use while those timers are initialized but
268 // have not yet fired. If this ever happens, it is a bug in the
269 // code that created and used the timer.
270 //
271 // Further, note that this should never happen even with a
272 // misbehaving user, because nsTimerImpl::Release checks for a
273 // refcount of 1 with an armed timer (a timer whose only reference
274 // is from the timer thread) and when it hits this will remove the
275 // timer from the timer thread and thus destroy the last reference,
276 // preventing this situation from occurring.
277 MOZ_ASSERT(rc != 0, "destroyed timer off its target thread!");
278 }
279 }
281 if (mShutdown)
282 break;
284 // Update now, as PostTimerEvent plus the locking may have taken a
285 // tick or two, and we may goto next below.
286 now = TimeStamp::Now();
287 }
288 }
290 if (!mTimers.IsEmpty()) {
291 timer = mTimers[0];
293 TimeStamp timeout = timer->mTimeout;
295 // Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer
296 // is due now or overdue.
297 //
298 // Note that we can only sleep for integer values of a certain
299 // resolution. We use halfMicrosecondsIntervalResolution, calculated
300 // before, to do the optimal rounding (i.e., of how to decide what
301 // interval is so small we should not wait at all).
302 double microseconds = (timeout - now).ToMilliseconds()*1000;
304 if (ChaosMode::isActive()) {
305 // The mean value of sFractions must be 1 to ensure that
306 // the average of a long sequence of timeouts converges to the
307 // actual sum of their times.
308 static const float sFractions[] = {
309 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 1.75f, 2.75f
310 };
311 microseconds *= sFractions[ChaosMode::randomUint32LessThan(ArrayLength(sFractions))];
312 forceRunNextTimer = true;
313 }
315 if (microseconds < halfMicrosecondsIntervalResolution) {
316 forceRunNextTimer = false;
317 goto next; // round down; execute event now
318 }
319 waitFor = PR_MicrosecondsToInterval(static_cast<uint32_t>(microseconds)); // Floor is accurate enough.
320 if (waitFor == 0)
321 waitFor = 1; // round up, wait the minimum time we can wait
322 }
324 #ifdef DEBUG_TIMERS
325 if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) {
326 if (waitFor == PR_INTERVAL_NO_TIMEOUT)
327 PR_LOG(GetTimerLog(), PR_LOG_DEBUG,
328 ("waiting for PR_INTERVAL_NO_TIMEOUT\n"));
329 else
330 PR_LOG(GetTimerLog(), PR_LOG_DEBUG,
331 ("waiting for %u\n", PR_IntervalToMilliseconds(waitFor)));
332 }
333 #endif
334 }
336 mWaiting = true;
337 mNotified = false;
338 mMonitor.Wait(waitFor);
339 if (mNotified) {
340 forceRunNextTimer = false;
341 }
342 mWaiting = false;
343 }
345 return NS_OK;
346 }
348 nsresult TimerThread::AddTimer(nsTimerImpl *aTimer)
349 {
350 MonitorAutoLock lock(mMonitor);
352 // Add the timer to our list.
353 int32_t i = AddTimerInternal(aTimer);
354 if (i < 0)
355 return NS_ERROR_OUT_OF_MEMORY;
357 // Awaken the timer thread.
358 if (mWaiting && i == 0) {
359 mNotified = true;
360 mMonitor.Notify();
361 }
363 return NS_OK;
364 }
366 nsresult TimerThread::TimerDelayChanged(nsTimerImpl *aTimer)
367 {
368 MonitorAutoLock lock(mMonitor);
370 // Our caller has a strong ref to aTimer, so it can't go away here under
371 // ReleaseTimerInternal.
372 RemoveTimerInternal(aTimer);
374 int32_t i = AddTimerInternal(aTimer);
375 if (i < 0)
376 return NS_ERROR_OUT_OF_MEMORY;
378 // Awaken the timer thread.
379 if (mWaiting && i == 0) {
380 mNotified = true;
381 mMonitor.Notify();
382 }
384 return NS_OK;
385 }
387 nsresult TimerThread::RemoveTimer(nsTimerImpl *aTimer)
388 {
389 MonitorAutoLock lock(mMonitor);
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 mMonitor 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 (mWaiting) {
403 mNotified = true;
404 mMonitor.Notify();
405 }
407 return NS_OK;
408 }
410 // This function must be called from within a lock
411 int32_t TimerThread::AddTimerInternal(nsTimerImpl *aTimer)
412 {
413 if (mShutdown)
414 return -1;
416 TimeStamp now = TimeStamp::Now();
418 TimerAdditionComparator c(now, aTimer);
419 nsTimerImpl** insertSlot = mTimers.InsertElementSorted(aTimer, c);
421 if (!insertSlot)
422 return -1;
424 aTimer->mArmed = true;
425 NS_ADDREF(aTimer);
427 #ifdef MOZ_TASK_TRACER
428 aTimer->DispatchTracedTask();
429 #endif
431 return insertSlot - mTimers.Elements();
432 }
434 bool TimerThread::RemoveTimerInternal(nsTimerImpl *aTimer)
435 {
436 if (!mTimers.RemoveElement(aTimer))
437 return false;
439 ReleaseTimerInternal(aTimer);
440 return true;
441 }
443 void TimerThread::ReleaseTimerInternal(nsTimerImpl *aTimer)
444 {
445 // Order is crucial here -- see nsTimerImpl::Release.
446 aTimer->mArmed = false;
447 NS_RELEASE(aTimer);
448 }
450 void TimerThread::DoBeforeSleep()
451 {
452 mSleeping = true;
453 }
455 void TimerThread::DoAfterSleep()
456 {
457 mSleeping = true; // wake may be notified without preceding sleep notification
458 for (uint32_t i = 0; i < mTimers.Length(); i ++) {
459 nsTimerImpl *timer = mTimers[i];
460 // get and set the delay to cause its timeout to be recomputed
461 uint32_t delay;
462 timer->GetDelay(&delay);
463 timer->SetDelay(delay);
464 }
466 mSleeping = false;
467 }
470 /* void observe (in nsISupports aSubject, in string aTopic, in wstring aData); */
471 NS_IMETHODIMP
472 TimerThread::Observe(nsISupports* /* aSubject */, const char *aTopic, const char16_t* /* aData */)
473 {
474 if (strcmp(aTopic, "sleep_notification") == 0 ||
475 strcmp(aTopic, "suspend_process_notification") == 0)
476 DoBeforeSleep();
477 else if (strcmp(aTopic, "wake_notification") == 0 ||
478 strcmp(aTopic, "resume_process_notification") == 0)
479 DoAfterSleep();
481 return NS_OK;
482 }