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 "mozilla/ArrayUtils.h"
8 #include "mozilla/BackgroundHangMonitor.h"
9 #include "mozilla/LinkedList.h"
10 #include "mozilla/Monitor.h"
11 #include "mozilla/Move.h"
12 #include "mozilla/Preferences.h"
13 #include "mozilla/StaticPtr.h"
14 #include "mozilla/Telemetry.h"
15 #include "mozilla/ThreadHangStats.h"
16 #include "mozilla/ThreadLocal.h"
17
18 #include "prinrval.h"
19 #include "prthread.h"
20 #include "ThreadStackHelper.h"
21 #include "nsIObserverService.h"
22 #include "nsIObserver.h"
23 #include "mozilla/Services.h"
24 #include "nsXULAppAPI.h"
25
26 #include <algorithm>
27
28 // Activate BHR only for one every BHR_BETA_MOD users.
29 // This is now 100% of Beta population for the Beta 45/46 e10s A/B trials
30 // It can be scaled back again in the future
31 #define BHR_BETA_MOD 1;
32
33 // Maximum depth of the call stack in the reported thread hangs. This value represents
34 // the 99.9th percentile of the thread hangs stack depths reported by Telemetry.
35 static const size_t kMaxThreadHangStackDepth = 30;
36
37 // An utility comparator function used by std::unique to collapse "(* script)" entries in
38 // a vector representing a call stack.
StackScriptEntriesCollapser(const char * aStackEntry,const char * aAnotherStackEntry)39 bool StackScriptEntriesCollapser(const char* aStackEntry, const char *aAnotherStackEntry)
40 {
41 return !strcmp(aStackEntry, aAnotherStackEntry) &&
42 (!strcmp(aStackEntry, "(chrome script)") || !strcmp(aStackEntry, "(content script)"));
43 }
44
45 namespace mozilla {
46
47 /**
48 * BackgroundHangManager is the global object that
49 * manages all instances of BackgroundHangThread.
50 */
51 class BackgroundHangManager : public nsIObserver
52 {
53 private:
54 // Background hang monitor thread function
MonitorThread(void * aData)55 static void MonitorThread(void* aData)
56 {
57 PR_SetCurrentThreadName("BgHangManager");
58
59 /* We do not hold a reference to BackgroundHangManager here
60 because the monitor thread only exists as long as the
61 BackgroundHangManager instance exists. We stop the monitor
62 thread in the BackgroundHangManager destructor, and we can
63 only get to the destructor if we don't hold a reference here. */
64 static_cast<BackgroundHangManager*>(aData)->RunMonitorThread();
65 }
66
67 // Hang monitor thread
68 PRThread* mHangMonitorThread;
69 // Stop hang monitoring
70 bool mShutdown;
71
72 BackgroundHangManager(const BackgroundHangManager&);
73 BackgroundHangManager& operator=(const BackgroundHangManager&);
74 void RunMonitorThread();
75
76 public:
77 NS_DECL_THREADSAFE_ISUPPORTS
78 NS_DECL_NSIOBSERVER
79 static StaticRefPtr<BackgroundHangManager> sInstance;
80 static bool sDisabled;
81
82 // Lock for access to members of this class
83 Monitor mLock;
84 // Current time as seen by hang monitors
85 PRIntervalTime mIntervalNow;
86 // List of BackgroundHangThread instances associated with each thread
87 LinkedList<BackgroundHangThread> mHangThreads;
88
Shutdown()89 void Shutdown()
90 {
91 MonitorAutoLock autoLock(mLock);
92 mShutdown = true;
93 autoLock.Notify();
94 }
95
Wakeup()96 void Wakeup()
97 {
98 // PR_CreateThread could have failed earlier
99 if (mHangMonitorThread) {
100 // Use PR_Interrupt to avoid potentially taking a lock
101 PR_Interrupt(mHangMonitorThread);
102 }
103 }
104
105 BackgroundHangManager();
106 private:
107 virtual ~BackgroundHangManager();
108 };
109
NS_IMPL_ISUPPORTS(BackgroundHangManager,nsIObserver)110 NS_IMPL_ISUPPORTS(BackgroundHangManager, nsIObserver)
111
112 NS_IMETHODIMP
113 BackgroundHangManager::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) {
114 NS_ENSURE_TRUE(!strcmp(aTopic, "profile-after-change"), NS_ERROR_UNEXPECTED);
115 BackgroundHangMonitor::DisableOnBeta();
116
117 nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
118 MOZ_ASSERT(observerService);
119 observerService->RemoveObserver(this, "profile-after-change");
120
121 return NS_OK;
122 }
123
124 /**
125 * BackgroundHangThread is a per-thread object that is used
126 * by all instances of BackgroundHangMonitor to monitor hangs.
127 */
128 class BackgroundHangThread : public LinkedListElement<BackgroundHangThread>
129 {
130 private:
131 static MOZ_THREAD_LOCAL(BackgroundHangThread*) sTlsKey;
132 static bool sTlsKeyInitialized;
133
134 BackgroundHangThread(const BackgroundHangThread&);
135 BackgroundHangThread& operator=(const BackgroundHangThread&);
136 ~BackgroundHangThread();
137
138 /* Keep a reference to the manager, so we can keep going even
139 after BackgroundHangManager::Shutdown is called. */
140 const RefPtr<BackgroundHangManager> mManager;
141 // Unique thread ID for identification
142 const PRThread* mThreadID;
143
144 void Update();
145
146 public:
147 NS_INLINE_DECL_REFCOUNTING(BackgroundHangThread)
148 /**
149 * Returns the BackgroundHangThread associated with the
150 * running thread. Note that this will not find private
151 * BackgroundHangThread threads.
152 *
153 * @return BackgroundHangThread*, or nullptr if no thread
154 * is found.
155 */
156 static BackgroundHangThread* FindThread();
157
Startup()158 static void Startup()
159 {
160 /* We can tolerate init() failing. */
161 sTlsKeyInitialized = sTlsKey.init();
162 }
163
164 // Hang timeout in ticks
165 const PRIntervalTime mTimeout;
166 // PermaHang timeout in ticks
167 const PRIntervalTime mMaxTimeout;
168 // Time at last activity
169 PRIntervalTime mInterval;
170 // Time when a hang started
171 PRIntervalTime mHangStart;
172 // Is the thread in a hang
173 bool mHanging;
174 // Is the thread in a waiting state
175 bool mWaiting;
176 // Is the thread dedicated to a single BackgroundHangMonitor
177 BackgroundHangMonitor::ThreadType mThreadType;
178 // Platform-specific helper to get hang stacks
179 ThreadStackHelper mStackHelper;
180 // Stack of current hang
181 Telemetry::HangStack mHangStack;
182 // Statistics for telemetry
183 Telemetry::ThreadHangStats mStats;
184 // Annotations for the current hang
185 UniquePtr<HangMonitor::HangAnnotations> mAnnotations;
186 // Annotators registered for this thread
187 HangMonitor::Observer::Annotators mAnnotators;
188
189 BackgroundHangThread(const char* aName,
190 uint32_t aTimeoutMs,
191 uint32_t aMaxTimeoutMs,
192 BackgroundHangMonitor::ThreadType aThreadType = BackgroundHangMonitor::THREAD_SHARED);
193
194 // Report a hang; aManager->mLock IS locked
195 Telemetry::HangHistogram& ReportHang(PRIntervalTime aHangTime);
196 // Report a permanent hang; aManager->mLock IS locked
197 void ReportPermaHang();
198 // Called by BackgroundHangMonitor::NotifyActivity
NotifyActivity()199 void NotifyActivity()
200 {
201 MonitorAutoLock autoLock(mManager->mLock);
202 Update();
203 }
204 // Called by BackgroundHangMonitor::NotifyWait
NotifyWait()205 void NotifyWait()
206 {
207 MonitorAutoLock autoLock(mManager->mLock);
208
209 if (mWaiting) {
210 return;
211 }
212
213 Update();
214 mWaiting = true;
215 }
216
217 // Returns true if this thread is (or might be) shared between other
218 // BackgroundHangMonitors for the monitored thread.
IsShared()219 bool IsShared() {
220 return mThreadType == BackgroundHangMonitor::THREAD_SHARED;
221 }
222 };
223
224
225 StaticRefPtr<BackgroundHangManager> BackgroundHangManager::sInstance;
226 bool BackgroundHangManager::sDisabled = false;
227
228 MOZ_THREAD_LOCAL(BackgroundHangThread*) BackgroundHangThread::sTlsKey;
229 bool BackgroundHangThread::sTlsKeyInitialized;
230
BackgroundHangManager()231 BackgroundHangManager::BackgroundHangManager()
232 : mShutdown(false)
233 , mLock("BackgroundHangManager")
234 , mIntervalNow(0)
235 {
236 // Lock so we don't race against the new monitor thread
237 MonitorAutoLock autoLock(mLock);
238 mHangMonitorThread = PR_CreateThread(
239 PR_USER_THREAD, MonitorThread, this,
240 PR_PRIORITY_LOW, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
241
242 MOZ_ASSERT(mHangMonitorThread, "Failed to create monitor thread");
243 }
244
~BackgroundHangManager()245 BackgroundHangManager::~BackgroundHangManager()
246 {
247 MOZ_ASSERT(mShutdown, "Destruction without Shutdown call");
248 MOZ_ASSERT(mHangThreads.isEmpty(), "Destruction with outstanding monitors");
249 MOZ_ASSERT(mHangMonitorThread, "No monitor thread");
250
251 // PR_CreateThread could have failed above due to resource limitation
252 if (mHangMonitorThread) {
253 // The monitor thread can only live as long as the instance lives
254 PR_JoinThread(mHangMonitorThread);
255 }
256 }
257
258 void
RunMonitorThread()259 BackgroundHangManager::RunMonitorThread()
260 {
261 // Keep us locked except when waiting
262 MonitorAutoLock autoLock(mLock);
263
264 /* mIntervalNow is updated at various intervals determined by waitTime.
265 However, if an update latency is too long (due to CPU scheduling, system
266 sleep, etc.), we don't update mIntervalNow at all. This is done so that
267 long latencies in our timing are not detected as hangs. systemTime is
268 used to track PR_IntervalNow() and determine our latency. */
269
270 PRIntervalTime systemTime = PR_IntervalNow();
271 // Default values for the first iteration of thread loop
272 PRIntervalTime waitTime = PR_INTERVAL_NO_WAIT;
273 PRIntervalTime recheckTimeout = PR_INTERVAL_NO_WAIT;
274
275 while (!mShutdown) {
276
277 PR_ClearInterrupt();
278 nsresult rv = autoLock.Wait(waitTime);
279
280 PRIntervalTime newTime = PR_IntervalNow();
281 PRIntervalTime systemInterval = newTime - systemTime;
282 systemTime = newTime;
283
284 /* waitTime is a quarter of the shortest timeout value; If our timing
285 latency is low enough (less than half the shortest timeout value),
286 we can update mIntervalNow. */
287 if (MOZ_LIKELY(waitTime != PR_INTERVAL_NO_TIMEOUT &&
288 systemInterval < 2 * waitTime)) {
289 mIntervalNow += systemInterval;
290 }
291
292 /* If it's before the next recheck timeout, and our wait did not
293 get interrupted (either through Notify or PR_Interrupt), we can
294 keep the current waitTime and skip iterating through hang monitors. */
295 if (MOZ_LIKELY(systemInterval < recheckTimeout &&
296 systemInterval >= waitTime &&
297 rv == NS_OK)) {
298 recheckTimeout -= systemInterval;
299 continue;
300 }
301
302 /* We are in one of the following scenarios,
303 - Hang or permahang recheck timeout
304 - Thread added/removed
305 - Thread wait or hang ended
306 In all cases, we want to go through our list of hang
307 monitors and update waitTime and recheckTimeout. */
308 waitTime = PR_INTERVAL_NO_TIMEOUT;
309 recheckTimeout = PR_INTERVAL_NO_TIMEOUT;
310
311 // Locally hold mIntervalNow
312 PRIntervalTime intervalNow = mIntervalNow;
313
314 // iterate through hang monitors
315 for (BackgroundHangThread* currentThread = mHangThreads.getFirst();
316 currentThread; currentThread = currentThread->getNext()) {
317
318 if (currentThread->mWaiting) {
319 // Thread is waiting, not hanging
320 continue;
321 }
322 PRIntervalTime interval = currentThread->mInterval;
323 PRIntervalTime hangTime = intervalNow - interval;
324 if (MOZ_UNLIKELY(hangTime >= currentThread->mMaxTimeout)) {
325 // A permahang started
326 // Skip subsequent iterations and tolerate a race on mWaiting here
327 currentThread->mWaiting = true;
328 currentThread->mHanging = false;
329 currentThread->ReportPermaHang();
330 continue;
331 }
332
333 if (MOZ_LIKELY(!currentThread->mHanging)) {
334 if (MOZ_UNLIKELY(hangTime >= currentThread->mTimeout)) {
335 // A hang started
336 currentThread->mStackHelper.GetStack(currentThread->mHangStack);
337 currentThread->mHangStart = interval;
338 currentThread->mHanging = true;
339 currentThread->mAnnotations =
340 currentThread->mAnnotators.GatherAnnotations();
341 }
342 } else {
343 if (MOZ_LIKELY(interval != currentThread->mHangStart)) {
344 // A hang ended
345 currentThread->ReportHang(intervalNow - currentThread->mHangStart);
346 currentThread->mHanging = false;
347 }
348 }
349
350 /* If we are hanging, the next time we check for hang status is when
351 the hang turns into a permahang. If we're not hanging, the next
352 recheck timeout is when we may be entering a hang. */
353 PRIntervalTime nextRecheck;
354 if (currentThread->mHanging) {
355 nextRecheck = currentThread->mMaxTimeout;
356 } else {
357 nextRecheck = currentThread->mTimeout;
358 }
359 recheckTimeout = std::min(recheckTimeout, nextRecheck - hangTime);
360
361 if (currentThread->mTimeout != PR_INTERVAL_NO_TIMEOUT) {
362 /* We wait for a quarter of the shortest timeout
363 value to give mIntervalNow enough granularity. */
364 waitTime = std::min(waitTime, currentThread->mTimeout / 4);
365 }
366 }
367 }
368
369 /* We are shutting down now.
370 Wait for all outstanding monitors to unregister. */
371 while (!mHangThreads.isEmpty()) {
372 autoLock.Wait(PR_INTERVAL_NO_TIMEOUT);
373 }
374 }
375
376
BackgroundHangThread(const char * aName,uint32_t aTimeoutMs,uint32_t aMaxTimeoutMs,BackgroundHangMonitor::ThreadType aThreadType)377 BackgroundHangThread::BackgroundHangThread(const char* aName,
378 uint32_t aTimeoutMs,
379 uint32_t aMaxTimeoutMs,
380 BackgroundHangMonitor::ThreadType aThreadType)
381 : mManager(BackgroundHangManager::sInstance)
382 , mThreadID(PR_GetCurrentThread())
383 , mTimeout(aTimeoutMs == BackgroundHangMonitor::kNoTimeout
384 ? PR_INTERVAL_NO_TIMEOUT
385 : PR_MillisecondsToInterval(aTimeoutMs))
386 , mMaxTimeout(aMaxTimeoutMs == BackgroundHangMonitor::kNoTimeout
387 ? PR_INTERVAL_NO_TIMEOUT
388 : PR_MillisecondsToInterval(aMaxTimeoutMs))
389 , mInterval(mManager->mIntervalNow)
390 , mHangStart(mInterval)
391 , mHanging(false)
392 , mWaiting(true)
393 , mThreadType(aThreadType)
394 , mStats(aName)
395 {
396 if (sTlsKeyInitialized && IsShared()) {
397 sTlsKey.set(this);
398 }
399 // Lock here because LinkedList is not thread-safe
400 MonitorAutoLock autoLock(mManager->mLock);
401 // Add to thread list
402 mManager->mHangThreads.insertBack(this);
403 // Wake up monitor thread to process new thread
404 autoLock.Notify();
405 }
406
~BackgroundHangThread()407 BackgroundHangThread::~BackgroundHangThread()
408 {
409 // Lock here because LinkedList is not thread-safe
410 MonitorAutoLock autoLock(mManager->mLock);
411 // Remove from thread list
412 remove();
413 // Wake up monitor thread to process removed thread
414 autoLock.Notify();
415
416 // We no longer have a thread
417 if (sTlsKeyInitialized && IsShared()) {
418 sTlsKey.set(nullptr);
419 }
420
421 // Move our copy of ThreadHangStats to Telemetry storage
422 Telemetry::RecordThreadHangStats(mStats);
423 }
424
425 Telemetry::HangHistogram&
ReportHang(PRIntervalTime aHangTime)426 BackgroundHangThread::ReportHang(PRIntervalTime aHangTime)
427 {
428 // Recovered from a hang; called on the monitor thread
429 // mManager->mLock IS locked
430
431 // Remove unwanted "js::RunScript" frame from the stack
432 for (size_t i = 0; i < mHangStack.length(); ) {
433 const char** f = mHangStack.begin() + i;
434 if (!mHangStack.IsInBuffer(*f) && !strcmp(*f, "js::RunScript")) {
435 mHangStack.erase(f);
436 } else {
437 i++;
438 }
439 }
440
441 // Collapse duplicated "(chrome script)" and "(content script)" entries in the stack.
442 auto it = std::unique(mHangStack.begin(), mHangStack.end(), StackScriptEntriesCollapser);
443 mHangStack.erase(it, mHangStack.end());
444
445 // Limit the depth of the reported stack if greater than our limit. Only keep its
446 // last entries, since the most recent frames are at the end of the vector.
447 if (mHangStack.length() > kMaxThreadHangStackDepth) {
448 const int elementsToRemove = mHangStack.length() - kMaxThreadHangStackDepth;
449 // Replace the oldest frame with a known label so that we can tell this stack
450 // was limited.
451 mHangStack[0] = "(reduced stack)";
452 mHangStack.erase(mHangStack.begin() + 1, mHangStack.begin() + elementsToRemove);
453 }
454
455 Telemetry::HangHistogram newHistogram(Move(mHangStack));
456 for (Telemetry::HangHistogram* oldHistogram = mStats.mHangs.begin();
457 oldHistogram != mStats.mHangs.end(); oldHistogram++) {
458 if (newHistogram == *oldHistogram) {
459 // New histogram matches old one
460 oldHistogram->Add(aHangTime, Move(mAnnotations));
461 return *oldHistogram;
462 }
463 }
464 // Add new histogram
465 newHistogram.Add(aHangTime, Move(mAnnotations));
466 if (!mStats.mHangs.append(Move(newHistogram))) {
467 MOZ_CRASH();
468 }
469 return mStats.mHangs.back();
470 }
471
472 void
ReportPermaHang()473 BackgroundHangThread::ReportPermaHang()
474 {
475 // Permanently hanged; called on the monitor thread
476 // mManager->mLock IS locked
477
478 Telemetry::HangHistogram& hang = ReportHang(mMaxTimeout);
479 Telemetry::HangStack& stack = hang.GetNativeStack();
480 if (stack.empty()) {
481 mStackHelper.GetNativeStack(stack);
482 }
483 }
484
485 MOZ_ALWAYS_INLINE void
Update()486 BackgroundHangThread::Update()
487 {
488 PRIntervalTime intervalNow = mManager->mIntervalNow;
489 if (mWaiting) {
490 mInterval = intervalNow;
491 mWaiting = false;
492 /* We have to wake up the manager thread because when all threads
493 are waiting, the manager thread waits indefinitely as well. */
494 mManager->Wakeup();
495 } else {
496 PRIntervalTime duration = intervalNow - mInterval;
497 mStats.mActivity.Add(duration);
498 if (MOZ_UNLIKELY(duration >= mTimeout)) {
499 /* Wake up the manager thread to tell it that a hang ended */
500 mManager->Wakeup();
501 }
502 mInterval = intervalNow;
503 }
504 }
505
506 BackgroundHangThread*
FindThread()507 BackgroundHangThread::FindThread()
508 {
509 #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
510 if (BackgroundHangManager::sInstance == nullptr) {
511 MOZ_ASSERT(BackgroundHangManager::sDisabled,
512 "BackgroundHandleManager is not initialized");
513 return nullptr;
514 }
515
516 if (sTlsKeyInitialized) {
517 // Use TLS if available
518 return sTlsKey.get();
519 }
520 // If TLS is unavailable, we can search through the thread list
521 RefPtr<BackgroundHangManager> manager(BackgroundHangManager::sInstance);
522 MOZ_ASSERT(manager, "Creating BackgroundHangMonitor after shutdown");
523
524 PRThread* threadID = PR_GetCurrentThread();
525 // Lock thread list for traversal
526 MonitorAutoLock autoLock(manager->mLock);
527 for (BackgroundHangThread* thread = manager->mHangThreads.getFirst();
528 thread; thread = thread->getNext()) {
529 if (thread->mThreadID == threadID && thread->IsShared()) {
530 return thread;
531 }
532 }
533 #endif
534 // Current thread is not initialized
535 return nullptr;
536 }
537
538 bool
ShouldDisableOnBeta(const nsCString & clientID)539 BackgroundHangMonitor::ShouldDisableOnBeta(const nsCString &clientID) {
540 MOZ_ASSERT(clientID.Length() == 36, "clientID is invalid");
541 const char *suffix = clientID.get() + clientID.Length() - 4;
542 return strtol(suffix, NULL, 16) % BHR_BETA_MOD;
543 }
544
545 bool
IsDisabled()546 BackgroundHangMonitor::IsDisabled() {
547 #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
548 return BackgroundHangManager::sDisabled;
549 #else
550 return true;
551 #endif
552 }
553
554 bool
DisableOnBeta()555 BackgroundHangMonitor::DisableOnBeta() {
556 nsAdoptingCString clientID = Preferences::GetCString("toolkit.telemetry.cachedClientID");
557 bool telemetryEnabled = Preferences::GetBool("toolkit.telemetry.enabled");
558
559 if (!telemetryEnabled || !clientID || BackgroundHangMonitor::ShouldDisableOnBeta(clientID)) {
560 if (XRE_IsParentProcess()) {
561 BackgroundHangMonitor::Shutdown();
562 } else {
563 BackgroundHangManager::sDisabled = true;
564 }
565 return true;
566 }
567
568 return false;
569 }
570
571 void
Startup()572 BackgroundHangMonitor::Startup()
573 {
574 #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
575 MOZ_ASSERT(!BackgroundHangManager::sInstance, "Already initialized");
576
577 if (!strcmp(NS_STRINGIFY(MOZ_UPDATE_CHANNEL), "beta")) {
578 if (XRE_IsParentProcess()) { // cached ClientID hasn't been read yet
579 ThreadStackHelper::Startup();
580 BackgroundHangThread::Startup();
581 BackgroundHangManager::sInstance = new BackgroundHangManager();
582
583 nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
584 MOZ_ASSERT(observerService);
585
586 observerService->AddObserver(BackgroundHangManager::sInstance, "profile-after-change", false);
587 return;
588 } else if(DisableOnBeta()){
589 return;
590 }
591 }
592
593 ThreadStackHelper::Startup();
594 BackgroundHangThread::Startup();
595 BackgroundHangManager::sInstance = new BackgroundHangManager();
596 #endif
597 }
598
599 void
Shutdown()600 BackgroundHangMonitor::Shutdown()
601 {
602 #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
603 if (BackgroundHangManager::sDisabled) {
604 MOZ_ASSERT(!BackgroundHangManager::sInstance, "Initialized");
605 return;
606 }
607
608 MOZ_ASSERT(BackgroundHangManager::sInstance, "Not initialized");
609 /* Scope our lock inside Shutdown() because the sInstance object can
610 be destroyed as soon as we set sInstance to nullptr below, and
611 we don't want to hold the lock when it's being destroyed. */
612 BackgroundHangManager::sInstance->Shutdown();
613 BackgroundHangManager::sInstance = nullptr;
614 ThreadStackHelper::Shutdown();
615 BackgroundHangManager::sDisabled = true;
616 #endif
617 }
618
BackgroundHangMonitor(const char * aName,uint32_t aTimeoutMs,uint32_t aMaxTimeoutMs,ThreadType aThreadType)619 BackgroundHangMonitor::BackgroundHangMonitor(const char* aName,
620 uint32_t aTimeoutMs,
621 uint32_t aMaxTimeoutMs,
622 ThreadType aThreadType)
623 : mThread(aThreadType == THREAD_SHARED ? BackgroundHangThread::FindThread() : nullptr)
624 {
625 #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
626 if (!BackgroundHangManager::sDisabled && !mThread) {
627 mThread = new BackgroundHangThread(aName, aTimeoutMs, aMaxTimeoutMs,
628 aThreadType);
629 }
630 #endif
631 }
632
BackgroundHangMonitor()633 BackgroundHangMonitor::BackgroundHangMonitor()
634 : mThread(BackgroundHangThread::FindThread())
635 {
636 #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
637 if (BackgroundHangManager::sDisabled) {
638 return;
639 }
640 #endif
641 }
642
~BackgroundHangMonitor()643 BackgroundHangMonitor::~BackgroundHangMonitor()
644 {
645 }
646
647 void
NotifyActivity()648 BackgroundHangMonitor::NotifyActivity()
649 {
650 #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
651 if (mThread == nullptr) {
652 MOZ_ASSERT(BackgroundHangManager::sDisabled,
653 "This thread is not initialized for hang monitoring");
654 return;
655 }
656
657 if (Telemetry::CanRecordExtended()) {
658 mThread->NotifyActivity();
659 }
660 #endif
661 }
662
663 void
NotifyWait()664 BackgroundHangMonitor::NotifyWait()
665 {
666 #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
667 if (mThread == nullptr) {
668 MOZ_ASSERT(BackgroundHangManager::sDisabled,
669 "This thread is not initialized for hang monitoring");
670 return;
671 }
672
673 if (Telemetry::CanRecordExtended()) {
674 mThread->NotifyWait();
675 }
676 #endif
677 }
678
679 bool
RegisterAnnotator(HangMonitor::Annotator & aAnnotator)680 BackgroundHangMonitor::RegisterAnnotator(HangMonitor::Annotator& aAnnotator)
681 {
682 #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
683 BackgroundHangThread* thisThread = BackgroundHangThread::FindThread();
684 if (!thisThread) {
685 return false;
686 }
687 return thisThread->mAnnotators.Register(aAnnotator);
688 #else
689 return false;
690 #endif
691 }
692
693 bool
UnregisterAnnotator(HangMonitor::Annotator & aAnnotator)694 BackgroundHangMonitor::UnregisterAnnotator(HangMonitor::Annotator& aAnnotator)
695 {
696 #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
697 BackgroundHangThread* thisThread = BackgroundHangThread::FindThread();
698 if (!thisThread) {
699 return false;
700 }
701 return thisThread->mAnnotators.Unregister(aAnnotator);
702 #else
703 return false;
704 #endif
705 }
706
707 /* Because we are iterating through the BackgroundHangThread linked list,
708 we need to take a lock. Using MonitorAutoLock as a base class makes
709 sure all of that is taken care of for us. */
ThreadHangStatsIterator()710 BackgroundHangMonitor::ThreadHangStatsIterator::ThreadHangStatsIterator()
711 : MonitorAutoLock(BackgroundHangManager::sInstance->mLock)
712 , mThread(BackgroundHangManager::sInstance ?
713 BackgroundHangManager::sInstance->mHangThreads.getFirst() :
714 nullptr)
715 {
716 #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
717 MOZ_ASSERT(BackgroundHangManager::sInstance ||
718 BackgroundHangManager::sDisabled,
719 "Inconsistent state");
720 #endif
721 }
722
723 Telemetry::ThreadHangStats*
GetNext()724 BackgroundHangMonitor::ThreadHangStatsIterator::GetNext()
725 {
726 if (!mThread) {
727 return nullptr;
728 }
729 Telemetry::ThreadHangStats* stats = &mThread->mStats;
730 mThread = mThread->getNext();
731 return stats;
732 }
733
734 } // namespace mozilla
735