/src/mozilla-central/xpcom/threads/TimerThread.cpp
Line | Count | Source (jump to first uncovered line) |
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/. */ |
6 | | |
7 | | #include "nsTimerImpl.h" |
8 | | #include "TimerThread.h" |
9 | | |
10 | | #include "nsThreadUtils.h" |
11 | | #include "pratom.h" |
12 | | |
13 | | #include "nsIObserverService.h" |
14 | | #include "nsIServiceManager.h" |
15 | | #include "mozilla/Services.h" |
16 | | #include "mozilla/ChaosMode.h" |
17 | | #include "mozilla/ArenaAllocator.h" |
18 | | #include "mozilla/ArrayUtils.h" |
19 | | #include "mozilla/BinarySearch.h" |
20 | | |
21 | | #include <math.h> |
22 | | |
23 | | using namespace mozilla; |
24 | | #ifdef MOZ_TASK_TRACER |
25 | | #include "GeckoTaskTracerImpl.h" |
26 | | using namespace mozilla::tasktracer; |
27 | | #endif |
28 | | |
29 | | NS_IMPL_ISUPPORTS(TimerThread, nsIRunnable, nsIObserver) |
30 | | |
31 | | TimerThread::TimerThread() : |
32 | | mInitialized(false), |
33 | | mMonitor("TimerThread.mMonitor"), |
34 | | mShutdown(false), |
35 | | mWaiting(false), |
36 | | mNotified(false), |
37 | | mSleeping(false), |
38 | | mAllowedEarlyFiringMicroseconds(0) |
39 | 3 | { |
40 | 3 | } |
41 | | |
42 | | TimerThread::~TimerThread() |
43 | 0 | { |
44 | 0 | mThread = nullptr; |
45 | 0 |
|
46 | 0 | NS_ASSERTION(mTimers.IsEmpty(), "Timers remain in TimerThread::~TimerThread"); |
47 | 0 | } |
48 | | |
49 | | nsresult |
50 | | TimerThread::InitLocks() |
51 | 3 | { |
52 | 3 | return NS_OK; |
53 | 3 | } |
54 | | |
55 | | namespace { |
56 | | |
57 | | class TimerObserverRunnable : public Runnable |
58 | | { |
59 | | public: |
60 | | explicit TimerObserverRunnable(nsIObserver* aObserver) |
61 | | : mozilla::Runnable("TimerObserverRunnable") |
62 | | , mObserver(aObserver) |
63 | 1 | { |
64 | 1 | } |
65 | | |
66 | | NS_DECL_NSIRUNNABLE |
67 | | |
68 | | private: |
69 | | nsCOMPtr<nsIObserver> mObserver; |
70 | | }; |
71 | | |
72 | | NS_IMETHODIMP |
73 | | TimerObserverRunnable::Run() |
74 | 1 | { |
75 | 1 | nsCOMPtr<nsIObserverService> observerService = |
76 | 1 | mozilla::services::GetObserverService(); |
77 | 1 | if (observerService) { |
78 | 1 | observerService->AddObserver(mObserver, "sleep_notification", false); |
79 | 1 | observerService->AddObserver(mObserver, "wake_notification", false); |
80 | 1 | observerService->AddObserver(mObserver, "suspend_process_notification", false); |
81 | 1 | observerService->AddObserver(mObserver, "resume_process_notification", false); |
82 | 1 | } |
83 | 1 | return NS_OK; |
84 | 1 | } |
85 | | |
86 | | } // namespace |
87 | | |
88 | | namespace { |
89 | | |
90 | | // TimerEventAllocator is a thread-safe allocator used only for nsTimerEvents. |
91 | | // It's needed to avoid contention over the default allocator lock when |
92 | | // firing timer events (see bug 733277). The thread-safety is required because |
93 | | // nsTimerEvent objects are allocated on the timer thread, and freed on another |
94 | | // thread. Because TimerEventAllocator has its own lock, contention over that |
95 | | // lock is limited to the allocation and deallocation of nsTimerEvent objects. |
96 | | // |
97 | | // Because this is layered over ArenaAllocator, it never shrinks -- even |
98 | | // "freed" nsTimerEvents aren't truly freed, they're just put onto a free-list |
99 | | // for later recycling. So the amount of memory consumed will always be equal |
100 | | // to the high-water mark consumption. But nsTimerEvents are small and it's |
101 | | // unusual to have more than a few hundred of them, so this shouldn't be a |
102 | | // problem in practice. |
103 | | |
104 | | class TimerEventAllocator |
105 | | { |
106 | | private: |
107 | | struct FreeEntry |
108 | | { |
109 | | FreeEntry* mNext; |
110 | | }; |
111 | | |
112 | | ArenaAllocator<4096> mPool; |
113 | | FreeEntry* mFirstFree; |
114 | | mozilla::Monitor mMonitor; |
115 | | |
116 | | public: |
117 | | TimerEventAllocator() |
118 | | : mPool() |
119 | | , mFirstFree(nullptr) |
120 | | // Timer thread state may be accessed during GC, so uses of this monitor |
121 | | // are not preserved when recording/replaying. |
122 | | , mMonitor("TimerEventAllocator", recordreplay::Behavior::DontPreserve) |
123 | 1 | { |
124 | 1 | } |
125 | | |
126 | | ~TimerEventAllocator() |
127 | 0 | { |
128 | 0 | } |
129 | | |
130 | | void* Alloc(size_t aSize); |
131 | | void Free(void* aPtr); |
132 | | }; |
133 | | |
134 | | } // namespace |
135 | | |
136 | | // This is a nsICancelableRunnable because we can dispatch it to Workers and |
137 | | // those can be shut down at any time, and in these cases, Cancel() is called |
138 | | // instead of Run(). |
139 | | class nsTimerEvent final : public CancelableRunnable |
140 | | { |
141 | | public: |
142 | | NS_IMETHOD Run() override; |
143 | | |
144 | | nsresult Cancel() override |
145 | 0 | { |
146 | 0 | mTimer->Cancel(); |
147 | 0 | return NS_OK; |
148 | 0 | } |
149 | | |
150 | | #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY |
151 | | NS_IMETHOD GetName(nsACString& aName) override; |
152 | | #endif |
153 | | |
154 | | nsTimerEvent() |
155 | | : mozilla::CancelableRunnable("nsTimerEvent") |
156 | | , mTimer() |
157 | | , mGeneration(0) |
158 | 86 | { |
159 | 86 | // Note: We override operator new for this class, and the override is |
160 | 86 | // fallible! |
161 | 86 | sAllocatorUsers++; |
162 | 86 | } |
163 | | |
164 | | TimeStamp mInitTime; |
165 | | |
166 | | static void Init(); |
167 | | static void Shutdown(); |
168 | | static void DeleteAllocatorIfNeeded(); |
169 | | |
170 | | static void* operator new(size_t aSize) CPP_THROW_NEW |
171 | 86 | { |
172 | 86 | return sAllocator->Alloc(aSize); |
173 | 86 | } |
174 | | void operator delete(void* aPtr) |
175 | 0 | { |
176 | 0 | sAllocator->Free(aPtr); |
177 | 0 | DeleteAllocatorIfNeeded(); |
178 | 0 | } |
179 | | |
180 | | already_AddRefed<nsTimerImpl> ForgetTimer() |
181 | 0 | { |
182 | 0 | return mTimer.forget(); |
183 | 0 | } |
184 | | |
185 | | void SetTimer(already_AddRefed<nsTimerImpl> aTimer) |
186 | 86 | { |
187 | 86 | mTimer = aTimer; |
188 | 86 | mGeneration = mTimer->GetGeneration(); |
189 | 86 | } |
190 | | |
191 | | private: |
192 | | nsTimerEvent(const nsTimerEvent&) = delete; |
193 | | nsTimerEvent& operator=(const nsTimerEvent&) = delete; |
194 | | nsTimerEvent& operator=(const nsTimerEvent&&) = delete; |
195 | | |
196 | | ~nsTimerEvent() |
197 | 0 | { |
198 | 0 | MOZ_ASSERT(!sCanDeleteAllocator || sAllocatorUsers > 0, |
199 | 0 | "This will result in us attempting to deallocate the nsTimerEvent allocator twice"); |
200 | 0 | sAllocatorUsers--; |
201 | 0 | } |
202 | | |
203 | | RefPtr<nsTimerImpl> mTimer; |
204 | | int32_t mGeneration; |
205 | | |
206 | | static TimerEventAllocator* sAllocator; |
207 | | |
208 | | // Timer thread state may be accessed during GC, so uses of this atomic are |
209 | | // not preserved when recording/replaying. |
210 | | static Atomic<int32_t, SequentiallyConsistent, |
211 | | recordreplay::Behavior::DontPreserve> sAllocatorUsers; |
212 | | static bool sCanDeleteAllocator; |
213 | | }; |
214 | | |
215 | | TimerEventAllocator* nsTimerEvent::sAllocator = nullptr; |
216 | | Atomic<int32_t, SequentiallyConsistent, |
217 | | recordreplay::Behavior::DontPreserve> nsTimerEvent::sAllocatorUsers; |
218 | | bool nsTimerEvent::sCanDeleteAllocator = false; |
219 | | |
220 | | namespace { |
221 | | |
222 | | void* |
223 | | TimerEventAllocator::Alloc(size_t aSize) |
224 | 86 | { |
225 | 86 | MOZ_ASSERT(aSize == sizeof(nsTimerEvent)); |
226 | 86 | |
227 | 86 | mozilla::MonitorAutoLock lock(mMonitor); |
228 | 86 | |
229 | 86 | void* p; |
230 | 86 | if (mFirstFree) { |
231 | 0 | p = mFirstFree; |
232 | 0 | mFirstFree = mFirstFree->mNext; |
233 | 86 | } else { |
234 | 86 | p = mPool.Allocate(aSize, fallible); |
235 | 86 | } |
236 | 86 | |
237 | 86 | return p; |
238 | 86 | } |
239 | | |
240 | | void |
241 | | TimerEventAllocator::Free(void* aPtr) |
242 | 0 | { |
243 | 0 | mozilla::MonitorAutoLock lock(mMonitor); |
244 | 0 |
|
245 | 0 | FreeEntry* entry = reinterpret_cast<FreeEntry*>(aPtr); |
246 | 0 |
|
247 | 0 | entry->mNext = mFirstFree; |
248 | 0 | mFirstFree = entry; |
249 | 0 | } |
250 | | |
251 | | } // namespace |
252 | | |
253 | | void |
254 | | nsTimerEvent::Init() |
255 | 1 | { |
256 | 1 | sAllocator = new TimerEventAllocator(); |
257 | 1 | } |
258 | | |
259 | | void |
260 | | nsTimerEvent::Shutdown() |
261 | 0 | { |
262 | 0 | sCanDeleteAllocator = true; |
263 | 0 | DeleteAllocatorIfNeeded(); |
264 | 0 | } |
265 | | |
266 | | void |
267 | | nsTimerEvent::DeleteAllocatorIfNeeded() |
268 | 0 | { |
269 | 0 | if (sCanDeleteAllocator && sAllocatorUsers == 0) { |
270 | 0 | delete sAllocator; |
271 | 0 | sAllocator = nullptr; |
272 | 0 | } |
273 | 0 | } |
274 | | |
275 | | #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY |
276 | | NS_IMETHODIMP |
277 | | nsTimerEvent::GetName(nsACString& aName) |
278 | 0 | { |
279 | 0 | bool current; |
280 | 0 | MOZ_RELEASE_ASSERT(NS_SUCCEEDED(mTimer->mEventTarget->IsOnCurrentThread(¤t)) && current); |
281 | 0 |
|
282 | 0 | mTimer->GetName(aName); |
283 | 0 | return NS_OK; |
284 | 0 | } |
285 | | #endif |
286 | | |
287 | | NS_IMETHODIMP |
288 | | nsTimerEvent::Run() |
289 | 0 | { |
290 | 0 | if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { |
291 | 0 | TimeStamp now = TimeStamp::Now(); |
292 | 0 | MOZ_LOG(GetTimerLog(), LogLevel::Debug, |
293 | 0 | ("[this=%p] time between PostTimerEvent() and Fire(): %fms\n", |
294 | 0 | this, (now - mInitTime).ToMilliseconds())); |
295 | 0 | } |
296 | 0 |
|
297 | 0 | mTimer->Fire(mGeneration); |
298 | 0 |
|
299 | 0 | return NS_OK; |
300 | 0 | } |
301 | | |
302 | | nsresult |
303 | | TimerThread::Init() |
304 | 90 | { |
305 | 90 | mMonitor.AssertCurrentThreadOwns(); |
306 | 90 | MOZ_LOG(GetTimerLog(), LogLevel::Debug, |
307 | 90 | ("TimerThread::Init [%d]\n", mInitialized)); |
308 | 90 | |
309 | 90 | if (!mInitialized) { |
310 | 1 | nsTimerEvent::Init(); |
311 | 1 | |
312 | 1 | // We hold on to mThread to keep the thread alive. |
313 | 1 | nsresult rv = |
314 | 1 | NS_NewNamedThread("Timer Thread", getter_AddRefs(mThread), this); |
315 | 1 | if (NS_FAILED(rv)) { |
316 | 0 | mThread = nullptr; |
317 | 1 | } else { |
318 | 1 | RefPtr<TimerObserverRunnable> r = new TimerObserverRunnable(this); |
319 | 1 | if (NS_IsMainThread()) { |
320 | 1 | r->Run(); |
321 | 1 | } else { |
322 | 0 | NS_DispatchToMainThread(r); |
323 | 0 | } |
324 | 1 | } |
325 | 1 | |
326 | 1 | mInitialized = true; |
327 | 1 | } |
328 | 90 | |
329 | 90 | if (!mThread) { |
330 | 0 | return NS_ERROR_FAILURE; |
331 | 0 | } |
332 | 90 | |
333 | 90 | return NS_OK; |
334 | 90 | } |
335 | | |
336 | | nsresult |
337 | | TimerThread::Shutdown() |
338 | 0 | { |
339 | 0 | MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("TimerThread::Shutdown begin\n")); |
340 | 0 |
|
341 | 0 | if (!mThread) { |
342 | 0 | return NS_ERROR_NOT_INITIALIZED; |
343 | 0 | } |
344 | 0 | |
345 | 0 | nsTArray<RefPtr<nsTimerImpl>> timers; |
346 | 0 | { |
347 | 0 | // lock scope |
348 | 0 | MonitorAutoLock lock(mMonitor); |
349 | 0 |
|
350 | 0 | mShutdown = true; |
351 | 0 |
|
352 | 0 | // notify the cond var so that Run() can return |
353 | 0 | if (mWaiting) { |
354 | 0 | mNotified = true; |
355 | 0 | mMonitor.Notify(); |
356 | 0 | } |
357 | 0 |
|
358 | 0 | // Need to copy content of mTimers array to a local array |
359 | 0 | // because call to timers' Cancel() (and release its self) |
360 | 0 | // must not be done under the lock. Destructor of a callback |
361 | 0 | // might potentially call some code reentering the same lock |
362 | 0 | // that leads to unexpected behavior or deadlock. |
363 | 0 | // See bug 422472. |
364 | 0 | for (const UniquePtr<Entry>& entry : mTimers) { |
365 | 0 | timers.AppendElement(entry->Take()); |
366 | 0 | } |
367 | 0 |
|
368 | 0 | mTimers.Clear(); |
369 | 0 | } |
370 | 0 |
|
371 | 0 | for (const RefPtr<nsTimerImpl>& timer : timers) { |
372 | 0 | if (timer) { |
373 | 0 | timer->Cancel(); |
374 | 0 | } |
375 | 0 | } |
376 | 0 |
|
377 | 0 | mThread->Shutdown(); // wait for the thread to die |
378 | 0 |
|
379 | 0 | nsTimerEvent::Shutdown(); |
380 | 0 |
|
381 | 0 | MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("TimerThread::Shutdown end\n")); |
382 | 0 | return NS_OK; |
383 | 0 | } |
384 | | |
385 | | namespace { |
386 | | |
387 | | struct MicrosecondsToInterval |
388 | | { |
389 | 9 | PRIntervalTime operator[](size_t aMs) const { |
390 | 9 | return PR_MicrosecondsToInterval(aMs); |
391 | 9 | } |
392 | | }; |
393 | | |
394 | | struct IntervalComparator |
395 | | { |
396 | 9 | int operator()(PRIntervalTime aInterval) const { |
397 | 9 | return (0 < aInterval) ? -1 : 1; |
398 | 9 | } |
399 | | }; |
400 | | |
401 | | } // namespace |
402 | | |
403 | | NS_IMETHODIMP |
404 | | TimerThread::Run() |
405 | 1 | { |
406 | 1 | NS_SetCurrentThreadName("Timer"); |
407 | 1 | |
408 | 1 | MonitorAutoLock lock(mMonitor); |
409 | 1 | |
410 | 1 | // We need to know how many microseconds give a positive PRIntervalTime. This |
411 | 1 | // is platform-dependent and we calculate it at runtime, finding a value |v| |
412 | 1 | // such that |PR_MicrosecondsToInterval(v) > 0| and then binary-searching in |
413 | 1 | // the range [0, v) to find the ms-to-interval scale. |
414 | 1 | uint32_t usForPosInterval = 1; |
415 | 10 | while (PR_MicrosecondsToInterval(usForPosInterval) == 0) { |
416 | 9 | usForPosInterval <<= 1; |
417 | 9 | } |
418 | 1 | |
419 | 1 | size_t usIntervalResolution; |
420 | 1 | BinarySearchIf(MicrosecondsToInterval(), 0, usForPosInterval, IntervalComparator(), &usIntervalResolution); |
421 | 1 | MOZ_ASSERT(PR_MicrosecondsToInterval(usIntervalResolution - 1) == 0); |
422 | 1 | MOZ_ASSERT(PR_MicrosecondsToInterval(usIntervalResolution) == 1); |
423 | 1 | |
424 | 1 | // Half of the amount of microseconds needed to get positive PRIntervalTime. |
425 | 1 | // We use this to decide how to round our wait times later |
426 | 1 | mAllowedEarlyFiringMicroseconds = usIntervalResolution / 2; |
427 | 1 | bool forceRunNextTimer = false; |
428 | 1 | |
429 | 175 | while (!mShutdown) { |
430 | 174 | // Have to use PRIntervalTime here, since PR_WaitCondVar takes it |
431 | 174 | TimeDuration waitFor; |
432 | 174 | bool forceRunThisTimer = forceRunNextTimer; |
433 | 174 | forceRunNextTimer = false; |
434 | 174 | |
435 | 174 | if (mSleeping) { |
436 | 0 | // Sleep for 0.1 seconds while not firing timers. |
437 | 0 | uint32_t milliseconds = 100; |
438 | 0 | if (ChaosMode::isActive(ChaosFeature::TimerScheduling)) { |
439 | 0 | milliseconds = ChaosMode::randomUint32LessThan(200); |
440 | 0 | } |
441 | 0 | waitFor = TimeDuration::FromMilliseconds(milliseconds); |
442 | 174 | } else { |
443 | 174 | waitFor = TimeDuration::Forever(); |
444 | 174 | TimeStamp now = TimeStamp::Now(); |
445 | 174 | |
446 | 174 | RemoveLeadingCanceledTimersInternal(); |
447 | 174 | |
448 | 174 | if (!mTimers.IsEmpty()) { |
449 | 174 | if (now >= mTimers[0]->Value()->mTimeout || forceRunThisTimer) { |
450 | 86 | next: |
451 | 86 | // NB: AddRef before the Release under RemoveTimerInternal to avoid |
452 | 86 | // mRefCnt passing through zero, in case all other refs than the one |
453 | 86 | // from mTimers have gone away (the last non-mTimers[i]-ref's Release |
454 | 86 | // must be racing with us, blocked in gThread->RemoveTimer waiting |
455 | 86 | // for TimerThread::mMonitor, under nsTimerImpl::Release. |
456 | 86 | |
457 | 86 | RefPtr<nsTimerImpl> timerRef(mTimers[0]->Take()); |
458 | 86 | RemoveFirstTimerInternal(); |
459 | 86 | |
460 | 86 | MOZ_LOG(GetTimerLog(), LogLevel::Debug, |
461 | 86 | ("Timer thread woke up %fms from when it was supposed to\n", |
462 | 86 | fabs((now - timerRef->mTimeout).ToMilliseconds()))); |
463 | 86 | |
464 | 86 | // We are going to let the call to PostTimerEvent here handle the |
465 | 86 | // release of the timer so that we don't end up releasing the timer |
466 | 86 | // on the TimerThread instead of on the thread it targets. |
467 | 86 | timerRef = PostTimerEvent(timerRef.forget()); |
468 | 86 | |
469 | 86 | if (timerRef) { |
470 | 0 | // We got our reference back due to an error. |
471 | 0 | // Unhook the nsRefPtr, and release manually so we can get the |
472 | 0 | // refcount. |
473 | 0 | nsrefcnt rc = timerRef.forget().take()->Release(); |
474 | 0 | (void)rc; |
475 | 0 |
|
476 | 0 | // The nsITimer interface requires that its users keep a reference |
477 | 0 | // to the timers they use while those timers are initialized but |
478 | 0 | // have not yet fired. If this ever happens, it is a bug in the |
479 | 0 | // code that created and used the timer. |
480 | 0 | // |
481 | 0 | // Further, note that this should never happen even with a |
482 | 0 | // misbehaving user, because nsTimerImpl::Release checks for a |
483 | 0 | // refcount of 1 with an armed timer (a timer whose only reference |
484 | 0 | // is from the timer thread) and when it hits this will remove the |
485 | 0 | // timer from the timer thread and thus destroy the last reference, |
486 | 0 | // preventing this situation from occurring. |
487 | 0 | MOZ_ASSERT(rc != 0, "destroyed timer off its target thread!"); |
488 | 0 | } |
489 | 86 | |
490 | 86 | if (mShutdown) { |
491 | 0 | break; |
492 | 0 | } |
493 | 86 | |
494 | 86 | // Update now, as PostTimerEvent plus the locking may have taken a |
495 | 86 | // tick or two, and we may goto next below. |
496 | 86 | now = TimeStamp::Now(); |
497 | 86 | } |
498 | 174 | } |
499 | 174 | |
500 | 174 | RemoveLeadingCanceledTimersInternal(); |
501 | 174 | |
502 | 174 | if (!mTimers.IsEmpty()) { |
503 | 95 | TimeStamp timeout = mTimers[0]->Value()->mTimeout; |
504 | 95 | |
505 | 95 | // Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer |
506 | 95 | // is due now or overdue. |
507 | 95 | // |
508 | 95 | // Note that we can only sleep for integer values of a certain |
509 | 95 | // resolution. We use mAllowedEarlyFiringMicroseconds, calculated |
510 | 95 | // before, to do the optimal rounding (i.e., of how to decide what |
511 | 95 | // interval is so small we should not wait at all). |
512 | 95 | double microseconds = (timeout - now).ToMilliseconds() * 1000; |
513 | 95 | |
514 | 95 | if (ChaosMode::isActive(ChaosFeature::TimerScheduling)) { |
515 | 0 | // The mean value of sFractions must be 1 to ensure that |
516 | 0 | // the average of a long sequence of timeouts converges to the |
517 | 0 | // actual sum of their times. |
518 | 0 | static const float sFractions[] = { |
519 | 0 | 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 1.75f, 2.75f |
520 | 0 | }; |
521 | 0 | microseconds *= |
522 | 0 | sFractions[ChaosMode::randomUint32LessThan(ArrayLength(sFractions))]; |
523 | 0 | forceRunNextTimer = true; |
524 | 0 | } |
525 | 95 | |
526 | 95 | if (microseconds < mAllowedEarlyFiringMicroseconds) { |
527 | 0 | forceRunNextTimer = false; |
528 | 0 | goto next; // round down; execute event now |
529 | 0 | } |
530 | 95 | waitFor = TimeDuration::FromMicroseconds(microseconds); |
531 | 95 | if (waitFor.IsZero()) { |
532 | 0 | // round up, wait the minimum time we can wait |
533 | 0 | waitFor = TimeDuration::FromMicroseconds(1); |
534 | 0 | } |
535 | 95 | } |
536 | 174 | |
537 | 174 | if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { |
538 | 0 | if (waitFor == TimeDuration::Forever()) |
539 | 0 | MOZ_LOG(GetTimerLog(), LogLevel::Debug, |
540 | 0 | ("waiting forever\n")); |
541 | 0 | else |
542 | 0 | MOZ_LOG(GetTimerLog(), LogLevel::Debug, |
543 | 0 | ("waiting for %f\n", waitFor.ToMilliseconds())); |
544 | 0 | } |
545 | 174 | } |
546 | 174 | |
547 | 174 | mWaiting = true; |
548 | 174 | mNotified = false; |
549 | 174 | mMonitor.Wait(waitFor); |
550 | 174 | if (mNotified) { |
551 | 87 | forceRunNextTimer = false; |
552 | 87 | } |
553 | 174 | mWaiting = false; |
554 | 174 | } |
555 | 1 | |
556 | 1 | return NS_OK; |
557 | 1 | } |
558 | | |
559 | | nsresult |
560 | | TimerThread::AddTimer(nsTimerImpl* aTimer) |
561 | 90 | { |
562 | 90 | MonitorAutoLock lock(mMonitor); |
563 | 90 | |
564 | 90 | if (!aTimer->mEventTarget) { |
565 | 0 | return NS_ERROR_NOT_INITIALIZED; |
566 | 0 | } |
567 | 90 | |
568 | 90 | nsresult rv = Init(); |
569 | 90 | if (NS_FAILED(rv)) { |
570 | 0 | return rv; |
571 | 0 | } |
572 | 90 | |
573 | 90 | // Add the timer to our list. |
574 | 90 | if(!AddTimerInternal(aTimer)) { |
575 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
576 | 0 | } |
577 | 90 | |
578 | 90 | // Awaken the timer thread. |
579 | 90 | if (mWaiting && mTimers[0]->Value() == aTimer) { |
580 | 83 | mNotified = true; |
581 | 83 | mMonitor.Notify(); |
582 | 83 | } |
583 | 90 | |
584 | 90 | return NS_OK; |
585 | 90 | } |
586 | | |
587 | | nsresult |
588 | | TimerThread::RemoveTimer(nsTimerImpl* aTimer) |
589 | 258 | { |
590 | 258 | MonitorAutoLock lock(mMonitor); |
591 | 258 | |
592 | 258 | // Remove the timer from our array. Tell callers that aTimer was not found |
593 | 258 | // by returning NS_ERROR_NOT_AVAILABLE. |
594 | 258 | |
595 | 258 | if (!RemoveTimerInternal(aTimer)) { |
596 | 254 | return NS_ERROR_NOT_AVAILABLE; |
597 | 254 | } |
598 | 4 | |
599 | 4 | // Awaken the timer thread. |
600 | 4 | if (mWaiting) { |
601 | 4 | mNotified = true; |
602 | 4 | mMonitor.Notify(); |
603 | 4 | } |
604 | 4 | |
605 | 4 | return NS_OK; |
606 | 4 | } |
607 | | |
608 | | TimeStamp |
609 | | TimerThread::FindNextFireTimeForCurrentThread(TimeStamp aDefault, uint32_t aSearchBound) |
610 | 0 | { |
611 | 0 | MonitorAutoLock lock(mMonitor); |
612 | 0 | TimeStamp timeStamp = aDefault; |
613 | 0 | uint32_t index = 0; |
614 | 0 |
|
615 | | #ifdef DEBUG |
616 | | TimeStamp firstTimeStamp; |
617 | | Entry* initialFirstEntry = nullptr; |
618 | | if (!mTimers.IsEmpty()) { |
619 | | initialFirstEntry = mTimers[0].get(); |
620 | | firstTimeStamp = mTimers[0]->Timeout(); |
621 | | } |
622 | | #endif |
623 | |
|
624 | 0 | auto end = mTimers.end(); |
625 | 0 | while(end != mTimers.begin()) { |
626 | 0 | nsTimerImpl* timer = mTimers[0]->Value(); |
627 | 0 | if (timer) { |
628 | 0 | if (timer->mTimeout > aDefault) { |
629 | 0 | timeStamp = aDefault; |
630 | 0 | break; |
631 | 0 | } |
632 | 0 | |
633 | 0 | // Don't yield to timers created with the *_LOW_PRIORITY type. |
634 | 0 | if (!timer->IsLowPriority()) { |
635 | 0 | bool isOnCurrentThread = false; |
636 | 0 | nsresult rv = timer->mEventTarget->IsOnCurrentThread(&isOnCurrentThread); |
637 | 0 | if (NS_SUCCEEDED(rv) && isOnCurrentThread) { |
638 | 0 | timeStamp = timer->mTimeout; |
639 | 0 | break; |
640 | 0 | } |
641 | 0 | } |
642 | 0 | |
643 | 0 | if (++index > aSearchBound) { |
644 | 0 | // Track the currently highest timeout so that we can bail out when we |
645 | 0 | // reach the bound or when we find a timer for the current thread. |
646 | 0 | // This won't give accurate information if we stop before finding |
647 | 0 | // any timer for the current thread, but at least won't report too |
648 | 0 | // long idle period. |
649 | 0 | timeStamp = timer->mTimeout; |
650 | 0 | break; |
651 | 0 | } |
652 | 0 | } |
653 | 0 | |
654 | 0 | std::pop_heap(mTimers.begin(), end, Entry::UniquePtrLessThan); |
655 | 0 | --end; |
656 | 0 | } |
657 | 0 |
|
658 | 0 | while (end != mTimers.end()) { |
659 | 0 | ++end; |
660 | 0 | std::push_heap(mTimers.begin(), end, Entry::UniquePtrLessThan); |
661 | 0 | } |
662 | 0 |
|
663 | | #ifdef DEBUG |
664 | | if (!mTimers.IsEmpty()) { |
665 | | if (firstTimeStamp != mTimers[0]->Timeout()) { |
666 | | TimeStamp now = TimeStamp::Now(); |
667 | | printf_stderr("firstTimeStamp %f, mTimers[0]->Timeout() %f, " |
668 | | "initialFirstTimer %p, current first %p\n", |
669 | | (firstTimeStamp - now).ToMilliseconds(), |
670 | | (mTimers[0]->Timeout() - now).ToMilliseconds(), |
671 | | initialFirstEntry, mTimers[0].get()); |
672 | | } |
673 | | } |
674 | | MOZ_ASSERT_IF(!mTimers.IsEmpty(), firstTimeStamp == mTimers[0]->Timeout()); |
675 | | #endif |
676 | |
|
677 | 0 | return timeStamp; |
678 | 0 | } |
679 | | |
680 | | // This function must be called from within a lock |
681 | | bool |
682 | | TimerThread::AddTimerInternal(nsTimerImpl* aTimer) |
683 | 90 | { |
684 | 90 | mMonitor.AssertCurrentThreadOwns(); |
685 | 90 | if (mShutdown) { |
686 | 0 | return false; |
687 | 0 | } |
688 | 90 | |
689 | 90 | TimeStamp now = TimeStamp::Now(); |
690 | 90 | |
691 | 90 | UniquePtr<Entry>* entry = mTimers.AppendElement( |
692 | 90 | MakeUnique<Entry>(now, aTimer->mTimeout, aTimer), mozilla::fallible); |
693 | 90 | if (!entry) { |
694 | 0 | return false; |
695 | 0 | } |
696 | 90 | |
697 | 90 | std::push_heap(mTimers.begin(), mTimers.end(), Entry::UniquePtrLessThan); |
698 | 90 | |
699 | | #ifdef MOZ_TASK_TRACER |
700 | | // Caller of AddTimer is the parent task of its timer event, so we store the |
701 | | // TraceInfo here for later used. |
702 | | aTimer->GetTLSTraceInfo(); |
703 | | #endif |
704 | | |
705 | 90 | return true; |
706 | 90 | } |
707 | | |
708 | | bool |
709 | | TimerThread::RemoveTimerInternal(nsTimerImpl* aTimer) |
710 | 258 | { |
711 | 258 | mMonitor.AssertCurrentThreadOwns(); |
712 | 258 | if (!aTimer || !aTimer->mHolder) { |
713 | 254 | return false; |
714 | 254 | } |
715 | 4 | aTimer->mHolder->Forget(aTimer); |
716 | 4 | return true; |
717 | 4 | } |
718 | | |
719 | | void |
720 | | TimerThread::RemoveLeadingCanceledTimersInternal() |
721 | 348 | { |
722 | 348 | mMonitor.AssertCurrentThreadOwns(); |
723 | 348 | |
724 | 348 | // Move all canceled timers from the front of the list to |
725 | 348 | // the back of the list using std::pop_heap(). We do this |
726 | 348 | // without actually removing them from the list so we can |
727 | 348 | // modify the nsTArray in a single bulk operation. |
728 | 348 | auto sortedEnd = mTimers.end(); |
729 | 352 | while (sortedEnd != mTimers.begin() && !mTimers[0]->Value()) { |
730 | 4 | std::pop_heap(mTimers.begin(), sortedEnd, Entry::UniquePtrLessThan); |
731 | 4 | --sortedEnd; |
732 | 4 | } |
733 | 348 | |
734 | 348 | // If there were no canceled timers then we are done. |
735 | 348 | if (sortedEnd == mTimers.end()) { |
736 | 344 | return; |
737 | 344 | } |
738 | 4 | |
739 | 4 | // Finally, remove the canceled timers from the back of the |
740 | 4 | // nsTArray. Note, since std::pop_heap() uses iterators |
741 | 4 | // we must convert to nsTArray indices and number of |
742 | 4 | // elements here. |
743 | 4 | mTimers.RemoveElementsAt(sortedEnd - mTimers.begin(), |
744 | 4 | mTimers.end() - sortedEnd); |
745 | 4 | } |
746 | | |
747 | | void |
748 | | TimerThread::RemoveFirstTimerInternal() |
749 | 86 | { |
750 | 86 | mMonitor.AssertCurrentThreadOwns(); |
751 | 86 | MOZ_ASSERT(!mTimers.IsEmpty()); |
752 | 86 | std::pop_heap(mTimers.begin(), mTimers.end(), Entry::UniquePtrLessThan); |
753 | 86 | mTimers.RemoveLastElement(); |
754 | 86 | } |
755 | | |
756 | | already_AddRefed<nsTimerImpl> |
757 | | TimerThread::PostTimerEvent(already_AddRefed<nsTimerImpl> aTimerRef) |
758 | 86 | { |
759 | 86 | mMonitor.AssertCurrentThreadOwns(); |
760 | 86 | |
761 | 86 | RefPtr<nsTimerImpl> timer(aTimerRef); |
762 | 86 | if (!timer->mEventTarget) { |
763 | 0 | NS_ERROR("Attempt to post timer event to NULL event target"); |
764 | 0 | return timer.forget(); |
765 | 0 | } |
766 | 86 | |
767 | 86 | // XXX we may want to reuse this nsTimerEvent in the case of repeating timers. |
768 | 86 | |
769 | 86 | // Since we already addref'd 'timer', we don't need to addref here. |
770 | 86 | // We will release either in ~nsTimerEvent(), or pass the reference back to |
771 | 86 | // the caller. We need to copy the generation number from this timer into the |
772 | 86 | // event, so we can avoid firing a timer that was re-initialized after being |
773 | 86 | // canceled. |
774 | 86 | |
775 | 86 | RefPtr<nsTimerEvent> event = new nsTimerEvent; |
776 | 86 | if (!event) { |
777 | 0 | return timer.forget(); |
778 | 0 | } |
779 | 86 | |
780 | 86 | if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { |
781 | 0 | event->mInitTime = TimeStamp::Now(); |
782 | 0 | } |
783 | 86 | |
784 | | #ifdef MOZ_TASK_TRACER |
785 | | // During the dispatch of TimerEvent, we overwrite the current TraceInfo |
786 | | // partially with the info saved in timer earlier, and restore it back by |
787 | | // AutoSaveCurTraceInfo. |
788 | | AutoSaveCurTraceInfo saveCurTraceInfo; |
789 | | (timer->GetTracedTask()).SetTLSTraceInfo(); |
790 | | #endif |
791 | | |
792 | 86 | nsCOMPtr<nsIEventTarget> target = timer->mEventTarget; |
793 | 86 | event->SetTimer(timer.forget()); |
794 | 86 | |
795 | 86 | nsresult rv; |
796 | 86 | { |
797 | 86 | // We release mMonitor around the Dispatch because if this timer is targeted |
798 | 86 | // at the TimerThread we'll deadlock. |
799 | 86 | MonitorAutoUnlock unlock(mMonitor); |
800 | 86 | rv = target->Dispatch(event, NS_DISPATCH_NORMAL); |
801 | 86 | } |
802 | 86 | |
803 | 86 | if (NS_FAILED(rv)) { |
804 | 0 | timer = event->ForgetTimer(); |
805 | 0 | RemoveTimerInternal(timer); |
806 | 0 | return timer.forget(); |
807 | 0 | } |
808 | 86 | |
809 | 86 | return nullptr; |
810 | 86 | } |
811 | | |
812 | | void |
813 | | TimerThread::DoBeforeSleep() |
814 | 0 | { |
815 | 0 | // Mainthread |
816 | 0 | MonitorAutoLock lock(mMonitor); |
817 | 0 | mSleeping = true; |
818 | 0 | } |
819 | | |
820 | | // Note: wake may be notified without preceding sleep notification |
821 | | void |
822 | | TimerThread::DoAfterSleep() |
823 | 0 | { |
824 | 0 | // Mainthread |
825 | 0 | MonitorAutoLock lock(mMonitor); |
826 | 0 | mSleeping = false; |
827 | 0 |
|
828 | 0 | // Wake up the timer thread to re-process the array to ensure the sleep delay is correct, |
829 | 0 | // and fire any expired timers (perhaps quite a few) |
830 | 0 | mNotified = true; |
831 | 0 | mMonitor.Notify(); |
832 | 0 | } |
833 | | |
834 | | |
835 | | NS_IMETHODIMP |
836 | | TimerThread::Observe(nsISupports* /* aSubject */, const char* aTopic, |
837 | | const char16_t* /* aData */) |
838 | 0 | { |
839 | 0 | if (strcmp(aTopic, "sleep_notification") == 0 || |
840 | 0 | strcmp(aTopic, "suspend_process_notification") == 0) { |
841 | 0 | DoBeforeSleep(); |
842 | 0 | } else if (strcmp(aTopic, "wake_notification") == 0 || |
843 | 0 | strcmp(aTopic, "resume_process_notification") == 0) { |
844 | 0 | DoAfterSleep(); |
845 | 0 | } |
846 | 0 |
|
847 | 0 | return NS_OK; |
848 | 0 | } |
849 | | |
850 | | uint32_t |
851 | | TimerThread::AllowedEarlyFiringMicroseconds() const |
852 | 0 | { |
853 | 0 | return mAllowedEarlyFiringMicroseconds; |
854 | 0 | } |