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 14namespace { 15 16// How long do we wait for the terminated thread or process to die (in ms) 17static const int kTerminateTimeout = 2000; 18 19} // namespace 20 21const wchar_t HungWindowDetector::kHungChildWindowTimeout[] = 22 L"Chrome_HungChildWindowTimeout"; 23 24HungWindowDetector::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 34HungWindowDetector::~HungWindowDetector() { 35} 36 37bool 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 54void 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 78bool 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 158BOOL 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