/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_BackgroundHangMonitor_h #define mozilla_BackgroundHangMonitor_h #include "mozilla/HangAnnotations.h" #include "mozilla/Monitor.h" #include "mozilla/RefPtr.h" #include "nsString.h" #include namespace mozilla { namespace Telemetry { class ThreadHangStats; } // namespace Telemetry class BackgroundHangThread; class BackgroundHangManager; /** * The background hang monitor is responsible for detecting and reporting * hangs in main and background threads. A thread registers itself using * the BackgroundHangMonitor object and periodically calls its methods to * inform the hang monitor of the thread's activity. Each thread is given * a thread name, a timeout, and a maximum timeout. If one of the thread's * tasks runs for longer than the timeout duration but shorter than the * maximum timeout, a (transient) hang is reported. On the other hand, if * a task runs for longer than the maximum timeout duration or never * finishes (e.g. in a deadlock), a permahang is reported. * * Tasks are defined arbitrarily, but are typically represented by events * in an event loop -- processing one event is equivalent to running one * task. To ensure responsiveness, tasks in a thread often have a target * running time. This is a good starting point for determining the timeout * and maximum timeout values. For example, the Compositor thread has a * responsiveness goal of 60Hz or 17ms, so a starting timeout could be * 100ms. Considering some platforms (e.g. Android) can terminate the app * when a critical thread hangs for longer than a few seconds, a good * starting maximum timeout is 4 or 5 seconds. * * A thread registers itself through the BackgroundHangMonitor constructor. * Multiple BackgroundHangMonitor objects can be used in one thread. The * constructor without arguments can be used when it is known that the thread * already has a BackgroundHangMonitor registered. When all instances of * BackgroundHangMonitor are destroyed, the thread is unregistered. * * The thread then uses two methods to inform BackgroundHangMonitor of the * thread's activity: * * > BackgroundHangMonitor::NotifyActivity should be called *before* * starting a task. The task run time is determined by the interval * between this call and the next NotifyActivity call. * * > BackgroundHangMonitor::NotifyWait should be called *before* the * thread enters a wait state (e.g. to wait for a new event). This * prevents a waiting thread from being detected as hanging. The wait * state is automatically cleared at the next NotifyActivity call. * * The following example shows hang monitoring in a simple event loop: * * void thread_main() * { * mozilla::BackgroundHangMonitor hangMonitor("example1", 100, 1000); * while (!exiting) { * hangMonitor.NotifyActivity(); * process_next_event(); * hangMonitor.NotifyWait(); * wait_for_next_event(); * } * } * * The following example shows reentrancy in nested event loops: * * void thread_main() * { * mozilla::BackgroundHangMonitor hangMonitor("example2", 100, 1000); * while (!exiting) { * hangMonitor.NotifyActivity(); * process_next_event(); * hangMonitor.NotifyWait(); * wait_for_next_event(); * } * } * * void process_next_event() * { * mozilla::BackgroundHangMonitor hangMonitor(); * if (is_sync_event) { * while (!finished_event) { * hangMonitor.NotifyActivity(); * process_next_event(); * hangMonitor.NotifyWait(); * wait_for_next_event(); * } * } else { * process_nonsync_event(); * } * } */ class BackgroundHangMonitor { private: friend BackgroundHangManager; RefPtr mThread; static bool ShouldDisableOnBeta(const nsCString &); static bool DisableOnBeta(); public: static const uint32_t kNoTimeout = 0; enum ThreadType { // For a new BackgroundHangMonitor for thread T, only create a new // monitoring thread for T if one doesn't already exist. If one does, // share that pre-existing monitoring thread. THREAD_SHARED, // For a new BackgroundHangMonitor for thread T, create a new // monitoring thread for T even if there are other, pre-existing // monitoring threads for T. THREAD_PRIVATE }; /** * ThreadHangStatsIterator is used to iterate through the ThreadHangStats * associated with each active monitored thread. Because of an internal * lock while this object is alive, a thread must use only one instance * of this class at a time and must iterate through the list as fast as * possible. The following example shows using the iterator: * * { * // Scope the iter variable so it's destroyed as soon as we're done * BackgroundHangMonitor::ThreadHangStatsIterator iter; * for (ThreadHangStats* histogram = iter.GetNext(); * histogram; histogram = iter.GetNext()) { * // Process histogram * } * } */ class ThreadHangStatsIterator : public MonitorAutoLock { private: BackgroundHangThread* mThread; ThreadHangStatsIterator(const ThreadHangStatsIterator&); ThreadHangStatsIterator& operator=(const ThreadHangStatsIterator&); public: /** * Create an ThreadHangStatsIterator instance and take the internal lock. * Internal lock is released on destruction. */ ThreadHangStatsIterator(); /** * Get the next item in the list; the first call returns the first item. * Returns nullptr at the end of the list. */ Telemetry::ThreadHangStats* GetNext(); }; /** * Enable hang monitoring. * Must return before using BackgroundHangMonitor. */ static void Startup(); /** * Disable hang monitoring. * Can be called without destroying all BackgroundHangMonitors first. */ static void Shutdown(); /** * Returns true if BHR is disabled. */ static bool IsDisabled(); /** * Start monitoring hangs for the current thread. * * @param aName Name to identify the thread with * @param aTimeoutMs Amount of time in milliseconds without * activity before registering a hang * @param aMaxTimeoutMs Amount of time in milliseconds without * activity before registering a permanent hang * @param aThreadType * The ThreadType type of monitoring thread that should be created * for this monitor. See the documentation for ThreadType. */ BackgroundHangMonitor(const char* aName, uint32_t aTimeoutMs, uint32_t aMaxTimeoutMs, ThreadType aThreadType = THREAD_SHARED); /** * Monitor hangs using an existing monitor * associated with the current thread. */ BackgroundHangMonitor(); /** * Destroys the hang monitor; hang monitoring for a thread stops * when all monitors associated with the thread are destroyed. */ ~BackgroundHangMonitor(); /** * Notify the hang monitor of pending current thread activity. * Call this method before starting an "activity" or after * exiting from a wait state. */ void NotifyActivity(); /** * Notify the hang monitor of current thread wait. * Call this method before entering a wait state; call * NotifyActivity when subsequently exiting the wait state. */ void NotifyWait(); /** * Register an annotator with BHR for the current thread. * @param aAnnotator annotator to register * @return true if the annotator was registered, otherwise false. */ static bool RegisterAnnotator(HangMonitor::Annotator& aAnnotator); /** * Unregister an annotator that was previously registered via * RegisterAnnotator. * @param aAnnotator annotator to unregister * @return true if there are still remaining annotators registered */ static bool UnregisterAnnotator(HangMonitor::Annotator& aAnnotator); }; } // namespace mozilla #endif // mozilla_BackgroundHangMonitor_h