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)
14static const int kTerminateTimeout = 2000;
15
16const wchar_t HungWindowDetector::kHungChildWindowTimeout[] =
17    L"Chrome_HungChildWindowTimeout";
18
19HungWindowDetector::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
29HungWindowDetector::~HungWindowDetector() {
30}
31
32bool 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
49void 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
65bool 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
167BOOL 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