summaryrefslogtreecommitdiffstats
path: root/xpcom/threads/BackgroundHangMonitor.h
blob: 698cf2305ebbec95aca3b2360a2f31f40fcb93e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
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