|
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/. */ |
|
5 |
|
6 #include "nsTimerImpl.h" |
|
7 #include "TimerThread.h" |
|
8 |
|
9 #include "nsThreadUtils.h" |
|
10 #include "pratom.h" |
|
11 |
|
12 #include "nsIObserverService.h" |
|
13 #include "nsIServiceManager.h" |
|
14 #include "mozilla/Services.h" |
|
15 #include "mozilla/ChaosMode.h" |
|
16 #include "mozilla/ArrayUtils.h" |
|
17 |
|
18 #include <math.h> |
|
19 |
|
20 using namespace mozilla; |
|
21 |
|
22 NS_IMPL_ISUPPORTS(TimerThread, nsIRunnable, nsIObserver) |
|
23 |
|
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 } |
|
34 |
|
35 TimerThread::~TimerThread() |
|
36 { |
|
37 mThread = nullptr; |
|
38 |
|
39 NS_ASSERTION(mTimers.IsEmpty(), "Timers remain in TimerThread::~TimerThread"); |
|
40 } |
|
41 |
|
42 nsresult |
|
43 TimerThread::InitLocks() |
|
44 { |
|
45 return NS_OK; |
|
46 } |
|
47 |
|
48 namespace { |
|
49 |
|
50 class TimerObserverRunnable : public nsRunnable |
|
51 { |
|
52 public: |
|
53 TimerObserverRunnable(nsIObserver* observer) |
|
54 : mObserver(observer) |
|
55 { } |
|
56 |
|
57 NS_DECL_NSIRUNNABLE |
|
58 |
|
59 private: |
|
60 nsCOMPtr<nsIObserver> mObserver; |
|
61 }; |
|
62 |
|
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 } |
|
76 |
|
77 } // anonymous namespace |
|
78 |
|
79 nsresult TimerThread::Init() |
|
80 { |
|
81 PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("TimerThread::Init [%d]\n", mInitialized)); |
|
82 |
|
83 if (mInitialized) { |
|
84 if (!mThread) |
|
85 return NS_ERROR_FAILURE; |
|
86 |
|
87 return NS_OK; |
|
88 } |
|
89 |
|
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 } |
|
105 |
|
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 } |
|
118 |
|
119 if (!mThread) |
|
120 return NS_ERROR_FAILURE; |
|
121 |
|
122 return NS_OK; |
|
123 } |
|
124 |
|
125 nsresult TimerThread::Shutdown() |
|
126 { |
|
127 PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("TimerThread::Shutdown begin\n")); |
|
128 |
|
129 if (!mThread) |
|
130 return NS_ERROR_NOT_INITIALIZED; |
|
131 |
|
132 nsTArray<nsTimerImpl*> timers; |
|
133 { // lock scope |
|
134 MonitorAutoLock lock(mMonitor); |
|
135 |
|
136 mShutdown = true; |
|
137 |
|
138 // notify the cond var so that Run() can return |
|
139 if (mWaiting) { |
|
140 mNotified = true; |
|
141 mMonitor.Notify(); |
|
142 } |
|
143 |
|
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 } |
|
153 |
|
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 } |
|
160 |
|
161 mThread->Shutdown(); // wait for the thread to die |
|
162 |
|
163 PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("TimerThread::Shutdown end\n")); |
|
164 return NS_OK; |
|
165 } |
|
166 |
|
167 #ifdef MOZ_NUWA_PROCESS |
|
168 #include "ipc/Nuwa.h" |
|
169 #endif |
|
170 |
|
171 /* void Run(); */ |
|
172 NS_IMETHODIMP TimerThread::Run() |
|
173 { |
|
174 PR_SetCurrentThreadName("Timer"); |
|
175 |
|
176 #ifdef MOZ_NUWA_PROCESS |
|
177 if (IsNuwaProcess()) { |
|
178 NS_ASSERTION(NuwaMarkCurrentThread != nullptr, |
|
179 "NuwaMarkCurrentThread is undefined!"); |
|
180 NuwaMarkCurrentThread(nullptr, nullptr); |
|
181 } |
|
182 #endif |
|
183 |
|
184 MonitorAutoLock lock(mMonitor); |
|
185 |
|
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 } |
|
203 |
|
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; |
|
208 |
|
209 while (!mShutdown) { |
|
210 // Have to use PRIntervalTime here, since PR_WaitCondVar takes it |
|
211 PRIntervalTime waitFor; |
|
212 bool forceRunThisTimer = forceRunNextTimer; |
|
213 forceRunNextTimer = false; |
|
214 |
|
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; |
|
226 |
|
227 if (!mTimers.IsEmpty()) { |
|
228 timer = mTimers[0]; |
|
229 |
|
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. |
|
237 |
|
238 nsRefPtr<nsTimerImpl> timerRef(timer); |
|
239 RemoveTimerInternal(timer); |
|
240 timer = nullptr; |
|
241 |
|
242 { |
|
243 // We release mMonitor around the Fire call to avoid deadlock. |
|
244 MonitorAutoUnlock unlock(mMonitor); |
|
245 |
|
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 |
|
253 |
|
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()); |
|
258 |
|
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; |
|
265 |
|
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 } |
|
280 |
|
281 if (mShutdown) |
|
282 break; |
|
283 |
|
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 } |
|
289 |
|
290 if (!mTimers.IsEmpty()) { |
|
291 timer = mTimers[0]; |
|
292 |
|
293 TimeStamp timeout = timer->mTimeout; |
|
294 |
|
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; |
|
303 |
|
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 } |
|
314 |
|
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 } |
|
323 |
|
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 } |
|
335 |
|
336 mWaiting = true; |
|
337 mNotified = false; |
|
338 mMonitor.Wait(waitFor); |
|
339 if (mNotified) { |
|
340 forceRunNextTimer = false; |
|
341 } |
|
342 mWaiting = false; |
|
343 } |
|
344 |
|
345 return NS_OK; |
|
346 } |
|
347 |
|
348 nsresult TimerThread::AddTimer(nsTimerImpl *aTimer) |
|
349 { |
|
350 MonitorAutoLock lock(mMonitor); |
|
351 |
|
352 // Add the timer to our list. |
|
353 int32_t i = AddTimerInternal(aTimer); |
|
354 if (i < 0) |
|
355 return NS_ERROR_OUT_OF_MEMORY; |
|
356 |
|
357 // Awaken the timer thread. |
|
358 if (mWaiting && i == 0) { |
|
359 mNotified = true; |
|
360 mMonitor.Notify(); |
|
361 } |
|
362 |
|
363 return NS_OK; |
|
364 } |
|
365 |
|
366 nsresult TimerThread::TimerDelayChanged(nsTimerImpl *aTimer) |
|
367 { |
|
368 MonitorAutoLock lock(mMonitor); |
|
369 |
|
370 // Our caller has a strong ref to aTimer, so it can't go away here under |
|
371 // ReleaseTimerInternal. |
|
372 RemoveTimerInternal(aTimer); |
|
373 |
|
374 int32_t i = AddTimerInternal(aTimer); |
|
375 if (i < 0) |
|
376 return NS_ERROR_OUT_OF_MEMORY; |
|
377 |
|
378 // Awaken the timer thread. |
|
379 if (mWaiting && i == 0) { |
|
380 mNotified = true; |
|
381 mMonitor.Notify(); |
|
382 } |
|
383 |
|
384 return NS_OK; |
|
385 } |
|
386 |
|
387 nsresult TimerThread::RemoveTimer(nsTimerImpl *aTimer) |
|
388 { |
|
389 MonitorAutoLock lock(mMonitor); |
|
390 |
|
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. |
|
397 |
|
398 if (!RemoveTimerInternal(aTimer)) |
|
399 return NS_ERROR_NOT_AVAILABLE; |
|
400 |
|
401 // Awaken the timer thread. |
|
402 if (mWaiting) { |
|
403 mNotified = true; |
|
404 mMonitor.Notify(); |
|
405 } |
|
406 |
|
407 return NS_OK; |
|
408 } |
|
409 |
|
410 // This function must be called from within a lock |
|
411 int32_t TimerThread::AddTimerInternal(nsTimerImpl *aTimer) |
|
412 { |
|
413 if (mShutdown) |
|
414 return -1; |
|
415 |
|
416 TimeStamp now = TimeStamp::Now(); |
|
417 |
|
418 TimerAdditionComparator c(now, aTimer); |
|
419 nsTimerImpl** insertSlot = mTimers.InsertElementSorted(aTimer, c); |
|
420 |
|
421 if (!insertSlot) |
|
422 return -1; |
|
423 |
|
424 aTimer->mArmed = true; |
|
425 NS_ADDREF(aTimer); |
|
426 |
|
427 #ifdef MOZ_TASK_TRACER |
|
428 aTimer->DispatchTracedTask(); |
|
429 #endif |
|
430 |
|
431 return insertSlot - mTimers.Elements(); |
|
432 } |
|
433 |
|
434 bool TimerThread::RemoveTimerInternal(nsTimerImpl *aTimer) |
|
435 { |
|
436 if (!mTimers.RemoveElement(aTimer)) |
|
437 return false; |
|
438 |
|
439 ReleaseTimerInternal(aTimer); |
|
440 return true; |
|
441 } |
|
442 |
|
443 void TimerThread::ReleaseTimerInternal(nsTimerImpl *aTimer) |
|
444 { |
|
445 // Order is crucial here -- see nsTimerImpl::Release. |
|
446 aTimer->mArmed = false; |
|
447 NS_RELEASE(aTimer); |
|
448 } |
|
449 |
|
450 void TimerThread::DoBeforeSleep() |
|
451 { |
|
452 mSleeping = true; |
|
453 } |
|
454 |
|
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 } |
|
465 |
|
466 mSleeping = false; |
|
467 } |
|
468 |
|
469 |
|
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(); |
|
480 |
|
481 return NS_OK; |
|
482 } |