1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/hang_monitor/hung_window_detector.h"
6
7 #include <windows.h>
8 #include <atlbase.h>
9
10 #include "base/logging.h"
11 #include "chrome/browser/hang_monitor/hang_crash_dump_win.h"
12 #include "content/public/common/result_codes.h"
13
14 namespace {
15
16 // How long do we wait for the terminated thread or process to die (in ms)
17 static const int kTerminateTimeout = 2000;
18
19 } // namespace
20
21 const wchar_t HungWindowDetector::kHungChildWindowTimeout[] =
22 L"Chrome_HungChildWindowTimeout";
23
HungWindowDetector(HungWindowNotification * notification)24 HungWindowDetector::HungWindowDetector(HungWindowNotification* notification)
25 : notification_(notification),
26 top_level_window_(NULL),
27 message_response_timeout_(0),
28 enumerating_(false) {
29 DCHECK(NULL != notification_);
30 }
31 // NOTE: It is the caller's responsibility to make sure that
32 // callbacks on this object have been stopped before
33 // destroying this object
~HungWindowDetector()34 HungWindowDetector::~HungWindowDetector() {
35 }
36
Initialize(HWND top_level_window,int message_response_timeout)37 bool HungWindowDetector::Initialize(HWND top_level_window,
38 int message_response_timeout) {
39 if (NULL == notification_) {
40 return false;
41 }
42 if (NULL == top_level_window) {
43 return false;
44 }
45 // It is OK to call Initialize on this object repeatedly
46 // with different top lebel HWNDs and timeout values each time.
47 // And we do not need a lock for this because we are just
48 // swapping DWORDs.
49 top_level_window_ = top_level_window;
50 message_response_timeout_ = message_response_timeout;
51 return true;
52 }
53
OnTick()54 void HungWindowDetector::OnTick() {
55 do {
56 base::AutoLock lock(hang_detection_lock_);
57 // If we already are checking for hung windows on another thread,
58 // don't do this again.
59 if (enumerating_) {
60 return;
61 }
62 enumerating_ = true;
63 } while (false); // To scope the AutoLock
64
65 EnumChildWindows(top_level_window_, ChildWndEnumProc,
66 reinterpret_cast<LPARAM>(this));
67
68 // The window shouldn't be disabled unless we're showing a modal dialog.
69 // If we're not, then reenable the window.
70 if (!::IsWindowEnabled(top_level_window_) &&
71 !::GetWindow(top_level_window_, GW_ENABLEDPOPUP)) {
72 ::EnableWindow(top_level_window_, TRUE);
73 }
74
75 enumerating_ = false;
76 }
77
CheckChildWindow(HWND child_window)78 bool HungWindowDetector::CheckChildWindow(HWND child_window) {
79 // It can happen that the window is DOA. It specifically happens
80 // when we have just killed a plugin process and the enum is still
81 // enumerating windows from that process.
82 if (!IsWindow(child_window)) {
83 return true;
84 }
85
86 DWORD top_level_window_thread_id =
87 GetWindowThreadProcessId(top_level_window_, NULL);
88
89 DWORD child_window_process_id = 0;
90 DWORD child_window_thread_id =
91 GetWindowThreadProcessId(child_window, &child_window_process_id);
92 bool continue_hang_detection = true;
93
94 if (top_level_window_thread_id != child_window_thread_id) {
95 // The message timeout for a child window starts of with a default
96 // value specified by the message_response_timeout_ member. It is
97 // tracked by a property on the child window.
98 #pragma warning(disable:4311)
99 int child_window_message_timeout =
100 reinterpret_cast<int>(GetProp(child_window, kHungChildWindowTimeout));
101 #pragma warning(default:4311)
102 if (!child_window_message_timeout) {
103 child_window_message_timeout = message_response_timeout_;
104 }
105
106 DWORD_PTR result = 0;
107 if (0 == SendMessageTimeout(child_window,
108 WM_NULL,
109 0,
110 0,
111 SMTO_BLOCK,
112 child_window_message_timeout,
113 &result)) {
114 HungWindowNotification::ActionOnHungWindow action =
115 HungWindowNotification::HUNG_WINDOW_IGNORE;
116 #pragma warning(disable:4312)
117 SetProp(child_window, kHungChildWindowTimeout,
118 reinterpret_cast<HANDLE>(child_window_message_timeout));
119 #pragma warning(default:4312)
120 continue_hang_detection =
121 notification_->OnHungWindowDetected(child_window, top_level_window_,
122 &action);
123 // Make sure this window still a child of our top-level parent
124 if (!IsChild(top_level_window_, child_window)) {
125 return continue_hang_detection;
126 }
127
128 if (action == HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS) {
129 RemoveProp(child_window, kHungChildWindowTimeout);
130 CHandle child_process(OpenProcess(PROCESS_ALL_ACCESS,
131 FALSE,
132 child_window_process_id));
133
134 if (NULL == child_process.m_h) {
135 return continue_hang_detection;
136 }
137 // Before swinging the axe, do some sanity checks to make
138 // sure this window still belongs to the same process
139 DWORD process_id_check = 0;
140 GetWindowThreadProcessId(child_window, &process_id_check);
141 if (process_id_check != child_window_process_id) {
142 return continue_hang_detection;
143 }
144
145 // Before terminating the process we try collecting a dump. Which
146 // a transient thread in the child process will do for us.
147 CrashDumpAndTerminateHungChildProcess(child_process);
148 child_process.Close();
149 }
150 } else {
151 RemoveProp(child_window, kHungChildWindowTimeout);
152 }
153 }
154
155 return continue_hang_detection;
156 }
157
ChildWndEnumProc(HWND child_window,LPARAM param)158 BOOL CALLBACK HungWindowDetector::ChildWndEnumProc(HWND child_window,
159 LPARAM param) {
160 HungWindowDetector* detector_instance =
161 reinterpret_cast<HungWindowDetector*>(param);
162 if (NULL == detector_instance) {
163 NOTREACHED();
164 return FALSE;
165 }
166
167 return detector_instance->CheckChildWindow(child_window);
168 }
169