Home | History | Annotate | Download | only in hang_monitor
      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 
     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
     34 HungWindowDetector::~HungWindowDetector() {
     35 }
     36 
     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 
     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 
     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 
    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