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