1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla Firefox.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * the Mozilla Foundation <http://www.mozilla.org>.
19 : * Portions created by the Initial Developer are Copyright (C) 2011
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either the GNU General Public License Version 2 or later (the "GPL"), or
26 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #include "mozilla/HangMonitor.h"
39 : #include "mozilla/Monitor.h"
40 : #include "mozilla/Preferences.h"
41 : #include "nsXULAppAPI.h"
42 : #include "nsThreadUtils.h"
43 :
44 : #ifdef MOZ_CRASHREPORTER
45 : #include "nsExceptionHandler.h"
46 : #endif
47 :
48 : #ifdef XP_WIN
49 : #include <windows.h>
50 : #endif
51 :
52 : namespace mozilla { namespace HangMonitor {
53 :
54 : /**
55 : * A flag which may be set from within a debugger to disable the hang
56 : * monitor.
57 : */
58 : volatile bool gDebugDisableHangMonitor = false;
59 :
60 : const char kHangMonitorPrefName[] = "hangmonitor.timeout";
61 :
62 : // Monitor protects gShutdown and gTimeout, but not gTimestamp which rely on
63 : // being atomically set by the processor; synchronization doesn't really matter
64 : // in this use case.
65 : Monitor* gMonitor;
66 :
67 : // The timeout preference, in seconds.
68 : PRInt32 gTimeout;
69 :
70 : PRThread* gThread;
71 :
72 : // Set when shutdown begins to signal the thread to exit immediately.
73 : bool gShutdown;
74 :
75 : // The timestamp of the last event notification, or PR_INTERVAL_NO_WAIT if
76 : // we're currently not processing events.
77 : volatile PRIntervalTime gTimestamp;
78 :
79 : // PrefChangedFunc
80 : int
81 1420 : PrefChanged(const char*, void*)
82 : {
83 1420 : PRInt32 newval = Preferences::GetInt(kHangMonitorPrefName);
84 2840 : MonitorAutoLock lock(*gMonitor);
85 1420 : if (newval != gTimeout) {
86 0 : gTimeout = newval;
87 0 : lock.Notify();
88 : }
89 :
90 1420 : return 0;
91 : }
92 :
93 : void
94 0 : Crash()
95 : {
96 0 : if (gDebugDisableHangMonitor) {
97 0 : return;
98 : }
99 :
100 : #ifdef XP_WIN
101 : if (::IsDebuggerPresent()) {
102 : return;
103 : }
104 : #endif
105 :
106 : #ifdef MOZ_CRASHREPORTER
107 0 : CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("Hang"),
108 0 : NS_LITERAL_CSTRING("1"));
109 : #endif
110 :
111 0 : NS_RUNTIMEABORT("HangMonitor triggered");
112 : }
113 :
114 : void
115 1419 : ThreadMain(void*)
116 : {
117 2838 : MonitorAutoLock lock(*gMonitor);
118 :
119 : // In order to avoid issues with the hang monitor incorrectly triggering
120 : // during a general system stop such as sleeping, the monitor thread must
121 : // run twice to trigger hang protection.
122 1419 : PRIntervalTime lastTimestamp = 0;
123 1419 : int waitCount = 0;
124 :
125 1419 : while (true) {
126 2838 : if (gShutdown) {
127 : return; // Exit the thread
128 : }
129 :
130 : // avoid rereading the volatile value in this loop
131 1419 : PRIntervalTime timestamp = gTimestamp;
132 :
133 1419 : PRIntervalTime now = PR_IntervalNow();
134 :
135 1419 : if (timestamp != PR_INTERVAL_NO_WAIT &&
136 : now < timestamp) {
137 : // 32-bit overflow, reset for another waiting period
138 0 : timestamp = 1; // lowest legal PRInterval value
139 : }
140 :
141 1419 : if (timestamp != PR_INTERVAL_NO_WAIT &&
142 : timestamp == lastTimestamp &&
143 : gTimeout > 0) {
144 0 : ++waitCount;
145 0 : if (waitCount == 2) {
146 : PRInt32 delay =
147 0 : PRInt32(PR_IntervalToSeconds(now - timestamp));
148 0 : if (delay > gTimeout) {
149 0 : MonitorAutoUnlock unlock(*gMonitor);
150 0 : Crash();
151 : }
152 0 : }
153 : }
154 : else {
155 1419 : lastTimestamp = timestamp;
156 1419 : waitCount = 0;
157 : }
158 :
159 : PRIntervalTime timeout;
160 1419 : if (gTimeout <= 0) {
161 1419 : timeout = PR_INTERVAL_NO_TIMEOUT;
162 : }
163 : else {
164 0 : timeout = PR_MillisecondsToInterval(gTimeout * 500);
165 : }
166 1419 : lock.Wait(timeout);
167 : }
168 : }
169 :
170 : void
171 1419 : Startup()
172 : {
173 : // The hang detector only runs in chrome processes. If you change this,
174 : // you must also deal with the threadsafety of AnnotateCrashReport in
175 : // non-chrome processes!
176 1419 : if (GeckoProcessType_Default != XRE_GetProcessType())
177 0 : return;
178 :
179 1419 : NS_ASSERTION(!gMonitor, "Hang monitor already initialized");
180 1419 : gMonitor = new Monitor("HangMonitor");
181 :
182 1419 : Preferences::RegisterCallback(PrefChanged, kHangMonitorPrefName, NULL);
183 1419 : PrefChanged(NULL, NULL);
184 :
185 : // Don't actually start measuring hangs until we hit the main event loop.
186 : // This potentially misses a small class of really early startup hangs,
187 : // but avoids dealing with some xpcshell tests and other situations which
188 : // start XPCOM but don't ever start the event loop.
189 1419 : Suspend();
190 :
191 : gThread = PR_CreateThread(PR_USER_THREAD,
192 : ThreadMain,
193 : NULL, PR_PRIORITY_LOW, PR_GLOBAL_THREAD,
194 1419 : PR_JOINABLE_THREAD, 0);
195 : }
196 :
197 : void
198 1419 : Shutdown()
199 : {
200 1419 : if (GeckoProcessType_Default != XRE_GetProcessType())
201 0 : return;
202 :
203 1419 : NS_ASSERTION(gMonitor, "Hang monitor not started");
204 :
205 : { // Scope the lock we're going to delete later
206 2838 : MonitorAutoLock lock(*gMonitor);
207 1419 : gShutdown = true;
208 1419 : lock.Notify();
209 : }
210 :
211 : // thread creation could theoretically fail
212 1419 : if (gThread) {
213 1419 : PR_JoinThread(gThread);
214 1419 : gThread = NULL;
215 : }
216 :
217 1419 : delete gMonitor;
218 1419 : gMonitor = NULL;
219 : }
220 :
221 : void
222 438268 : NotifyActivity()
223 : {
224 438268 : NS_ASSERTION(NS_IsMainThread(), "HangMonitor::Notify called from off the main thread.");
225 :
226 : // This is not a locked activity because PRTimeStamp is a 32-bit quantity
227 : // which can be read/written atomically, and we don't want to pay locking
228 : // penalties here.
229 438268 : gTimestamp = PR_IntervalNow();
230 438268 : }
231 :
232 : void
233 423366 : Suspend()
234 : {
235 423366 : NS_ASSERTION(NS_IsMainThread(), "HangMonitor::Suspend called from off the main thread.");
236 :
237 : // Because gTimestamp changes this resets the wait count.
238 423366 : gTimestamp = PR_INTERVAL_NO_WAIT;
239 423366 : }
240 :
241 : } } // namespace mozilla::HangMonitor
|