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 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 29 HungWindowDetector::~HungWindowDetector() { 30 } 31 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 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 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 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