blob: 89d8be87e870ec8475c0980644f66c360186bbd7 [file] [log] [blame]
// 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 <windows.h>
#include <atlbase.h>
#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<LPARAM>(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<int>(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<HANDLE>(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<HungWindowDetector*>(param);
if (NULL == detector_instance) {
NOTREACHED();
return FALSE;
}
return detector_instance->CheckChildWindow(child_window);
}