• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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