summaryrefslogtreecommitdiff
path: root/xpcom/threads/BackgroundHangMonitor.h
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/threads/BackgroundHangMonitor.h')
-rw-r--r--xpcom/threads/BackgroundHangMonitor.h246
1 files changed, 246 insertions, 0 deletions
diff --git a/xpcom/threads/BackgroundHangMonitor.h b/xpcom/threads/BackgroundHangMonitor.h
new file mode 100644
index 0000000000..698cf2305e
--- /dev/null
+++ b/xpcom/threads/BackgroundHangMonitor.h
@@ -0,0 +1,246 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 <stdint.h>
+
+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<BackgroundHangThread> 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