/src/mozilla-central/dom/ipc/ProcessHangMonitor.cpp
Line | Count | Source (jump to first uncovered line) |
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/ProcessHangMonitor.h" |
8 | | #include "mozilla/ProcessHangMonitorIPC.h" |
9 | | |
10 | | #include "jsapi.h" |
11 | | |
12 | | #include "mozilla/Atomics.h" |
13 | | #include "mozilla/BackgroundHangMonitor.h" |
14 | | #include "mozilla/dom/ContentParent.h" |
15 | | #include "mozilla/dom/Element.h" |
16 | | #include "mozilla/dom/ScriptSettings.h" |
17 | | #include "mozilla/dom/TabChild.h" |
18 | | #include "mozilla/dom/TabParent.h" |
19 | | #include "mozilla/ipc/TaskFactory.h" |
20 | | #include "mozilla/Monitor.h" |
21 | | #include "mozilla/plugins/PluginBridge.h" |
22 | | #include "mozilla/Preferences.h" |
23 | | #include "mozilla/Unused.h" |
24 | | #include "mozilla/WeakPtr.h" |
25 | | |
26 | | #include "nsExceptionHandler.h" |
27 | | #include "nsFrameLoader.h" |
28 | | #include "nsIHangReport.h" |
29 | | #include "nsITabParent.h" |
30 | | #include "nsQueryObject.h" |
31 | | #include "nsPluginHost.h" |
32 | | #include "nsThreadUtils.h" |
33 | | |
34 | | #include "base/task.h" |
35 | | #include "base/thread.h" |
36 | | |
37 | | #ifdef XP_WIN |
38 | | // For IsDebuggerPresent() |
39 | | #include <windows.h> |
40 | | #endif |
41 | | |
42 | | using namespace mozilla; |
43 | | using namespace mozilla::dom; |
44 | | using namespace mozilla::ipc; |
45 | | |
46 | | /* |
47 | | * Basic architecture: |
48 | | * |
49 | | * Each process has its own ProcessHangMonitor singleton. This singleton exists |
50 | | * as long as there is at least one content process in the system. Each content |
51 | | * process has a HangMonitorChild and the chrome process has one |
52 | | * HangMonitorParent per process. Each process (including the chrome process) |
53 | | * runs a hang monitoring thread. The PHangMonitor actors are bound to this |
54 | | * thread so that they never block on the main thread. |
55 | | * |
56 | | * When the content process detects a hang, it posts a task to its hang thread, |
57 | | * which sends an IPC message to the hang thread in the parent. The parent |
58 | | * cancels any ongoing CPOW requests and then posts a runnable to the main |
59 | | * thread that notifies Firefox frontend code of the hang. The frontend code is |
60 | | * passed an nsIHangReport, which can be used to terminate the hang. |
61 | | * |
62 | | * If the user chooses to terminate a script, a task is posted to the chrome |
63 | | * process's hang monitoring thread, which sends an IPC message to the hang |
64 | | * thread in the content process. That thread sets a flag to indicate that JS |
65 | | * execution should be terminated the next time it hits the interrupt |
66 | | * callback. A similar scheme is used for debugging slow scripts. If a content |
67 | | * process or plug-in needs to be terminated, the chrome process does so |
68 | | * directly, without messaging the content process. |
69 | | */ |
70 | | |
71 | | namespace { |
72 | | |
73 | | /* Child process objects */ |
74 | | |
75 | | class HangMonitorChild |
76 | | : public PProcessHangMonitorChild |
77 | | , public BackgroundHangAnnotator |
78 | | { |
79 | | public: |
80 | | explicit HangMonitorChild(ProcessHangMonitor* aMonitor); |
81 | | ~HangMonitorChild() override; |
82 | | |
83 | | void Bind(Endpoint<PProcessHangMonitorChild>&& aEndpoint); |
84 | | |
85 | | typedef ProcessHangMonitor::SlowScriptAction SlowScriptAction; |
86 | | SlowScriptAction NotifySlowScript(nsITabChild* aTabChild, |
87 | | const char* aFileName, |
88 | | const nsString& aAddonId); |
89 | | void NotifySlowScriptAsync(TabId aTabId, |
90 | | const nsCString& aFileName, |
91 | | const nsString& aAddonId); |
92 | | |
93 | | bool IsDebuggerStartupComplete(); |
94 | | |
95 | | void NotifyPluginHang(uint32_t aPluginId); |
96 | | void NotifyPluginHangAsync(uint32_t aPluginId); |
97 | | |
98 | | void ClearHang(); |
99 | | void ClearHangAsync(); |
100 | | void ClearPaintWhileInterruptingJS(const LayersObserverEpoch& aEpoch); |
101 | | |
102 | | // MaybeStartPaintWhileInterruptingJS will notify the background hang monitor of activity |
103 | | // if this is the first time calling it since ClearPaintWhileInterruptingJS. It should be |
104 | | // callable from any thread, but you must be holding mMonitor if using it off |
105 | | // the main thread, since it could race with ClearPaintWhileInterruptingJS. |
106 | | void MaybeStartPaintWhileInterruptingJS(); |
107 | | |
108 | | mozilla::ipc::IPCResult RecvTerminateScript(const bool& aTerminateGlobal) override; |
109 | | mozilla::ipc::IPCResult RecvBeginStartingDebugger() override; |
110 | | mozilla::ipc::IPCResult RecvEndStartingDebugger() override; |
111 | | |
112 | | mozilla::ipc::IPCResult RecvPaintWhileInterruptingJS(const TabId& aTabId, |
113 | | const bool& aForceRepaint, |
114 | | const LayersObserverEpoch& aEpoch) override; |
115 | | |
116 | | void ActorDestroy(ActorDestroyReason aWhy) override; |
117 | | |
118 | | void InterruptCallback(); |
119 | | void Shutdown(); |
120 | | |
121 | 1.62M | static HangMonitorChild* Get() { return sInstance; } |
122 | | |
123 | | void Dispatch(already_AddRefed<nsIRunnable> aRunnable) |
124 | 0 | { |
125 | 0 | mHangMonitor->Dispatch(std::move(aRunnable)); |
126 | 0 | } |
127 | 0 | bool IsOnThread() { return mHangMonitor->IsOnThread(); } |
128 | | |
129 | | void AnnotateHang(BackgroundHangAnnotations& aAnnotations) override; |
130 | | |
131 | | private: |
132 | | void ShutdownOnThread(); |
133 | | |
134 | | // Ordering of this atomic is not preserved while recording/replaying, as it |
135 | | // may be accessed during the JS interrupt callback. |
136 | | static Atomic<HangMonitorChild*, SequentiallyConsistent, |
137 | | recordreplay::Behavior::DontPreserve> sInstance; |
138 | | |
139 | | const RefPtr<ProcessHangMonitor> mHangMonitor; |
140 | | Monitor mMonitor; |
141 | | |
142 | | // Main thread-only. |
143 | | bool mSentReport; |
144 | | |
145 | | // These fields must be accessed with mMonitor held. |
146 | | bool mTerminateScript; |
147 | | bool mTerminateGlobal; |
148 | | bool mStartDebugger; |
149 | | bool mFinishedStartingDebugger; |
150 | | bool mPaintWhileInterruptingJS; |
151 | | bool mPaintWhileInterruptingJSForce; |
152 | | TabId mPaintWhileInterruptingJSTab; |
153 | | MOZ_INIT_OUTSIDE_CTOR LayersObserverEpoch mPaintWhileInterruptingJSEpoch; |
154 | | JSContext* mContext; |
155 | | bool mShutdownDone; |
156 | | |
157 | | // This field is only accessed on the hang thread. |
158 | | bool mIPCOpen; |
159 | | |
160 | | // Allows us to ensure we NotifyActivity only once, allowing |
161 | | // either thread to do so. |
162 | | Atomic<bool> mPaintWhileInterruptingJSActive; |
163 | | }; |
164 | | |
165 | | Atomic<HangMonitorChild*, SequentiallyConsistent, |
166 | | recordreplay::Behavior::DontPreserve> HangMonitorChild::sInstance; |
167 | | |
168 | | /* Parent process objects */ |
169 | | |
170 | | class HangMonitorParent; |
171 | | |
172 | | class HangMonitoredProcess final |
173 | | : public nsIHangReport |
174 | | { |
175 | | public: |
176 | | NS_DECL_THREADSAFE_ISUPPORTS |
177 | | |
178 | | HangMonitoredProcess(HangMonitorParent* aActor, |
179 | | ContentParent* aContentParent) |
180 | 0 | : mActor(aActor), mContentParent(aContentParent) {} |
181 | | |
182 | | NS_DECL_NSIHANGREPORT |
183 | | |
184 | | // Called when a content process shuts down. |
185 | 0 | void Clear() { |
186 | 0 | mContentParent = nullptr; |
187 | 0 | mActor = nullptr; |
188 | 0 | } |
189 | | |
190 | | /** |
191 | | * Sets the information associated with this hang: this includes the ID of |
192 | | * the plugin which caused the hang as well as the content PID. The ID of |
193 | | * a minidump taken during the hang can also be provided. |
194 | | * |
195 | | * @param aHangData The hang information |
196 | | * @param aDumpId The ID of a minidump taken when the hang occurred |
197 | | */ |
198 | 0 | void SetHangData(const HangData& aHangData, const nsAString& aDumpId) { |
199 | 0 | mHangData = aHangData; |
200 | 0 | mDumpId = aDumpId; |
201 | 0 | } |
202 | | |
203 | 0 | void ClearHang() { |
204 | 0 | mHangData = HangData(); |
205 | 0 | mDumpId.Truncate(); |
206 | 0 | } |
207 | | |
208 | | private: |
209 | 0 | ~HangMonitoredProcess() = default; |
210 | | |
211 | | // Everything here is main thread-only. |
212 | | HangMonitorParent* mActor; |
213 | | ContentParent* mContentParent; |
214 | | HangData mHangData; |
215 | | nsAutoString mDumpId; |
216 | | }; |
217 | | |
218 | | class HangMonitorParent |
219 | | : public PProcessHangMonitorParent |
220 | | , public SupportsWeakPtr<HangMonitorParent> |
221 | | { |
222 | | public: |
223 | | explicit HangMonitorParent(ProcessHangMonitor* aMonitor); |
224 | | ~HangMonitorParent() override; |
225 | | |
226 | | MOZ_DECLARE_WEAKREFERENCE_TYPENAME(HangMonitorParent) |
227 | | |
228 | | void Bind(Endpoint<PProcessHangMonitorParent>&& aEndpoint); |
229 | | |
230 | | mozilla::ipc::IPCResult RecvHangEvidence(const HangData& aHangData) override; |
231 | | mozilla::ipc::IPCResult RecvClearHang() override; |
232 | | |
233 | | void ActorDestroy(ActorDestroyReason aWhy) override; |
234 | | |
235 | 0 | void SetProcess(HangMonitoredProcess* aProcess) { mProcess = aProcess; } |
236 | | |
237 | | void Shutdown(); |
238 | | |
239 | | void PaintWhileInterruptingJS(dom::TabParent* aTabParent, |
240 | | bool aForceRepaint, |
241 | | const LayersObserverEpoch& aEpoch); |
242 | | |
243 | | void TerminateScript(bool aTerminateGlobal); |
244 | | void BeginStartingDebugger(); |
245 | | void EndStartingDebugger(); |
246 | | void CleanupPluginHang(uint32_t aPluginId, bool aRemoveFiles); |
247 | | |
248 | | /** |
249 | | * Update the dump for the specified plugin. This method is thread-safe and |
250 | | * is used to replace a browser minidump with a full minidump. If aDumpId is |
251 | | * empty this is a no-op. |
252 | | */ |
253 | | void UpdateMinidump(uint32_t aPluginId, const nsString& aDumpId); |
254 | | |
255 | | void Dispatch(already_AddRefed<nsIRunnable> aRunnable) |
256 | 0 | { |
257 | 0 | mHangMonitor->Dispatch(std::move(aRunnable)); |
258 | 0 | } |
259 | 0 | bool IsOnThread() { return mHangMonitor->IsOnThread(); } |
260 | | |
261 | | private: |
262 | | bool TakeBrowserMinidump(const PluginHangData& aPhd, nsString& aCrashId); |
263 | | |
264 | | void SendHangNotification(const HangData& aHangData, |
265 | | const nsString& aBrowserDumpId, |
266 | | bool aTakeMinidump); |
267 | | |
268 | | void ClearHangNotification(); |
269 | | |
270 | | void PaintWhileInterruptingJSOnThread(TabId aTabId, bool aForceRepaint, const LayersObserverEpoch& aEpoch); |
271 | | |
272 | | void ShutdownOnThread(); |
273 | | |
274 | | const RefPtr<ProcessHangMonitor> mHangMonitor; |
275 | | |
276 | | // This field is read-only after construction. |
277 | | bool mReportHangs; |
278 | | |
279 | | // This field is only accessed on the hang thread. |
280 | | bool mIPCOpen; |
281 | | |
282 | | Monitor mMonitor; |
283 | | |
284 | | // Must be accessed with mMonitor held. |
285 | | RefPtr<HangMonitoredProcess> mProcess; |
286 | | bool mShutdownDone; |
287 | | // Map from plugin ID to crash dump ID. Protected by mBrowserCrashDumpHashLock. |
288 | | nsDataHashtable<nsUint32HashKey, nsString> mBrowserCrashDumpIds; |
289 | | Mutex mBrowserCrashDumpHashLock; |
290 | | mozilla::ipc::TaskFactory<HangMonitorParent> mMainThreadTaskFactory; |
291 | | |
292 | | static bool sShouldPaintWhileInterruptingJS; |
293 | | }; |
294 | | |
295 | | bool HangMonitorParent::sShouldPaintWhileInterruptingJS = true; |
296 | | |
297 | | } // namespace |
298 | | |
299 | | /* HangMonitorChild implementation */ |
300 | | |
301 | | HangMonitorChild::HangMonitorChild(ProcessHangMonitor* aMonitor) |
302 | | : mHangMonitor(aMonitor), |
303 | | // Ordering of this atomic is not preserved while recording/replaying, as it |
304 | | // may be accessed during the JS interrupt callback. |
305 | | mMonitor("HangMonitorChild lock", recordreplay::Behavior::DontPreserve), |
306 | | mSentReport(false), |
307 | | mTerminateScript(false), |
308 | | mTerminateGlobal(false), |
309 | | mStartDebugger(false), |
310 | | mFinishedStartingDebugger(false), |
311 | | mPaintWhileInterruptingJS(false), |
312 | | mPaintWhileInterruptingJSForce(false), |
313 | | mShutdownDone(false), |
314 | | mIPCOpen(true), |
315 | | mPaintWhileInterruptingJSActive(false) |
316 | 0 | { |
317 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
318 | 0 | mContext = danger::GetJSContext(); |
319 | 0 |
|
320 | 0 | BackgroundHangMonitor::RegisterAnnotator(*this); |
321 | 0 | } |
322 | | |
323 | | HangMonitorChild::~HangMonitorChild() |
324 | 0 | { |
325 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
326 | 0 | MOZ_ASSERT(sInstance == this); |
327 | 0 | sInstance = nullptr; |
328 | 0 | } |
329 | | |
330 | | void |
331 | | HangMonitorChild::InterruptCallback() |
332 | 0 | { |
333 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
334 | 0 |
|
335 | 0 | bool paintWhileInterruptingJS; |
336 | 0 | bool paintWhileInterruptingJSForce; |
337 | 0 | TabId paintWhileInterruptingJSTab; |
338 | 0 | LayersObserverEpoch paintWhileInterruptingJSEpoch; |
339 | 0 |
|
340 | 0 | { |
341 | 0 | MonitorAutoLock lock(mMonitor); |
342 | 0 | paintWhileInterruptingJS = mPaintWhileInterruptingJS; |
343 | 0 | paintWhileInterruptingJSForce = mPaintWhileInterruptingJSForce; |
344 | 0 | paintWhileInterruptingJSTab = mPaintWhileInterruptingJSTab; |
345 | 0 | paintWhileInterruptingJSEpoch = mPaintWhileInterruptingJSEpoch; |
346 | 0 |
|
347 | 0 | mPaintWhileInterruptingJS = false; |
348 | 0 | } |
349 | 0 |
|
350 | 0 | // Don't paint from the interrupt callback when recording or replaying, as |
351 | 0 | // the interrupt callback is triggered non-deterministically. |
352 | 0 | if (paintWhileInterruptingJS && !recordreplay::IsRecordingOrReplaying()) { |
353 | 0 | RefPtr<TabChild> tabChild = TabChild::FindTabChild(paintWhileInterruptingJSTab); |
354 | 0 | if (tabChild) { |
355 | 0 | js::AutoAssertNoContentJS nojs(mContext); |
356 | 0 | tabChild->PaintWhileInterruptingJS(paintWhileInterruptingJSEpoch, |
357 | 0 | paintWhileInterruptingJSForce); |
358 | 0 | } |
359 | 0 | } |
360 | 0 | } |
361 | | |
362 | | void |
363 | | HangMonitorChild::AnnotateHang(BackgroundHangAnnotations& aAnnotations) |
364 | 0 | { |
365 | 0 | if (mPaintWhileInterruptingJSActive) { |
366 | 0 | aAnnotations.AddAnnotation(NS_LITERAL_STRING("PaintWhileInterruptingJS"), true); |
367 | 0 | } |
368 | 0 | } |
369 | | |
370 | | void |
371 | | HangMonitorChild::Shutdown() |
372 | 0 | { |
373 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
374 | 0 |
|
375 | 0 | MonitorAutoLock lock(mMonitor); |
376 | 0 | while (!mShutdownDone) { |
377 | 0 | mMonitor.Wait(); |
378 | 0 | } |
379 | 0 | } |
380 | | |
381 | | void |
382 | | HangMonitorChild::ShutdownOnThread() |
383 | 0 | { |
384 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
385 | 0 |
|
386 | 0 | MonitorAutoLock lock(mMonitor); |
387 | 0 | mShutdownDone = true; |
388 | 0 | mMonitor.Notify(); |
389 | 0 | } |
390 | | |
391 | | void |
392 | | HangMonitorChild::ActorDestroy(ActorDestroyReason aWhy) |
393 | 0 | { |
394 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
395 | 0 |
|
396 | 0 | mIPCOpen = false; |
397 | 0 |
|
398 | 0 | // We use a task here to ensure that IPDL is finished with this |
399 | 0 | // HangMonitorChild before it gets deleted on the main thread. |
400 | 0 | Dispatch(NewNonOwningRunnableMethod("HangMonitorChild::ShutdownOnThread", |
401 | 0 | this, |
402 | 0 | &HangMonitorChild::ShutdownOnThread)); |
403 | 0 | } |
404 | | |
405 | | mozilla::ipc::IPCResult |
406 | | HangMonitorChild::RecvTerminateScript(const bool& aTerminateGlobal) |
407 | 0 | { |
408 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
409 | 0 |
|
410 | 0 | MonitorAutoLock lock(mMonitor); |
411 | 0 | if (aTerminateGlobal) { |
412 | 0 | mTerminateGlobal = true; |
413 | 0 | } else { |
414 | 0 | mTerminateScript = true; |
415 | 0 | } |
416 | 0 | return IPC_OK(); |
417 | 0 | } |
418 | | |
419 | | mozilla::ipc::IPCResult |
420 | | HangMonitorChild::RecvBeginStartingDebugger() |
421 | 0 | { |
422 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
423 | 0 |
|
424 | 0 | MonitorAutoLock lock(mMonitor); |
425 | 0 | mStartDebugger = true; |
426 | 0 | return IPC_OK(); |
427 | 0 | } |
428 | | |
429 | | mozilla::ipc::IPCResult |
430 | | HangMonitorChild::RecvEndStartingDebugger() |
431 | 0 | { |
432 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
433 | 0 |
|
434 | 0 | MonitorAutoLock lock(mMonitor); |
435 | 0 | mFinishedStartingDebugger = true; |
436 | 0 | return IPC_OK(); |
437 | 0 | } |
438 | | |
439 | | mozilla::ipc::IPCResult |
440 | | HangMonitorChild::RecvPaintWhileInterruptingJS(const TabId& aTabId, |
441 | | const bool& aForceRepaint, |
442 | | const LayersObserverEpoch& aEpoch) |
443 | 0 | { |
444 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
445 | 0 |
|
446 | 0 | { |
447 | 0 | MonitorAutoLock lock(mMonitor); |
448 | 0 | MaybeStartPaintWhileInterruptingJS(); |
449 | 0 | mPaintWhileInterruptingJS = true; |
450 | 0 | mPaintWhileInterruptingJSForce = aForceRepaint; |
451 | 0 | mPaintWhileInterruptingJSTab = aTabId; |
452 | 0 | mPaintWhileInterruptingJSEpoch = aEpoch; |
453 | 0 | } |
454 | 0 |
|
455 | 0 | JS_RequestInterruptCallback(mContext); |
456 | 0 |
|
457 | 0 | return IPC_OK(); |
458 | 0 | } |
459 | | |
460 | | void |
461 | | HangMonitorChild::MaybeStartPaintWhileInterruptingJS() |
462 | 0 | { |
463 | 0 | mPaintWhileInterruptingJSActive = true; |
464 | 0 | } |
465 | | |
466 | | void |
467 | | HangMonitorChild::ClearPaintWhileInterruptingJS(const LayersObserverEpoch& aEpoch) |
468 | 0 | { |
469 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
470 | 0 | MOZ_RELEASE_ASSERT(XRE_IsContentProcess()); |
471 | 0 | mPaintWhileInterruptingJSActive = false; |
472 | 0 | } |
473 | | |
474 | | void |
475 | | HangMonitorChild::Bind(Endpoint<PProcessHangMonitorChild>&& aEndpoint) |
476 | 0 | { |
477 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
478 | 0 |
|
479 | 0 | MOZ_ASSERT(!sInstance); |
480 | 0 | sInstance = this; |
481 | 0 |
|
482 | 0 | DebugOnly<bool> ok = aEndpoint.Bind(this); |
483 | 0 | MOZ_ASSERT(ok); |
484 | 0 | } |
485 | | |
486 | | void |
487 | | HangMonitorChild::NotifySlowScriptAsync(TabId aTabId, |
488 | | const nsCString& aFileName, |
489 | | const nsString& aAddonId) |
490 | 0 | { |
491 | 0 | if (mIPCOpen) { |
492 | 0 | Unused << SendHangEvidence(SlowScriptData(aTabId, aFileName, aAddonId)); |
493 | 0 | } |
494 | 0 | } |
495 | | |
496 | | HangMonitorChild::SlowScriptAction |
497 | | HangMonitorChild::NotifySlowScript(nsITabChild* aTabChild, |
498 | | const char* aFileName, |
499 | | const nsString& aAddonId) |
500 | 0 | { |
501 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
502 | 0 |
|
503 | 0 | mSentReport = true; |
504 | 0 |
|
505 | 0 | { |
506 | 0 | MonitorAutoLock lock(mMonitor); |
507 | 0 |
|
508 | 0 | if (mTerminateScript) { |
509 | 0 | mTerminateScript = false; |
510 | 0 | return SlowScriptAction::Terminate; |
511 | 0 | } |
512 | 0 | |
513 | 0 | if (mTerminateGlobal) { |
514 | 0 | mTerminateGlobal = false; |
515 | 0 | return SlowScriptAction::TerminateGlobal; |
516 | 0 | } |
517 | 0 | |
518 | 0 | if (mStartDebugger) { |
519 | 0 | mStartDebugger = false; |
520 | 0 | return SlowScriptAction::StartDebugger; |
521 | 0 | } |
522 | 0 | } |
523 | 0 | |
524 | 0 | TabId id; |
525 | 0 | if (aTabChild) { |
526 | 0 | RefPtr<TabChild> tabChild = static_cast<TabChild*>(aTabChild); |
527 | 0 | id = tabChild->GetTabId(); |
528 | 0 | } |
529 | 0 | nsAutoCString filename(aFileName); |
530 | 0 |
|
531 | 0 | Dispatch(NewNonOwningRunnableMethod<TabId, nsCString, nsString>( |
532 | 0 | "HangMonitorChild::NotifySlowScriptAsync", |
533 | 0 | this, |
534 | 0 | &HangMonitorChild::NotifySlowScriptAsync, |
535 | 0 | id, |
536 | 0 | filename, aAddonId)); |
537 | 0 | return SlowScriptAction::Continue; |
538 | 0 | } |
539 | | |
540 | | bool |
541 | | HangMonitorChild::IsDebuggerStartupComplete() |
542 | 0 | { |
543 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
544 | 0 |
|
545 | 0 | MonitorAutoLock lock(mMonitor); |
546 | 0 |
|
547 | 0 | if (mFinishedStartingDebugger) { |
548 | 0 | mFinishedStartingDebugger = false; |
549 | 0 | return true; |
550 | 0 | } |
551 | 0 | |
552 | 0 | return false; |
553 | 0 | } |
554 | | |
555 | | void |
556 | | HangMonitorChild::NotifyPluginHang(uint32_t aPluginId) |
557 | 0 | { |
558 | 0 | // main thread in the child |
559 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
560 | 0 |
|
561 | 0 | mSentReport = true; |
562 | 0 |
|
563 | 0 | // bounce to background thread |
564 | 0 | Dispatch(NewNonOwningRunnableMethod<uint32_t>( |
565 | 0 | "HangMonitorChild::NotifyPluginHangAsync", |
566 | 0 | this, |
567 | 0 | &HangMonitorChild::NotifyPluginHangAsync, |
568 | 0 | aPluginId)); |
569 | 0 | } |
570 | | |
571 | | void |
572 | | HangMonitorChild::NotifyPluginHangAsync(uint32_t aPluginId) |
573 | 0 | { |
574 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
575 | 0 |
|
576 | 0 | // bounce back to parent on background thread |
577 | 0 | if (mIPCOpen) { |
578 | 0 | Unused << SendHangEvidence(PluginHangData(aPluginId, |
579 | 0 | base::GetCurrentProcId())); |
580 | 0 | } |
581 | 0 | } |
582 | | |
583 | | void |
584 | | HangMonitorChild::ClearHang() |
585 | 0 | { |
586 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
587 | 0 |
|
588 | 0 | if (mSentReport) { |
589 | 0 | // bounce to background thread |
590 | 0 | Dispatch(NewNonOwningRunnableMethod("HangMonitorChild::ClearHangAsync", |
591 | 0 | this, |
592 | 0 | &HangMonitorChild::ClearHangAsync)); |
593 | 0 |
|
594 | 0 | MonitorAutoLock lock(mMonitor); |
595 | 0 | mSentReport = false; |
596 | 0 | mTerminateScript = false; |
597 | 0 | mTerminateGlobal = false; |
598 | 0 | mStartDebugger = false; |
599 | 0 | mFinishedStartingDebugger = false; |
600 | 0 | } |
601 | 0 | } |
602 | | |
603 | | void |
604 | | HangMonitorChild::ClearHangAsync() |
605 | 0 | { |
606 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
607 | 0 |
|
608 | 0 | // bounce back to parent on background thread |
609 | 0 | if (mIPCOpen) { |
610 | 0 | Unused << SendClearHang(); |
611 | 0 | } |
612 | 0 | } |
613 | | |
614 | | /* HangMonitorParent implementation */ |
615 | | |
616 | | HangMonitorParent::HangMonitorParent(ProcessHangMonitor* aMonitor) |
617 | | : mHangMonitor(aMonitor), |
618 | | mIPCOpen(true), |
619 | | mMonitor("HangMonitorParent lock"), |
620 | | mShutdownDone(false), |
621 | | mBrowserCrashDumpHashLock("mBrowserCrashDumpIds lock"), |
622 | | mMainThreadTaskFactory(this) |
623 | 0 | { |
624 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
625 | 0 | mReportHangs = mozilla::Preferences::GetBool("dom.ipc.reportProcessHangs", false); |
626 | 0 |
|
627 | 0 | static bool sInited = false; |
628 | 0 | if (!sInited) { |
629 | 0 | sInited = true; |
630 | 0 | Preferences::AddBoolVarCache(&sShouldPaintWhileInterruptingJS, |
631 | 0 | "browser.tabs.remote.force-paint", true); |
632 | 0 | } |
633 | 0 | } |
634 | | |
635 | | HangMonitorParent::~HangMonitorParent() |
636 | 0 | { |
637 | 0 | MutexAutoLock lock(mBrowserCrashDumpHashLock); |
638 | 0 |
|
639 | 0 | for (auto iter = mBrowserCrashDumpIds.Iter(); !iter.Done(); iter.Next()) { |
640 | 0 | nsString crashId = iter.UserData(); |
641 | 0 | if (!crashId.IsEmpty()) { |
642 | 0 | CrashReporter::DeleteMinidumpFilesForID(crashId); |
643 | 0 | } |
644 | 0 | } |
645 | 0 | } |
646 | | |
647 | | void |
648 | | HangMonitorParent::Shutdown() |
649 | 0 | { |
650 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
651 | 0 |
|
652 | 0 | MonitorAutoLock lock(mMonitor); |
653 | 0 |
|
654 | 0 | if (mProcess) { |
655 | 0 | mProcess->Clear(); |
656 | 0 | mProcess = nullptr; |
657 | 0 | } |
658 | 0 |
|
659 | 0 | Dispatch(NewNonOwningRunnableMethod("HangMonitorParent::ShutdownOnThread", |
660 | 0 | this, |
661 | 0 | &HangMonitorParent::ShutdownOnThread)); |
662 | 0 |
|
663 | 0 | while (!mShutdownDone) { |
664 | 0 | mMonitor.Wait(); |
665 | 0 | } |
666 | 0 | } |
667 | | |
668 | | void |
669 | | HangMonitorParent::ShutdownOnThread() |
670 | 0 | { |
671 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
672 | 0 |
|
673 | 0 | // mIPCOpen is only written from this thread, so need need to take the lock |
674 | 0 | // here. We'd be shooting ourselves in the foot, because ActorDestroy takes |
675 | 0 | // it. |
676 | 0 | if (mIPCOpen) { |
677 | 0 | Close(); |
678 | 0 | } |
679 | 0 |
|
680 | 0 | MonitorAutoLock lock(mMonitor); |
681 | 0 | mShutdownDone = true; |
682 | 0 | mMonitor.Notify(); |
683 | 0 | } |
684 | | |
685 | | void |
686 | | HangMonitorParent::PaintWhileInterruptingJS(dom::TabParent* aTab, |
687 | | bool aForceRepaint, |
688 | | const LayersObserverEpoch& aEpoch) |
689 | 0 | { |
690 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
691 | 0 | if (sShouldPaintWhileInterruptingJS) { |
692 | 0 | TabId id = aTab->GetTabId(); |
693 | 0 | Dispatch(NewNonOwningRunnableMethod<TabId, bool, LayersObserverEpoch>( |
694 | 0 | "HangMonitorParent::PaintWhileInterruptingJSOnThread", |
695 | 0 | this, |
696 | 0 | &HangMonitorParent::PaintWhileInterruptingJSOnThread, |
697 | 0 | id, |
698 | 0 | aForceRepaint, |
699 | 0 | aEpoch)); |
700 | 0 | } |
701 | 0 | } |
702 | | |
703 | | void |
704 | | HangMonitorParent::PaintWhileInterruptingJSOnThread(TabId aTabId, |
705 | | bool aForceRepaint, |
706 | | const LayersObserverEpoch& aEpoch) |
707 | 0 | { |
708 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
709 | 0 |
|
710 | 0 | if (mIPCOpen) { |
711 | 0 | Unused << SendPaintWhileInterruptingJS(aTabId, aForceRepaint, aEpoch); |
712 | 0 | } |
713 | 0 | } |
714 | | |
715 | | void |
716 | | HangMonitorParent::ActorDestroy(ActorDestroyReason aWhy) |
717 | 0 | { |
718 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
719 | 0 | mIPCOpen = false; |
720 | 0 | } |
721 | | |
722 | | void |
723 | | HangMonitorParent::Bind(Endpoint<PProcessHangMonitorParent>&& aEndpoint) |
724 | 0 | { |
725 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
726 | 0 |
|
727 | 0 | DebugOnly<bool> ok = aEndpoint.Bind(this); |
728 | 0 | MOZ_ASSERT(ok); |
729 | 0 | } |
730 | | |
731 | | void |
732 | | HangMonitorParent::SendHangNotification(const HangData& aHangData, |
733 | | const nsString& aBrowserDumpId, |
734 | | bool aTakeMinidump) |
735 | 0 | { |
736 | 0 | // chrome process, main thread |
737 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
738 | 0 |
|
739 | 0 | nsString dumpId; |
740 | 0 | if ((aHangData.type() == HangData::TPluginHangData) && aTakeMinidump) { |
741 | 0 | // We've been handed a partial minidump; complete it with plugin and |
742 | 0 | // content process dumps. |
743 | 0 | const PluginHangData& phd = aHangData.get_PluginHangData(); |
744 | 0 |
|
745 | 0 | plugins::TakeFullMinidump(phd.pluginId(), phd.contentProcessId(), |
746 | 0 | aBrowserDumpId, dumpId); |
747 | 0 | UpdateMinidump(phd.pluginId(), dumpId); |
748 | 0 | } else { |
749 | 0 | // We already have a full minidump; go ahead and use it. |
750 | 0 | dumpId = aBrowserDumpId; |
751 | 0 | } |
752 | 0 |
|
753 | 0 | mProcess->SetHangData(aHangData, dumpId); |
754 | 0 |
|
755 | 0 | nsCOMPtr<nsIObserverService> observerService = |
756 | 0 | mozilla::services::GetObserverService(); |
757 | 0 | observerService->NotifyObservers(mProcess, "process-hang-report", nullptr); |
758 | 0 | } |
759 | | |
760 | | void |
761 | | HangMonitorParent::ClearHangNotification() |
762 | 0 | { |
763 | 0 | // chrome process, main thread |
764 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
765 | 0 | mProcess->ClearHang(); |
766 | 0 |
|
767 | 0 | nsCOMPtr<nsIObserverService> observerService = |
768 | 0 | mozilla::services::GetObserverService(); |
769 | 0 | observerService->NotifyObservers(mProcess, "clear-hang-report", nullptr); |
770 | 0 | } |
771 | | |
772 | | // Take a minidump of the browser process if one wasn't already taken for the |
773 | | // plugin that caused the hang. Return false if a dump was already available or |
774 | | // true if new one has been taken. |
775 | | bool |
776 | | HangMonitorParent::TakeBrowserMinidump(const PluginHangData& aPhd, |
777 | | nsString& aCrashId) |
778 | 0 | { |
779 | 0 | MutexAutoLock lock(mBrowserCrashDumpHashLock); |
780 | 0 | if (!mBrowserCrashDumpIds.Get(aPhd.pluginId(), &aCrashId)) { |
781 | 0 | nsCOMPtr<nsIFile> browserDump; |
782 | 0 | if (CrashReporter::TakeMinidump(getter_AddRefs(browserDump), true)) { |
783 | 0 | if (!CrashReporter::GetIDFromMinidump(browserDump, aCrashId) |
784 | 0 | || aCrashId.IsEmpty()) { |
785 | 0 | browserDump->Remove(false); |
786 | 0 | NS_WARNING("Failed to generate timely browser stack, " |
787 | 0 | "this is bad for plugin hang analysis!"); |
788 | 0 | } else { |
789 | 0 | mBrowserCrashDumpIds.Put(aPhd.pluginId(), aCrashId); |
790 | 0 | return true; |
791 | 0 | } |
792 | 0 | } |
793 | 0 | } |
794 | 0 | |
795 | 0 | return false; |
796 | 0 | } |
797 | | |
798 | | mozilla::ipc::IPCResult |
799 | | HangMonitorParent::RecvHangEvidence(const HangData& aHangData) |
800 | 0 | { |
801 | 0 | // chrome process, background thread |
802 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
803 | 0 |
|
804 | 0 | if (!mReportHangs) { |
805 | 0 | return IPC_OK(); |
806 | 0 | } |
807 | 0 |
|
808 | | #ifdef XP_WIN |
809 | | // Don't report hangs if we're debugging the process. You can comment this |
810 | | // line out for testing purposes. |
811 | | if (IsDebuggerPresent()) { |
812 | | return IPC_OK(); |
813 | | } |
814 | | #endif |
815 | | |
816 | 0 | // Before we wake up the browser main thread we want to take a |
817 | 0 | // browser minidump. |
818 | 0 | nsAutoString crashId; |
819 | 0 | bool takeMinidump = false; |
820 | 0 | if (aHangData.type() == HangData::TPluginHangData) { |
821 | 0 | takeMinidump = TakeBrowserMinidump(aHangData.get_PluginHangData(), crashId); |
822 | 0 | } |
823 | 0 |
|
824 | 0 | mHangMonitor->InitiateCPOWTimeout(); |
825 | 0 |
|
826 | 0 | MonitorAutoLock lock(mMonitor); |
827 | 0 |
|
828 | 0 | NS_DispatchToMainThread( |
829 | 0 | mMainThreadTaskFactory.NewRunnableMethod( |
830 | 0 | &HangMonitorParent::SendHangNotification, aHangData, crashId, |
831 | 0 | takeMinidump)); |
832 | 0 |
|
833 | 0 | return IPC_OK(); |
834 | 0 | } |
835 | | |
836 | | mozilla::ipc::IPCResult |
837 | | HangMonitorParent::RecvClearHang() |
838 | 0 | { |
839 | 0 | // chrome process, background thread |
840 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
841 | 0 |
|
842 | 0 | if (!mReportHangs) { |
843 | 0 | return IPC_OK(); |
844 | 0 | } |
845 | 0 |
|
846 | 0 | mHangMonitor->InitiateCPOWTimeout(); |
847 | 0 |
|
848 | 0 | MonitorAutoLock lock(mMonitor); |
849 | 0 |
|
850 | 0 | NS_DispatchToMainThread( |
851 | 0 | mMainThreadTaskFactory.NewRunnableMethod( |
852 | 0 | &HangMonitorParent::ClearHangNotification)); |
853 | 0 |
|
854 | 0 | return IPC_OK(); |
855 | 0 | } |
856 | | |
857 | | void |
858 | | HangMonitorParent::TerminateScript(bool aTerminateGlobal) |
859 | 0 | { |
860 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
861 | 0 |
|
862 | 0 | if (mIPCOpen) { |
863 | 0 | Unused << SendTerminateScript(aTerminateGlobal); |
864 | 0 | } |
865 | 0 | } |
866 | | |
867 | | void |
868 | | HangMonitorParent::BeginStartingDebugger() |
869 | 0 | { |
870 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
871 | 0 |
|
872 | 0 | if (mIPCOpen) { |
873 | 0 | Unused << SendBeginStartingDebugger(); |
874 | 0 | } |
875 | 0 | } |
876 | | |
877 | | void |
878 | | HangMonitorParent::EndStartingDebugger() |
879 | 0 | { |
880 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
881 | 0 |
|
882 | 0 | if (mIPCOpen) { |
883 | 0 | Unused << SendEndStartingDebugger(); |
884 | 0 | } |
885 | 0 | } |
886 | | |
887 | | void |
888 | | HangMonitorParent::CleanupPluginHang(uint32_t aPluginId, bool aRemoveFiles) |
889 | 0 | { |
890 | 0 | MutexAutoLock lock(mBrowserCrashDumpHashLock); |
891 | 0 | nsAutoString crashId; |
892 | 0 | if (!mBrowserCrashDumpIds.Get(aPluginId, &crashId)) { |
893 | 0 | return; |
894 | 0 | } |
895 | 0 | mBrowserCrashDumpIds.Remove(aPluginId); |
896 | 0 |
|
897 | 0 | if (aRemoveFiles && !crashId.IsEmpty()) { |
898 | 0 | CrashReporter::DeleteMinidumpFilesForID(crashId); |
899 | 0 | } |
900 | 0 | } |
901 | | |
902 | | void |
903 | | HangMonitorParent::UpdateMinidump(uint32_t aPluginId, const nsString& aDumpId) |
904 | 0 | { |
905 | 0 | if (aDumpId.IsEmpty()) { |
906 | 0 | return; |
907 | 0 | } |
908 | 0 | |
909 | 0 | MutexAutoLock lock(mBrowserCrashDumpHashLock); |
910 | 0 | mBrowserCrashDumpIds.Put(aPluginId, aDumpId); |
911 | 0 | } |
912 | | |
913 | | /* HangMonitoredProcess implementation */ |
914 | | |
915 | | NS_IMPL_ISUPPORTS(HangMonitoredProcess, nsIHangReport) |
916 | | |
917 | | NS_IMETHODIMP |
918 | | HangMonitoredProcess::GetHangType(uint32_t* aHangType) |
919 | 0 | { |
920 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
921 | 0 | switch (mHangData.type()) { |
922 | 0 | case HangData::TSlowScriptData: |
923 | 0 | *aHangType = SLOW_SCRIPT; |
924 | 0 | break; |
925 | 0 | case HangData::TPluginHangData: |
926 | 0 | *aHangType = PLUGIN_HANG; |
927 | 0 | break; |
928 | 0 | default: |
929 | 0 | MOZ_ASSERT_UNREACHABLE("Unexpected HangData type"); |
930 | 0 | return NS_ERROR_UNEXPECTED; |
931 | 0 | } |
932 | 0 |
|
933 | 0 | return NS_OK; |
934 | 0 | } |
935 | | |
936 | | NS_IMETHODIMP |
937 | | HangMonitoredProcess::GetScriptBrowser(Element** aBrowser) |
938 | 0 | { |
939 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
940 | 0 | if (mHangData.type() != HangData::TSlowScriptData) { |
941 | 0 | return NS_ERROR_NOT_AVAILABLE; |
942 | 0 | } |
943 | 0 | |
944 | 0 | TabId tabId = mHangData.get_SlowScriptData().tabId(); |
945 | 0 | if (!mContentParent) { |
946 | 0 | return NS_ERROR_NOT_AVAILABLE; |
947 | 0 | } |
948 | 0 | |
949 | 0 | nsTArray<PBrowserParent*> tabs; |
950 | 0 | mContentParent->ManagedPBrowserParent(tabs); |
951 | 0 | for (size_t i = 0; i < tabs.Length(); i++) { |
952 | 0 | TabParent* tp = TabParent::GetFrom(tabs[i]); |
953 | 0 | if (tp->GetTabId() == tabId) { |
954 | 0 | RefPtr<Element> node = tp->GetOwnerElement(); |
955 | 0 | node.forget(aBrowser); |
956 | 0 | return NS_OK; |
957 | 0 | } |
958 | 0 | } |
959 | 0 |
|
960 | 0 | *aBrowser = nullptr; |
961 | 0 | return NS_OK; |
962 | 0 | } |
963 | | |
964 | | NS_IMETHODIMP |
965 | | HangMonitoredProcess::GetScriptFileName(nsACString& aFileName) |
966 | 0 | { |
967 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
968 | 0 | if (mHangData.type() != HangData::TSlowScriptData) { |
969 | 0 | return NS_ERROR_NOT_AVAILABLE; |
970 | 0 | } |
971 | 0 | |
972 | 0 | aFileName = mHangData.get_SlowScriptData().filename(); |
973 | 0 | return NS_OK; |
974 | 0 | } |
975 | | |
976 | | NS_IMETHODIMP |
977 | | HangMonitoredProcess::GetAddonId(nsAString& aAddonId) |
978 | 0 | { |
979 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
980 | 0 | if (mHangData.type() != HangData::TSlowScriptData) { |
981 | 0 | return NS_ERROR_NOT_AVAILABLE; |
982 | 0 | } |
983 | 0 | |
984 | 0 | aAddonId = mHangData.get_SlowScriptData().addonId(); |
985 | 0 | return NS_OK; |
986 | 0 | } |
987 | | |
988 | | NS_IMETHODIMP |
989 | | HangMonitoredProcess::GetPluginName(nsACString& aPluginName) |
990 | 0 | { |
991 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
992 | 0 | if (mHangData.type() != HangData::TPluginHangData) { |
993 | 0 | return NS_ERROR_NOT_AVAILABLE; |
994 | 0 | } |
995 | 0 | |
996 | 0 | uint32_t id = mHangData.get_PluginHangData().pluginId(); |
997 | 0 |
|
998 | 0 | RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); |
999 | 0 | nsPluginTag* tag = host->PluginWithId(id); |
1000 | 0 | if (!tag) { |
1001 | 0 | return NS_ERROR_UNEXPECTED; |
1002 | 0 | } |
1003 | 0 | |
1004 | 0 | aPluginName = tag->Name(); |
1005 | 0 | return NS_OK; |
1006 | 0 | } |
1007 | | |
1008 | | NS_IMETHODIMP |
1009 | | HangMonitoredProcess::TerminateScript() |
1010 | 0 | { |
1011 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1012 | 0 | if (mHangData.type() != HangData::TSlowScriptData) { |
1013 | 0 | return NS_ERROR_UNEXPECTED; |
1014 | 0 | } |
1015 | 0 | |
1016 | 0 | if (!mActor) { |
1017 | 0 | return NS_ERROR_UNEXPECTED; |
1018 | 0 | } |
1019 | 0 | |
1020 | 0 | ProcessHangMonitor::Get()->Dispatch( |
1021 | 0 | NewNonOwningRunnableMethod<bool>("HangMonitorParent::TerminateScript", |
1022 | 0 | mActor, |
1023 | 0 | &HangMonitorParent::TerminateScript, false)); |
1024 | 0 | return NS_OK; |
1025 | 0 | } |
1026 | | |
1027 | | NS_IMETHODIMP |
1028 | | HangMonitoredProcess::TerminateGlobal() |
1029 | 0 | { |
1030 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1031 | 0 | if (mHangData.type() != HangData::TSlowScriptData) { |
1032 | 0 | return NS_ERROR_UNEXPECTED; |
1033 | 0 | } |
1034 | 0 | |
1035 | 0 | if (!mActor) { |
1036 | 0 | return NS_ERROR_UNEXPECTED; |
1037 | 0 | } |
1038 | 0 | |
1039 | 0 | ProcessHangMonitor::Get()->Dispatch( |
1040 | 0 | NewNonOwningRunnableMethod<bool>("HangMonitorParent::TerminateScript", |
1041 | 0 | mActor, |
1042 | 0 | &HangMonitorParent::TerminateScript, true)); |
1043 | 0 | return NS_OK; |
1044 | 0 | } |
1045 | | |
1046 | | NS_IMETHODIMP |
1047 | | HangMonitoredProcess::BeginStartingDebugger() |
1048 | 0 | { |
1049 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1050 | 0 | if (mHangData.type() != HangData::TSlowScriptData) { |
1051 | 0 | return NS_ERROR_UNEXPECTED; |
1052 | 0 | } |
1053 | 0 | |
1054 | 0 | if (!mActor) { |
1055 | 0 | return NS_ERROR_UNEXPECTED; |
1056 | 0 | } |
1057 | 0 | |
1058 | 0 | ProcessHangMonitor::Get()->Dispatch( |
1059 | 0 | NewNonOwningRunnableMethod("HangMonitorParent::BeginStartingDebugger", |
1060 | 0 | mActor, |
1061 | 0 | &HangMonitorParent::BeginStartingDebugger)); |
1062 | 0 | return NS_OK; |
1063 | 0 | } |
1064 | | |
1065 | | NS_IMETHODIMP |
1066 | | HangMonitoredProcess::EndStartingDebugger() |
1067 | 0 | { |
1068 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1069 | 0 | if (mHangData.type() != HangData::TSlowScriptData) { |
1070 | 0 | return NS_ERROR_UNEXPECTED; |
1071 | 0 | } |
1072 | 0 | |
1073 | 0 | if (!mActor) { |
1074 | 0 | return NS_ERROR_UNEXPECTED; |
1075 | 0 | } |
1076 | 0 | |
1077 | 0 | ProcessHangMonitor::Get()->Dispatch( |
1078 | 0 | NewNonOwningRunnableMethod("HangMonitorParent::EndStartingDebugger", |
1079 | 0 | mActor, |
1080 | 0 | &HangMonitorParent::EndStartingDebugger)); |
1081 | 0 | return NS_OK; |
1082 | 0 | } |
1083 | | |
1084 | | NS_IMETHODIMP |
1085 | | HangMonitoredProcess::TerminatePlugin() |
1086 | 0 | { |
1087 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1088 | 0 | if (mHangData.type() != HangData::TPluginHangData) { |
1089 | 0 | return NS_ERROR_UNEXPECTED; |
1090 | 0 | } |
1091 | 0 | |
1092 | 0 | // Use the multi-process crash report generated earlier. |
1093 | 0 | uint32_t id = mHangData.get_PluginHangData().pluginId(); |
1094 | 0 | base::ProcessId contentPid = mHangData.get_PluginHangData().contentProcessId(); |
1095 | 0 | plugins::TerminatePlugin(id, contentPid, NS_LITERAL_CSTRING("HangMonitor"), |
1096 | 0 | mDumpId); |
1097 | 0 |
|
1098 | 0 | if (mActor) { |
1099 | 0 | mActor->CleanupPluginHang(id, false); |
1100 | 0 | } |
1101 | 0 | return NS_OK; |
1102 | 0 | } |
1103 | | |
1104 | | NS_IMETHODIMP |
1105 | | HangMonitoredProcess::IsReportForBrowser(nsFrameLoader* aFrameLoader, bool* aResult) |
1106 | 0 | { |
1107 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1108 | 0 |
|
1109 | 0 | if (!mActor) { |
1110 | 0 | *aResult = false; |
1111 | 0 | return NS_OK; |
1112 | 0 | } |
1113 | 0 | |
1114 | 0 | NS_ENSURE_STATE(aFrameLoader); |
1115 | 0 |
|
1116 | 0 | TabParent* tp = TabParent::GetFrom(aFrameLoader); |
1117 | 0 | if (!tp) { |
1118 | 0 | *aResult = false; |
1119 | 0 | return NS_OK; |
1120 | 0 | } |
1121 | 0 | |
1122 | 0 | *aResult = mContentParent == tp->Manager(); |
1123 | 0 | return NS_OK; |
1124 | 0 | } |
1125 | | |
1126 | | NS_IMETHODIMP |
1127 | | HangMonitoredProcess::UserCanceled() |
1128 | 0 | { |
1129 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1130 | 0 | if (mHangData.type() != HangData::TPluginHangData) { |
1131 | 0 | return NS_OK; |
1132 | 0 | } |
1133 | 0 | |
1134 | 0 | if (mActor) { |
1135 | 0 | uint32_t id = mHangData.get_PluginHangData().pluginId(); |
1136 | 0 | mActor->CleanupPluginHang(id, true); |
1137 | 0 | } |
1138 | 0 | return NS_OK; |
1139 | 0 | } |
1140 | | |
1141 | | static bool |
1142 | | InterruptCallback(JSContext* cx) |
1143 | 0 | { |
1144 | 0 | if (HangMonitorChild* child = HangMonitorChild::Get()) { |
1145 | 0 | child->InterruptCallback(); |
1146 | 0 | } |
1147 | 0 |
|
1148 | 0 | return true; |
1149 | 0 | } |
1150 | | |
1151 | | ProcessHangMonitor* ProcessHangMonitor::sInstance; |
1152 | | |
1153 | | ProcessHangMonitor::ProcessHangMonitor() |
1154 | | : mCPOWTimeout(false) |
1155 | 0 | { |
1156 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1157 | 0 |
|
1158 | 0 | if (XRE_IsContentProcess()) { |
1159 | 0 | nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
1160 | 0 | obs->AddObserver(this, "xpcom-shutdown", false); |
1161 | 0 | } |
1162 | 0 |
|
1163 | 0 | if (NS_FAILED(NS_NewNamedThread("ProcessHangMon", getter_AddRefs(mThread)))) { |
1164 | 0 | mThread = nullptr; |
1165 | 0 | } |
1166 | 0 | } |
1167 | | |
1168 | | ProcessHangMonitor::~ProcessHangMonitor() |
1169 | 0 | { |
1170 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1171 | 0 |
|
1172 | 0 | MOZ_ASSERT(sInstance == this); |
1173 | 0 | sInstance = nullptr; |
1174 | 0 |
|
1175 | 0 | mThread->Shutdown(); |
1176 | 0 | mThread = nullptr; |
1177 | 0 | } |
1178 | | |
1179 | | ProcessHangMonitor* |
1180 | | ProcessHangMonitor::GetOrCreate() |
1181 | 0 | { |
1182 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1183 | 0 | if (!sInstance) { |
1184 | 0 | sInstance = new ProcessHangMonitor(); |
1185 | 0 | } |
1186 | 0 | return sInstance; |
1187 | 0 | } |
1188 | | |
1189 | | NS_IMPL_ISUPPORTS(ProcessHangMonitor, nsIObserver) |
1190 | | |
1191 | | NS_IMETHODIMP |
1192 | | ProcessHangMonitor::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) |
1193 | 0 | { |
1194 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1195 | 0 | if (!strcmp(aTopic, "xpcom-shutdown")) { |
1196 | 0 | if (HangMonitorChild* child = HangMonitorChild::Get()) { |
1197 | 0 | child->Shutdown(); |
1198 | 0 | delete child; |
1199 | 0 | } |
1200 | 0 |
|
1201 | 0 | nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
1202 | 0 | if (obs) { |
1203 | 0 | obs->RemoveObserver(this, "xpcom-shutdown"); |
1204 | 0 | } |
1205 | 0 | } |
1206 | 0 | return NS_OK; |
1207 | 0 | } |
1208 | | |
1209 | | ProcessHangMonitor::SlowScriptAction |
1210 | | ProcessHangMonitor::NotifySlowScript(nsITabChild* aTabChild, |
1211 | | const char* aFileName, |
1212 | | const nsString& aAddonId) |
1213 | 0 | { |
1214 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1215 | 0 | return HangMonitorChild::Get()->NotifySlowScript(aTabChild, aFileName, aAddonId); |
1216 | 0 | } |
1217 | | |
1218 | | bool |
1219 | | ProcessHangMonitor::IsDebuggerStartupComplete() |
1220 | 0 | { |
1221 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1222 | 0 | return HangMonitorChild::Get()->IsDebuggerStartupComplete(); |
1223 | 0 | } |
1224 | | |
1225 | | bool |
1226 | | ProcessHangMonitor::ShouldTimeOutCPOWs() |
1227 | 0 | { |
1228 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1229 | 0 |
|
1230 | 0 | if (mCPOWTimeout) { |
1231 | 0 | mCPOWTimeout = false; |
1232 | 0 | return true; |
1233 | 0 | } |
1234 | 0 | return false; |
1235 | 0 | } |
1236 | | |
1237 | | void |
1238 | | ProcessHangMonitor::InitiateCPOWTimeout() |
1239 | 0 | { |
1240 | 0 | MOZ_RELEASE_ASSERT(IsOnThread()); |
1241 | 0 | mCPOWTimeout = true; |
1242 | 0 | } |
1243 | | |
1244 | | void |
1245 | | ProcessHangMonitor::NotifyPluginHang(uint32_t aPluginId) |
1246 | 0 | { |
1247 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1248 | 0 | return HangMonitorChild::Get()->NotifyPluginHang(aPluginId); |
1249 | 0 | } |
1250 | | |
1251 | | static PProcessHangMonitorParent* |
1252 | | CreateHangMonitorParent(ContentParent* aContentParent, |
1253 | | Endpoint<PProcessHangMonitorParent>&& aEndpoint) |
1254 | 0 | { |
1255 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1256 | 0 |
|
1257 | 0 | ProcessHangMonitor* monitor = ProcessHangMonitor::GetOrCreate(); |
1258 | 0 | auto* parent = new HangMonitorParent(monitor); |
1259 | 0 |
|
1260 | 0 | auto* process = new HangMonitoredProcess(parent, aContentParent); |
1261 | 0 | parent->SetProcess(process); |
1262 | 0 |
|
1263 | 0 | monitor->Dispatch( |
1264 | 0 | NewNonOwningRunnableMethod<Endpoint<PProcessHangMonitorParent>&&>( |
1265 | 0 | "HangMonitorParent::Bind", |
1266 | 0 | parent, |
1267 | 0 | &HangMonitorParent::Bind, |
1268 | 0 | std::move(aEndpoint))); |
1269 | 0 |
|
1270 | 0 | return parent; |
1271 | 0 | } |
1272 | | |
1273 | | void |
1274 | | mozilla::CreateHangMonitorChild(Endpoint<PProcessHangMonitorChild>&& aEndpoint) |
1275 | 0 | { |
1276 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1277 | 0 |
|
1278 | 0 | JSContext* cx = danger::GetJSContext(); |
1279 | 0 | JS_AddInterruptCallback(cx, InterruptCallback); |
1280 | 0 |
|
1281 | 0 | ProcessHangMonitor* monitor = ProcessHangMonitor::GetOrCreate(); |
1282 | 0 | auto* child = new HangMonitorChild(monitor); |
1283 | 0 |
|
1284 | 0 | monitor->Dispatch( |
1285 | 0 | NewNonOwningRunnableMethod<Endpoint<PProcessHangMonitorChild>&&>( |
1286 | 0 | "HangMonitorChild::Bind", |
1287 | 0 | child, |
1288 | 0 | &HangMonitorChild::Bind, |
1289 | 0 | std::move(aEndpoint))); |
1290 | 0 | } |
1291 | | |
1292 | | void |
1293 | | ProcessHangMonitor::Dispatch(already_AddRefed<nsIRunnable> aRunnable) |
1294 | 0 | { |
1295 | 0 | mThread->Dispatch(std::move(aRunnable), nsIEventTarget::NS_DISPATCH_NORMAL); |
1296 | 0 | } |
1297 | | |
1298 | | bool |
1299 | | ProcessHangMonitor::IsOnThread() |
1300 | 0 | { |
1301 | 0 | bool on; |
1302 | 0 | return NS_SUCCEEDED(mThread->IsOnCurrentThread(&on)) && on; |
1303 | 0 | } |
1304 | | |
1305 | | /* static */ PProcessHangMonitorParent* |
1306 | | ProcessHangMonitor::AddProcess(ContentParent* aContentParent) |
1307 | 0 | { |
1308 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1309 | 0 |
|
1310 | 0 | if (!mozilla::Preferences::GetBool("dom.ipc.processHangMonitor", false)) { |
1311 | 0 | return nullptr; |
1312 | 0 | } |
1313 | 0 | |
1314 | 0 | Endpoint<PProcessHangMonitorParent> parent; |
1315 | 0 | Endpoint<PProcessHangMonitorChild> child; |
1316 | 0 | nsresult rv; |
1317 | 0 | rv = PProcessHangMonitor::CreateEndpoints(base::GetCurrentProcId(), |
1318 | 0 | aContentParent->OtherPid(), |
1319 | 0 | &parent, &child); |
1320 | 0 | if (NS_FAILED(rv)) { |
1321 | 0 | MOZ_ASSERT(false, "PProcessHangMonitor::CreateEndpoints failed"); |
1322 | 0 | return nullptr; |
1323 | 0 | } |
1324 | 0 |
|
1325 | 0 | if (!aContentParent->SendInitProcessHangMonitor(std::move(child))) { |
1326 | 0 | MOZ_ASSERT(false); |
1327 | 0 | return nullptr; |
1328 | 0 | } |
1329 | 0 |
|
1330 | 0 | return CreateHangMonitorParent(aContentParent, std::move(parent)); |
1331 | 0 | } |
1332 | | |
1333 | | /* static */ void |
1334 | | ProcessHangMonitor::RemoveProcess(PProcessHangMonitorParent* aParent) |
1335 | 0 | { |
1336 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1337 | 0 | auto parent = static_cast<HangMonitorParent*>(aParent); |
1338 | 0 | parent->Shutdown(); |
1339 | 0 | delete parent; |
1340 | 0 | } |
1341 | | |
1342 | | /* static */ void |
1343 | | ProcessHangMonitor::ClearHang() |
1344 | 1.62M | { |
1345 | 1.62M | MOZ_ASSERT(NS_IsMainThread()); |
1346 | 1.62M | if (HangMonitorChild* child = HangMonitorChild::Get()) { |
1347 | 0 | child->ClearHang(); |
1348 | 0 | } |
1349 | 1.62M | } |
1350 | | |
1351 | | /* static */ void |
1352 | | ProcessHangMonitor::PaintWhileInterruptingJS(PProcessHangMonitorParent* aParent, |
1353 | | dom::TabParent* aTabParent, |
1354 | | bool aForceRepaint, |
1355 | | const layers::LayersObserverEpoch& aEpoch) |
1356 | 0 | { |
1357 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1358 | 0 | auto parent = static_cast<HangMonitorParent*>(aParent); |
1359 | 0 | parent->PaintWhileInterruptingJS(aTabParent, aForceRepaint, aEpoch); |
1360 | 0 | } |
1361 | | |
1362 | | /* static */ void |
1363 | | ProcessHangMonitor::ClearPaintWhileInterruptingJS(const layers::LayersObserverEpoch& aEpoch) |
1364 | 0 | { |
1365 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1366 | 0 | MOZ_RELEASE_ASSERT(XRE_IsContentProcess()); |
1367 | 0 |
|
1368 | 0 | if (HangMonitorChild* child = HangMonitorChild::Get()) { |
1369 | 0 | child->ClearPaintWhileInterruptingJS(aEpoch); |
1370 | 0 | } |
1371 | 0 | } |
1372 | | |
1373 | | /* static */ void |
1374 | | ProcessHangMonitor::MaybeStartPaintWhileInterruptingJS() |
1375 | 0 | { |
1376 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
1377 | 0 | MOZ_RELEASE_ASSERT(XRE_IsContentProcess()); |
1378 | 0 |
|
1379 | 0 | if (HangMonitorChild* child = HangMonitorChild::Get()) { |
1380 | 0 | child->MaybeStartPaintWhileInterruptingJS(); |
1381 | 0 | } |
1382 | 0 | } |