Home | History | Annotate | Download | only in hang_monitor
      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 
     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
     29 HungWindowDetector::~HungWindowDetector() {
     30 }
     31 
     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 
     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 
     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 
    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