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