summaryrefslogtreecommitdiffstats
path: root/components/browser_watcher
diff options
context:
space:
mode:
authorpmonette <pmonette@chromium.org>2016-01-29 14:24:37 -0800
committerCommit bot <commit-bot@chromium.org>2016-01-29 22:26:03 +0000
commit3955a4ff6fb5b66408cf3748c9dcd4bd22885c80 (patch)
tree04eef123c924537220d796372623094903005382 /components/browser_watcher
parentf1ca4764a94823a92c49a0b98c9f5cc9681894b9 (diff)
downloadchromium_src-3955a4ff6fb5b66408cf3748c9dcd4bd22885c80.zip
chromium_src-3955a4ff6fb5b66408cf3748c9dcd4bd22885c80.tar.gz
chromium_src-3955a4ff6fb5b66408cf3748c9dcd4bd22885c80.tar.bz2
Added an integration test for kasko hang reports
Review URL: https://codereview.chromium.org/1543803005 Cr-Commit-Position: refs/heads/master@{#372442}
Diffstat (limited to 'components/browser_watcher')
-rw-r--r--components/browser_watcher/window_hang_monitor_win.cc52
-rw-r--r--components/browser_watcher/window_hang_monitor_win.h4
-rw-r--r--components/browser_watcher/window_hang_monitor_win_unittest.cc532
3 files changed, 346 insertions, 242 deletions
diff --git a/components/browser_watcher/window_hang_monitor_win.cc b/components/browser_watcher/window_hang_monitor_win.cc
index 964a6f7..da0eff1 100644
--- a/components/browser_watcher/window_hang_monitor_win.cc
+++ b/components/browser_watcher/window_hang_monitor_win.cc
@@ -4,22 +4,48 @@
#include "components/browser_watcher/window_hang_monitor_win.h"
#include "base/callback.h"
+#include "base/files/file_util.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
#include "base/win/message_window.h"
namespace browser_watcher {
namespace {
-HWND FindNamedWindowForProcess(const base::string16 name, base::ProcessId pid) {
- HWND candidate = base::win::MessageWindow::FindWindow(name);
- if (candidate) {
+// Returns true if the class name for |window| equals |str|.
+bool WindowClassNameEqualsString(HWND window, base::StringPiece16 str) {
+ wchar_t class_name[MAX_PATH];
+ int str_length = ::GetClassName(window, class_name, MAX_PATH);
+ return str_length && str.compare(class_name) == 0;
+}
+
+// Returns true if the window text is an existing directory. Ensures that
+// |window| is the right Chrome message window to ping. This could be improved
+// by testing for a valid profile in the directory.
+bool WindowNameIsExistingDirectory(HWND window) {
+ base::string16 window_name;
+ int str_length = ::GetWindowText(
+ window, base::WriteInto(&window_name, MAX_PATH), MAX_PATH);
+ window_name.resize(str_length);
+ return base::DirectoryExists(base::FilePath(window_name));
+}
+
+// Returns the Chrome message window handle for the specified |pid| or nullptr
+// if not found.
+HWND FindChromeMessageWindow(base::ProcessId pid) {
+ HWND candidate = ::FindWindowEx(HWND_MESSAGE, nullptr, nullptr, nullptr);
+ while (candidate) {
DWORD actual_process_id = 0;
::GetWindowThreadProcessId(candidate, &actual_process_id);
- if (actual_process_id == pid)
+ if (WindowClassNameEqualsString(candidate, L"Chrome_MessageWindow") &&
+ WindowNameIsExistingDirectory(candidate) && actual_process_id == pid) {
return candidate;
+ }
+ candidate = ::GetNextWindow(candidate, GW_HWNDNEXT);
}
return nullptr;
}
@@ -45,9 +71,7 @@ WindowHangMonitor::~WindowHangMonitor() {
}
}
-void WindowHangMonitor::Initialize(base::Process process,
- const base::string16& window_name) {
- window_name_ = window_name;
+void WindowHangMonitor::Initialize(base::Process process) {
window_process_ = process.Pass();
timer_.SetTaskRunner(base::MessageLoop::current()->task_runner());
@@ -69,7 +93,7 @@ void WindowHangMonitor::PollForWindow() {
return;
}
- HWND hwnd = FindNamedWindowForProcess(window_name_, window_process_.Pid());
+ HWND hwnd = FindChromeMessageWindow(window_process_.Pid());
if (hwnd) {
// Sends a ping and schedules a timeout task. Upon receiving a ping response
// further pings will be scheduled ad infinitum. Will signal any failure now
@@ -121,7 +145,6 @@ void WindowHangMonitor::SendPing(HWND hwnd) {
void WindowHangMonitor::OnHangTimeout(HWND hwnd) {
DCHECK(window_process_.IsValid());
-
if (outstanding_ping_) {
// The ping is still outstanding, the window is hung or has vanished.
// Orphan the outstanding ping. If the callback arrives late, it will
@@ -129,8 +152,7 @@ void WindowHangMonitor::OnHangTimeout(HWND hwnd) {
outstanding_ping_->monitor = NULL;
outstanding_ping_ = NULL;
- if (hwnd !=
- FindNamedWindowForProcess(window_name_, window_process_.Pid())) {
+ if (hwnd != FindChromeMessageWindow(window_process_.Pid())) {
// The window vanished.
callback_.Run(WINDOW_VANISHED);
} else {
@@ -147,6 +169,7 @@ void WindowHangMonitor::OnHangTimeout(HWND hwnd) {
void WindowHangMonitor::OnRetryTimeout() {
DCHECK(window_process_.IsValid());
+ DCHECK(window_process_.IsValid());
DCHECK(!outstanding_ping_);
// We can't simply hold onto the previously located HWND due to potential
// aliasing.
@@ -155,11 +178,12 @@ void WindowHangMonitor::OnRetryTimeout() {
// process.
// 2. The window handle might have been re-assigned to a different process
// at any point after we found it.
- HWND hwnd = FindNamedWindowForProcess(window_name_, window_process_.Pid());
- if (hwnd)
+ HWND hwnd = FindChromeMessageWindow(window_process_.Pid());
+ if (hwnd) {
SendPing(hwnd);
- else
+ } else {
callback_.Run(WINDOW_VANISHED);
+ }
}
} // namespace browser_watcher
diff --git a/components/browser_watcher/window_hang_monitor_win.h b/components/browser_watcher/window_hang_monitor_win.h
index 4c515d7..5f2b415 100644
--- a/components/browser_watcher/window_hang_monitor_win.h
+++ b/components/browser_watcher/window_hang_monitor_win.h
@@ -43,9 +43,9 @@ class WindowHangMonitor {
const WindowEventCallback& callback);
~WindowHangMonitor();
- // Initializes the watcher to monitor a window named |window_name| and owned
+ // Initializes the watcher to monitor the Chrome message window owned
// by |process|. May be invoked prior to the appearance of the window.
- void Initialize(base::Process process, const base::string16& window_name);
+ void Initialize(base::Process process);
private:
struct OutstandingPing {
diff --git a/components/browser_watcher/window_hang_monitor_win_unittest.cc b/components/browser_watcher/window_hang_monitor_win_unittest.cc
index 0a2f414..8ff4a8e 100644
--- a/components/browser_watcher/window_hang_monitor_win_unittest.cc
+++ b/components/browser_watcher/window_hang_monitor_win_unittest.cc
@@ -4,32 +4,19 @@
#include "components/browser_watcher/window_hang_monitor_win.h"
-#include <stddef.h>
-
-#include <vector>
-
+#include "base/base_paths.h"
#include "base/base_switches.h"
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/callback.h"
-#include "base/callback_helpers.h"
#include "base/command_line.h"
-#include "base/location.h"
-#include "base/macros.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/process/process.h"
-#include "base/process/process_handle.h"
-#include "base/run_loop.h"
-#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
-#include "base/strings/stringprintf.h"
-#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/multiprocess_test.h"
#include "base/threading/thread.h"
#include "base/win/message_window.h"
+#include "base/win/scoped_handle.h"
+#include "base/win/win_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
@@ -37,9 +24,209 @@ namespace browser_watcher {
namespace {
-// Simulates a process that never opens a window.
-MULTIPROCESS_TEST_MAIN(NoWindowChild) {
- ::Sleep(INFINITE);
+const char kChildReadPipeSwitch[] = "child_read_pipe";
+const char kChildWritePipeSwitch[] = "child_write_pipe";
+
+// Signals used for IPC between the monitor process and the monitor.
+enum IPCSignal {
+ IPC_SIGNAL_INVALID,
+ IPC_SIGNAL_READY,
+ IPC_SIGNAL_TERMINATE_PROCESS,
+ IPC_SIGNAL_CREATE_MESSAGE_WINDOW,
+ IPC_SIGNAL_DELETE_MESSAGE_WINDOW,
+ IPC_SIGNAL_HANG_MESSAGE_WINDOW,
+};
+
+// Sends |ipc_signal| through the |write_pipe|.
+bool SendPipeSignal(HANDLE write_pipe, IPCSignal ipc_signal) {
+ DWORD bytes_written = 0;
+ if (!WriteFile(write_pipe, &ipc_signal, sizeof(ipc_signal), &bytes_written,
+ nullptr))
+ return false;
+
+ return bytes_written == sizeof(ipc_signal);
+}
+
+// Blocks on |read_pipe| until a signal is received into |ipc_signal|.
+bool WaitForPipeSignal(HANDLE read_pipe, IPCSignal* ipc_signal) {
+ CHECK(ipc_signal);
+ DWORD bytes_read = 0;
+ if (!ReadFile(read_pipe, ipc_signal, sizeof(*ipc_signal), &bytes_read,
+ nullptr))
+ return false;
+
+ return bytes_read == sizeof(*ipc_signal);
+}
+
+// Blocks on |read_pipe| until a signal is received and returns true if it
+// matches |expected_ipc_signal|.
+bool WaitForSpecificPipeSignal(HANDLE read_pipe,
+ IPCSignal expected_ipc_signal) {
+ IPCSignal received_signal = IPC_SIGNAL_INVALID;
+ return WaitForPipeSignal(read_pipe, &received_signal) &&
+ received_signal == expected_ipc_signal;
+}
+
+// Appends |handle| as a command line switch.
+void AppendSwitchHandle(base::CommandLine* command_line,
+ std::string switch_name,
+ HANDLE handle) {
+ command_line->AppendSwitchASCII(
+ switch_name, base::UintToString(base::win::HandleToUint32(handle)));
+}
+
+// Retrieves the |handle| associated to |switch_name| from the command line.
+HANDLE GetSwitchValueHandle(base::CommandLine* command_line,
+ std::string switch_name) {
+ std::string switch_string = command_line->GetSwitchValueASCII(switch_name);
+ unsigned int switch_uint = 0;
+ if (switch_string.empty() ||
+ !base::StringToUint(switch_string, &switch_uint)) {
+ DLOG(ERROR) << "Missing or invalid " << switch_name << " argument.";
+ return nullptr;
+ }
+ return reinterpret_cast<HANDLE>(switch_uint);
+}
+
+// An instance of this class lives in the monitored process and receives signals
+// and executes their associated function.
+class MonitoredProcessClient {
+ public:
+ MonitoredProcessClient()
+ : message_window_thread_("Message window thread"),
+ hang_event_(true, false) {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+
+ read_pipe_.Set(GetSwitchValueHandle(command_line, kChildReadPipeSwitch));
+ write_pipe_.Set(GetSwitchValueHandle(command_line, kChildWritePipeSwitch));
+ }
+
+ ~MonitoredProcessClient() {
+ if (message_window_thread_.IsRunning()) {
+ DeleteMessageWindow();
+ }
+ }
+
+ void RunEventLoop() {
+ bool running = true;
+ IPCSignal ipc_signal = IPC_SIGNAL_INVALID;
+ while (running) {
+ CHECK(WaitForPipeSignal(read_pipe_.Get(), &ipc_signal));
+ switch (ipc_signal) {
+ // The parent process should never send those.
+ case IPC_SIGNAL_INVALID:
+ case IPC_SIGNAL_READY:
+ CHECK(false);
+ break;
+ case IPC_SIGNAL_TERMINATE_PROCESS:
+ running = false;
+ break;
+ case IPC_SIGNAL_CREATE_MESSAGE_WINDOW:
+ CreateMessageWindow();
+ break;
+ case IPC_SIGNAL_DELETE_MESSAGE_WINDOW:
+ DeleteMessageWindow();
+ break;
+ case IPC_SIGNAL_HANG_MESSAGE_WINDOW:
+ HangMessageWindow();
+ break;
+ }
+ SendSignalToParent(IPC_SIGNAL_READY);
+ }
+ }
+
+ // Creates a thread then creates the message window on it.
+ void CreateMessageWindow() {
+ ASSERT_TRUE(message_window_thread_.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_UI, 0)));
+
+ bool succeeded = false;
+ base::WaitableEvent created(true, false);
+ ASSERT_TRUE(message_window_thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&MonitoredProcessClient::CreateMessageWindowInWorkerThread,
+ base::Unretained(this), &succeeded, &created)));
+ created.Wait();
+ ASSERT_TRUE(succeeded);
+ }
+
+ // Creates a thread then creates the message window on it.
+ void HangMessageWindow() {
+ message_window_thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&base::WaitableEvent::Wait, base::Unretained(&hang_event_)));
+ }
+
+ bool SendSignalToParent(IPCSignal ipc_signal) {
+ return SendPipeSignal(write_pipe_.Get(), ipc_signal);
+ }
+
+ private:
+ bool EmptyMessageCallback(UINT message,
+ WPARAM wparam,
+ LPARAM lparam,
+ LRESULT* result) {
+ EXPECT_EQ(message_window_thread_.message_loop(),
+ base::MessageLoop::current());
+ return false; // Pass through to DefWindowProc.
+ }
+
+ void CreateMessageWindowInWorkerThread(bool* success,
+ base::WaitableEvent* created) {
+ CHECK(created);
+
+ // As an alternative to checking if the name of the message window is the
+ // user data directory, the hang watcher verifies that the window name is an
+ // existing directory. DIR_CURRENT is used to meet this constraint.
+ base::FilePath existing_dir;
+ CHECK(PathService::Get(base::DIR_CURRENT, &existing_dir));
+
+ message_window_.reset(new base::win::MessageWindow);
+ *success = message_window_->CreateNamed(
+ base::Bind(&MonitoredProcessClient::EmptyMessageCallback,
+ base::Unretained(this)),
+ existing_dir.value().c_str());
+ created->Signal();
+ }
+
+ void DeleteMessageWindow() {
+ base::WaitableEvent deleted(true, false);
+ message_window_thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&MonitoredProcessClient::DeleteMessageWindowInWorkerThread,
+ base::Unretained(this), &deleted));
+ deleted.Wait();
+
+ message_window_thread_.Stop();
+ }
+
+ void DeleteMessageWindowInWorkerThread(base::WaitableEvent* deleted) {
+ CHECK(deleted);
+ message_window_.reset();
+ deleted->Signal();
+ }
+
+ // The thread that holds the message window.
+ base::Thread message_window_thread_;
+ scoped_ptr<base::win::MessageWindow> message_window_;
+
+ // Event used to hang the message window.
+ base::WaitableEvent hang_event_;
+
+ // Anonymous pipe handles for IPC with the parent process.
+ base::win::ScopedHandle read_pipe_;
+ base::win::ScopedHandle write_pipe_;
+
+ DISALLOW_COPY_AND_ASSIGN(MonitoredProcessClient);
+};
+
+// The monitored process main function.
+MULTIPROCESS_TEST_MAIN(MonitoredProcess) {
+ MonitoredProcessClient monitored_process_client;
+ CHECK(monitored_process_client.SendSignalToParent(IPC_SIGNAL_READY));
+
+ monitored_process_client.RunEventLoop();
+
return 0;
}
@@ -50,16 +237,16 @@ class HangMonitorThread {
HangMonitorThread()
: event_(WindowHangMonitor::WINDOW_NOT_FOUND),
event_received_(false, false),
- thread_("HangMonitorThread") {}
+ thread_("Hang monitor thread") {}
~HangMonitorThread() {
if (hang_monitor_.get())
DestroyWatcher();
}
- // Starts the background thread and the monitor to observe the window named
- // |window_name| in |process|. Blocks until the monitor has been initialized.
- bool Start(base::Process process, const base::string16& window_name) {
+ // Starts the background thread and the monitor to observe Chrome message
+ // window for |process|. Blocks until the monitor has been initialized.
+ bool Start(base::Process process) {
if (!thread_.StartWithOptions(
base::Thread::Options(base::MessageLoop::TYPE_UI, 0))) {
return false;
@@ -67,10 +254,10 @@ class HangMonitorThread {
base::WaitableEvent complete(false, false);
if (!thread_.task_runner()->PostTask(
- FROM_HERE, base::Bind(&HangMonitorThread::StartupOnThread,
- base::Unretained(this), window_name,
- base::Passed(process.Pass()),
- base::Unretained(&complete)))) {
+ FROM_HERE,
+ base::Bind(&HangMonitorThread::StartupOnThread,
+ base::Unretained(this), base::Passed(std::move(process)),
+ base::Unretained(&complete)))) {
return false;
}
@@ -90,6 +277,7 @@ class HangMonitorThread {
return event_;
}
+ private:
// Destroys the monitor and stops the background thread. Blocks until the
// operation completes.
void DestroyWatcher() {
@@ -100,7 +288,6 @@ class HangMonitorThread {
thread_.Stop();
}
- private:
// Invoked when the monitor signals an event. Unblocks a call to
// TimedWaitForEvent or WaitForEvent.
void EventCallback(WindowHangMonitor::WindowEvent event) {
@@ -110,16 +297,14 @@ class HangMonitorThread {
event_received_.Signal();
}
- // Initializes the WindowHangMonitor to observe the window named |window_name|
- // in |process|. Signals |complete| when done.
- void StartupOnThread(const base::string16& window_name,
- base::Process process,
- base::WaitableEvent* complete) {
+ // Initializes the WindowHangMonitor to observe the Chrome message window for
+ // |process|. Signals |complete| when done.
+ void StartupOnThread(base::Process process, base::WaitableEvent* complete) {
hang_monitor_.reset(new WindowHangMonitor(
base::TimeDelta::FromMilliseconds(100),
base::TimeDelta::FromMilliseconds(100),
base::Bind(&HangMonitorThread::EventCallback, base::Unretained(this))));
- hang_monitor_->Initialize(process.Pass(), window_name);
+ hang_monitor_->Initialize(std::move(process));
complete->Signal();
}
@@ -141,247 +326,142 @@ class HangMonitorThread {
class WindowHangMonitorTest : public testing::Test {
public:
- WindowHangMonitorTest()
- : ping_event_(false, false),
- pings_(0),
- window_thread_("WindowHangMonitorTest window_thread") {}
-
- void SetUp() override {
- // Pick a window name unique to this process.
- window_name_ = base::StringPrintf(L"WindowHanMonitorTest-%d",
- base::GetCurrentProcId());
- ASSERT_TRUE(window_thread_.StartWithOptions(
- base::Thread::Options(base::MessageLoop::TYPE_UI, 0)));
- }
+ WindowHangMonitorTest() {}
- void TearDown() override {
- DeleteMessageWindow();
- window_thread_.Stop();
+ ~WindowHangMonitorTest() {
+ // Close process if running.
+ monitored_process_.Terminate(1, false);
}
- void CreateMessageWindow() {
- bool succeeded = false;
- base::WaitableEvent created(true, false);
- ASSERT_TRUE(window_thread_.task_runner()->PostTask(
- FROM_HERE,
- base::Bind(&WindowHangMonitorTest::CreateMessageWindowInWorkerThread,
- base::Unretained(this), window_name_, &succeeded,
- &created)));
- created.Wait();
- ASSERT_TRUE(succeeded);
- }
+ // Starts a child process that will be monitored. Handles to anonymous pipes
+ // are passed to the command line to provide a way to communicate with the
+ // child process. This function blocks until IPC_SIGNAL_READY is received.
+ bool StartMonitoredProcess() {
+ HANDLE child_read_pipe = nullptr;
+ HANDLE child_write_pipe = nullptr;
+ if (!CreatePipes(&child_read_pipe, &child_write_pipe))
+ return false;
- void DeleteMessageWindow() {
- base::WaitableEvent deleted(true, false);
- window_thread_.task_runner()->PostTask(
- FROM_HERE,
- base::Bind(&WindowHangMonitorTest::DeleteMessageWindowInWorkerThread,
- base::Unretained(this), &deleted));
- deleted.Wait();
+ base::CommandLine command_line =
+ base::GetMultiProcessTestChildBaseCommandLine();
+ command_line.AppendSwitchASCII(switches::kTestChildProcess,
+ "MonitoredProcess");
+
+ AppendSwitchHandle(&command_line, kChildReadPipeSwitch, child_read_pipe);
+ AppendSwitchHandle(&command_line, kChildWritePipeSwitch, child_write_pipe);
+
+ base::LaunchOptions options = {};
+ options.inherit_handles = true;
+ monitored_process_ = base::LaunchProcess(command_line, options);
+ if (!monitored_process_.IsValid())
+ return false;
+
+ return WaitForSignal(IPC_SIGNAL_READY);
}
- void WaitForPing() {
- while (true) {
- {
- base::AutoLock auto_lock(ping_lock_);
- if (pings_) {
- ping_event_.Reset();
- --pings_;
- return;
- }
- }
- ping_event_.Wait();
- }
+ void StartHangMonitor() {
+ monitor_thread_.Start(monitored_process_.Duplicate());
}
- HangMonitorThread& monitor_thread() { return monitor_thread_; }
+ // Sends the |ipc_signal| to the child process and wait for a IPC_SIGNAL_READY
+ // response.
+ bool SendSignal(IPCSignal ipc_signal) {
+ if (!SendPipeSignal(write_pipe_.Get(), ipc_signal))
+ return false;
- const base::win::MessageWindow* message_window() const {
- return message_window_.get();
+ return WaitForSignal(IPC_SIGNAL_READY);
}
- const base::string16& window_name() const { return window_name_; }
+ // Blocks until |ipc_signal| is received from the child process.
+ bool WaitForSignal(IPCSignal ipc_signal) {
+ return WaitForSpecificPipeSignal(read_pipe_.Get(), ipc_signal);
+ }
- base::Thread* window_thread() { return &window_thread_; }
+ HangMonitorThread& monitor_thread() { return monitor_thread_; }
private:
- bool MessageCallback(UINT message,
- WPARAM wparam,
- LPARAM lparam,
- LRESULT* result) {
- EXPECT_EQ(window_thread_.message_loop(), base::MessageLoop::current());
- if (message == WM_NULL) {
- base::AutoLock auto_lock(ping_lock_);
- ++pings_;
- ping_event_.Signal();
+ // Creates pipes for IPC with the child process.
+ bool CreatePipes(HANDLE* child_read_pipe, HANDLE* child_write_pipe) {
+ CHECK(child_read_pipe);
+ CHECK(child_write_pipe);
+ SECURITY_ATTRIBUTES security_attributes = {
+ sizeof(SECURITY_ATTRIBUTES), nullptr, true /* inherit handles */};
+
+ HANDLE parent_read_pipe = nullptr;
+ if (!CreatePipe(&parent_read_pipe, child_write_pipe, &security_attributes,
+ 0)) {
+ return false;
}
+ read_pipe_.Set(parent_read_pipe);
- return false; // Pass through to DefWindowProc.
- }
-
- void CreateMessageWindowInWorkerThread(const base::string16& name,
- bool* success,
- base::WaitableEvent* created) {
- message_window_.reset(new base::win::MessageWindow);
- *success = message_window_->CreateNamed(
- base::Bind(&WindowHangMonitorTest::MessageCallback,
- base::Unretained(this)),
- name);
- created->Signal();
- }
-
- void DeleteMessageWindowInWorkerThread(base::WaitableEvent* deleted) {
- message_window_.reset();
- if (deleted)
- deleted->Signal();
+ HANDLE parent_write_pipe = nullptr;
+ if (!CreatePipe(child_read_pipe, &parent_write_pipe, &security_attributes,
+ 0)) {
+ return false;
+ }
+ write_pipe_.Set(parent_write_pipe);
+ return true;
}
+ // The thread that monitors the child process.
HangMonitorThread monitor_thread_;
- scoped_ptr<base::win::MessageWindow> message_window_;
- base::string16 window_name_;
- base::Lock ping_lock_;
- base::WaitableEvent ping_event_;
- size_t pings_;
- base::Thread window_thread_;
+ // The process that is monitored.
+ base::Process monitored_process_;
+
+ // Anonymous pipe handles for IPC with the monitored process.
+ base::win::ScopedHandle read_pipe_;
+ base::win::ScopedHandle write_pipe_;
DISALLOW_COPY_AND_ASSIGN(WindowHangMonitorTest);
};
} // namespace
-TEST_F(WindowHangMonitorTest, NoWindow) {
- base::CommandLine child_command_line =
- base::GetMultiProcessTestChildBaseCommandLine();
- child_command_line.AppendSwitchASCII(switches::kTestChildProcess,
- "NoWindowChild");
- base::Process process =
- base::LaunchProcess(child_command_line, base::LaunchOptions());
- ASSERT_TRUE(process.IsValid());
+TEST_F(WindowHangMonitorTest, WindowNotFound) {
+ ASSERT_TRUE(StartMonitoredProcess());
- base::ScopedClosureRunner terminate_process_runner(
- base::Bind(base::IgnoreResult(&base::Process::Terminate),
- base::Unretained(&process), 1, true));
+ StartHangMonitor();
- monitor_thread().Start(process.Duplicate(), window_name());
+ ASSERT_TRUE(SendSignal(IPC_SIGNAL_TERMINATE_PROCESS));
- ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
- base::TimeDelta::FromMilliseconds(150)));
-
- terminate_process_runner.Reset();
-
- ASSERT_EQ(WindowHangMonitor::WINDOW_NOT_FOUND,
+ EXPECT_EQ(WindowHangMonitor::WINDOW_NOT_FOUND,
monitor_thread().WaitForEvent());
-
- ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
- base::TimeDelta::FromMilliseconds(150)));
-}
-
-TEST_F(WindowHangMonitorTest, WindowBeforeWatcher) {
- CreateMessageWindow();
-
- monitor_thread().Start(base::Process::Current(), window_name());
-
- WaitForPing();
-
- ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
- base::TimeDelta::FromMilliseconds(150)));
-}
-
-TEST_F(WindowHangMonitorTest, WindowBeforeDestroy) {
- CreateMessageWindow();
-
- monitor_thread().Start(base::Process::Current(), window_name());
-
- WaitForPing();
-
- ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
- base::TimeDelta::FromMilliseconds(150)));
-
- monitor_thread().DestroyWatcher();
-
- ASSERT_FALSE(monitor_thread().TimedWaitForEvent(base::TimeDelta()));
-}
-
-TEST_F(WindowHangMonitorTest, NoWindowBeforeDestroy) {
- monitor_thread().Start(base::Process::Current(), window_name());
-
- ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
- base::TimeDelta::FromMilliseconds(150)));
- monitor_thread().DestroyWatcher();
-
- ASSERT_FALSE(monitor_thread().TimedWaitForEvent(base::TimeDelta()));
}
-TEST_F(WindowHangMonitorTest, WatcherBeforeWindow) {
- monitor_thread().Start(base::Process::Current(), window_name());
-
- ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
- base::TimeDelta::FromMilliseconds(150)));
+TEST_F(WindowHangMonitorTest, WindowVanished) {
+ ASSERT_TRUE(StartMonitoredProcess());
- CreateMessageWindow();
+ ASSERT_TRUE(SendSignal(IPC_SIGNAL_CREATE_MESSAGE_WINDOW));
- WaitForPing();
+ StartHangMonitor();
ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
- base::TimeDelta::FromMilliseconds(150)));
-}
-
-TEST_F(WindowHangMonitorTest, DetectsWindowDisappearance) {
- CreateMessageWindow();
-
- monitor_thread().Start(base::Process::Current(), window_name());
+ base::TimeDelta::FromMilliseconds(250)));
- WaitForPing();
+ ASSERT_TRUE(SendSignal(IPC_SIGNAL_DELETE_MESSAGE_WINDOW));
- DeleteMessageWindow();
-
- ASSERT_EQ(WindowHangMonitor::WINDOW_VANISHED,
+ EXPECT_EQ(WindowHangMonitor::WINDOW_VANISHED,
monitor_thread().WaitForEvent());
- ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
- base::TimeDelta::FromMilliseconds(150)));
+ ASSERT_TRUE(SendSignal(IPC_SIGNAL_TERMINATE_PROCESS));
}
-TEST_F(WindowHangMonitorTest, DetectsWindowNameChange) {
- // This test changes the title of the message window as a proxy for what
- // happens if the window handle is reused for a different purpose. The latter
- // is impossible to test in a deterministic fashion.
- CreateMessageWindow();
+TEST_F(WindowHangMonitorTest, WindowHang) {
+ ASSERT_TRUE(StartMonitoredProcess());
- monitor_thread().Start(base::Process::Current(), window_name());
+ ASSERT_TRUE(SendSignal(IPC_SIGNAL_CREATE_MESSAGE_WINDOW));
- ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
- base::TimeDelta::FromMilliseconds(150)));
-
- ASSERT_TRUE(::SetWindowText(message_window()->hwnd(), L"Gonsky"));
-
- ASSERT_EQ(WindowHangMonitor::WINDOW_VANISHED,
- monitor_thread().WaitForEvent());
-}
-
-TEST_F(WindowHangMonitorTest, DetectsWindowHang) {
- CreateMessageWindow();
-
- monitor_thread().Start(base::Process::Current(), window_name());
+ StartHangMonitor();
ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
- base::TimeDelta::FromMilliseconds(150)));
-
- // Block the worker thread.
- base::WaitableEvent hang(true, false);
+ base::TimeDelta::FromMilliseconds(250)));
- window_thread()->task_runner()->PostTask(
- FROM_HERE,
- base::Bind(&base::WaitableEvent::Wait, base::Unretained(&hang)));
+ ASSERT_TRUE(SendSignal(IPC_SIGNAL_HANG_MESSAGE_WINDOW));
EXPECT_EQ(WindowHangMonitor::WINDOW_HUNG,
monitor_thread().WaitForEvent());
- // Unblock the worker thread.
- hang.Signal();
-
- ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
- base::TimeDelta::FromMilliseconds(150)));
+ ASSERT_TRUE(SendSignal(IPC_SIGNAL_TERMINATE_PROCESS));
}
} // namespace browser_watcher