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