// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/hang_monitor/hung_window_detector.h" #include #include #include "base/logging.h" #include "chrome/browser/hang_monitor/hang_crash_dump_win.h" #include "content/public/common/result_codes.h" namespace { // How long do we wait for the terminated thread or process to die (in ms) static const int kTerminateTimeout = 2000; } // namespace const wchar_t HungWindowDetector::kHungChildWindowTimeout[] = L"Chrome_HungChildWindowTimeout"; HungWindowDetector::HungWindowDetector(HungWindowNotification* notification) : notification_(notification), top_level_window_(NULL), message_response_timeout_(0), enumerating_(false) { DCHECK(NULL != notification_); } // NOTE: It is the caller's responsibility to make sure that // callbacks on this object have been stopped before // destroying this object HungWindowDetector::~HungWindowDetector() { } bool HungWindowDetector::Initialize(HWND top_level_window, int message_response_timeout) { if (NULL == notification_) { return false; } if (NULL == top_level_window) { return false; } // It is OK to call Initialize on this object repeatedly // with different top lebel HWNDs and timeout values each time. // And we do not need a lock for this because we are just // swapping DWORDs. top_level_window_ = top_level_window; message_response_timeout_ = message_response_timeout; return true; } void HungWindowDetector::OnTick() { do { base::AutoLock lock(hang_detection_lock_); // If we already are checking for hung windows on another thread, // don't do this again. if (enumerating_) { return; } enumerating_ = true; } while (false); // To scope the AutoLock EnumChildWindows(top_level_window_, ChildWndEnumProc, reinterpret_cast(this)); // The window shouldn't be disabled unless we're showing a modal dialog. // If we're not, then reenable the window. if (!::IsWindowEnabled(top_level_window_) && !::GetWindow(top_level_window_, GW_ENABLEDPOPUP)) { ::EnableWindow(top_level_window_, TRUE); } enumerating_ = false; } bool HungWindowDetector::CheckChildWindow(HWND child_window) { // It can happen that the window is DOA. It specifically happens // when we have just killed a plugin process and the enum is still // enumerating windows from that process. if (!IsWindow(child_window)) { return true; } DWORD top_level_window_thread_id = GetWindowThreadProcessId(top_level_window_, NULL); DWORD child_window_process_id = 0; DWORD child_window_thread_id = GetWindowThreadProcessId(child_window, &child_window_process_id); bool continue_hang_detection = true; if (top_level_window_thread_id != child_window_thread_id) { // The message timeout for a child window starts of with a default // value specified by the message_response_timeout_ member. It is // tracked by a property on the child window. #pragma warning(disable:4311) int child_window_message_timeout = reinterpret_cast(GetProp(child_window, kHungChildWindowTimeout)); #pragma warning(default:4311) if (!child_window_message_timeout) { child_window_message_timeout = message_response_timeout_; } DWORD_PTR result = 0; if (0 == SendMessageTimeout(child_window, WM_NULL, 0, 0, SMTO_BLOCK, child_window_message_timeout, &result)) { HungWindowNotification::ActionOnHungWindow action = HungWindowNotification::HUNG_WINDOW_IGNORE; #pragma warning(disable:4312) SetProp(child_window, kHungChildWindowTimeout, reinterpret_cast(child_window_message_timeout)); #pragma warning(default:4312) continue_hang_detection = notification_->OnHungWindowDetected(child_window, top_level_window_, &action); // Make sure this window still a child of our top-level parent if (!IsChild(top_level_window_, child_window)) { return continue_hang_detection; } if (action == HungWindowNotification::HUNG_WINDOW_TERMINATE_PROCESS) { RemoveProp(child_window, kHungChildWindowTimeout); CHandle child_process(OpenProcess(PROCESS_ALL_ACCESS, FALSE, child_window_process_id)); if (NULL == child_process.m_h) { return continue_hang_detection; } // Before swinging the axe, do some sanity checks to make // sure this window still belongs to the same process DWORD process_id_check = 0; GetWindowThreadProcessId(child_window, &process_id_check); if (process_id_check != child_window_process_id) { return continue_hang_detection; } // Before terminating the process we try collecting a dump. Which // a transient thread in the child process will do for us. CrashDumpAndTerminateHungChildProcess(child_process); child_process.Close(); } } else { RemoveProp(child_window, kHungChildWindowTimeout); } } return continue_hang_detection; } BOOL CALLBACK HungWindowDetector::ChildWndEnumProc(HWND child_window, LPARAM param) { HungWindowDetector* detector_instance = reinterpret_cast(param); if (NULL == detector_instance) { NOTREACHED(); return FALSE; } return detector_instance->CheckChildWindow(child_window); }