Chromium Code Reviews| Index: components/browser_watcher/window_hang_monitor_win.cc |
| diff --git a/components/browser_watcher/window_hang_monitor_win.cc b/components/browser_watcher/window_hang_monitor_win.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..8d0fe9f10db15cfc94abc31c77250feb44282c06 |
| --- /dev/null |
| +++ b/components/browser_watcher/window_hang_monitor_win.cc |
| @@ -0,0 +1,186 @@ |
| +// Copyright 2015 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 "components/browser_watcher/window_hang_monitor_win.h" |
| + |
| +#include "base/message_loop/message_loop.h" |
| +#include "base/win/message_window.h" |
| + |
| +namespace browser_watcher { |
| + |
| +namespace { |
| + |
| +const size_t kPingIntervalSeconds = 60; |
| +const size_t kHangTimeoutSeconds = 20; |
| + |
| +bool IsWindowValid(HWND window, |
| + const base::string16& window_name, |
| + base::ProcessId pid) { |
| + // Validate the Window in two respects: |
| + // 1. The window handle might have been re-assigned to a different window |
| + // from the time we found it to the point where we query for its owning |
| + // process. |
| + // 2. The window handle might have been re-assigned to a different process |
| + // at any point after we found it. |
| + if (window != base::win::MessageWindow::FindWindow(window_name)) { |
| + // The window handle has been reassigned, bail. |
| + return false; |
| + } |
| + |
| + // Re-do the process ID lookup. |
| + DWORD new_process_id = 0; |
| + DWORD thread_id = ::GetWindowThreadProcessId(window, &new_process_id); |
| + if (thread_id == 0 || pid != new_process_id) { |
| + // Another process has taken over the handle. |
| + return false; |
| + } |
| + |
| + return true; |
| +} |
| + |
| +} // namespace |
| + |
| +WindowHangMonitor::WindowHangMonitor(const WindowEventCallback& callback) |
| + : callback_(callback), |
| + window_(NULL), |
| + outstanding_ping_(nullptr), |
| + timer_(false /* don't retain user task */, false /* don't repeat */), |
| + ping_interval_(base::TimeDelta::FromSeconds(kPingIntervalSeconds)), |
| + hang_timeout_(base::TimeDelta::FromSeconds(kHangTimeoutSeconds)) { |
| +} |
| + |
| +WindowHangMonitor::~WindowHangMonitor() { |
| + if (outstanding_ping_) { |
| + // We have an outstanding ping, disable it and leak it intentionally as |
| + // if the callback arrives eventually, it'll cause a use-after-free. |
| + outstanding_ping_->monitor = nullptr; |
| + outstanding_ping_ = nullptr; |
| + } |
| +} |
| + |
| +bool WindowHangMonitor::Initialize(const base::string16& window_name) { |
| + window_name_ = window_name; |
| + timer_.SetTaskRunner(base::MessageLoop::current()->task_runner()); |
| + |
| + // This code is fraught with all kinds of races. As the purpose here is |
| + // only monitoring, this code simply bails if any kind of race is encountered. |
| + // Find the window to monitor by name. |
| + window_ = base::win::MessageWindow::FindWindow(window_name); |
| + if (window_ == NULL) |
| + return false; |
| + |
| + // Find and open the process owning this window. |
| + DWORD process_id = 0; |
| + DWORD thread_id = ::GetWindowThreadProcessId(window_, &process_id); |
| + if (thread_id == 0 || process_id == 0) { |
| + // The window has vanished or there was some other problem with the handle. |
| + return false; |
| + } |
| + |
| + // Keep an open handle on the process to make sure the PID isn't reused. |
| + window_process_ = base::Process::Open(process_id); |
| + if (!window_process_.IsValid()) { |
| + // The process may be at a different security level. |
| + return false; |
| + } |
| + |
| + return MaybeSendPing(); |
| +} |
| + |
| +void WindowHangMonitor::OnPongReceived(LRESULT lresult) { |
| + // Outstanding, the window is not yet hung. |
|
erikwright (departed)
2015/03/25 18:57:21
har har.
Sigurður Ásgeirsson
2015/03/25 19:56:52
Acknowledged.
|
| + outstanding_ping_ = nullptr; |
| +} |
| + |
| +void WindowHangMonitor::SetPingIntervalForTesting( |
| + base::TimeDelta ping_interval) { |
| + ping_interval_ = ping_interval; |
| +} |
| + |
| +void WindowHangMonitor::SetHangTimeoutForTesting(base::TimeDelta hang_timeout) { |
| + hang_timeout_ = hang_timeout; |
| +} |
| + |
| +void CALLBACK WindowHangMonitor::SendAsyncProc(HWND window, |
| + UINT msg, |
| + ULONG_PTR data, |
| + LRESULT lresult) { |
| + OutstandingPing* outstanding = reinterpret_cast<OutstandingPing*>(data); |
| + |
| + // Delegate to the monitor if it's still around. |
| + if (outstanding->monitor) { |
| + outstanding->monitor->OnPongReceived(lresult); |
|
erikwright (departed)
2015/03/25 18:57:21
Why the indirection? This is a (static) member met
erikwright (departed)
2015/03/25 18:57:21
why pass the lresult?
Sigurður Ásgeirsson
2015/03/25 19:56:52
Yeah - indeed. I thought I'd have a use for it.
Sigurður Ásgeirsson
2015/03/25 19:56:53
Done.
|
| + } |
| + |
| + delete outstanding; |
|
erikwright (departed)
2015/03/25 18:57:21
I'd maybe put this in OnPongReceived
Sigurður Ásgeirsson
2015/03/25 19:56:52
This wants to be in the callback, as that way a la
|
| +} |
| + |
| +bool WindowHangMonitor::MaybeSendPing() { |
| + DCHECK(window_process_.IsValid()); |
|
erikwright (departed)
2015/03/25 18:57:21
include logging.h
Sigurður Ásgeirsson
2015/03/25 19:56:53
Done.
|
| + DCHECK(window_); |
| + DCHECK(!outstanding_ping_); |
| + |
| + if (!IsWindowValid(window_, window_name_, window_process_.Pid())) { |
| + // The window is no longer valid, issue the callback. |
| + callback_.Run(WINDOW_VANISHED); |
|
erikwright (departed)
2015/03/25 18:57:21
include callback.h
Sigurður Ásgeirsson
2015/03/25 19:56:53
Done.
|
| + return false; |
| + } |
| + |
| + // The window checks out, issue a ping against it. Set up all state ahead of |
| + // time to allow for the possibility of the callback being invoked from within |
| + // SendMessageCallback. |
| + outstanding_ping_ = new OutstandingPing; |
| + outstanding_ping_->monitor = this; |
| + |
| + // Note that this is still racy to |window_| having been re-assigned, but |
| + // the race is as small as we can make it, and the next attempt will re-try. |
| + if (!::SendMessageCallback(window_, WM_NULL, 0, 0, &SendAsyncProc, |
| + reinterpret_cast<ULONG_PTR>(outstanding_ping_))) { |
| + // Message sending failed, assume he window is no longer valid, |
|
erikwright (departed)
2015/03/25 18:57:21
he -> the
Sigurður Ásgeirsson
2015/03/25 19:56:53
Done.
|
| + // issue the callback and stop the polling. |
| + delete outstanding_ping_; |
| + outstanding_ping_ = nullptr; |
| + |
| + callback_.Run(WINDOW_VANISHED); |
| + return false; |
| + } |
| + |
| + // Issue the count-out callback. |
| + timer_.Start( |
| + FROM_HERE, hang_timeout_, |
| + base::Bind(&WindowHangMonitor::OnHangTimeout, base::Unretained(this))); |
| + |
| + return true; |
| +} |
| + |
| +void WindowHangMonitor::OnHangTimeout() { |
| + DCHECK(window_process_.IsValid()); |
| + DCHECK(window_); |
| + |
| + 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 |
| + // delete it, or if the callback never arrives it'll leak. |
| + outstanding_ping_->monitor = NULL; |
| + outstanding_ping_ = NULL; |
| + |
| + if (!IsWindowValid(window_, window_name_, window_process_.Pid())) { |
| + // The window vanished. |
| + callback_.Run(WINDOW_VANISHED); |
| + } else { |
| + // The window hung. |
| + callback_.Run(WINDOW_HUNG); |
| + } |
| + } else { |
| + // No ping outstanding, window is not yet hung. Schedule the next retry. |
| + timer_.Start( |
| + FROM_HERE, hang_timeout_ - ping_interval_, |
| + base::Bind(&WindowHangMonitor::OnRetryTimeout, base::Unretained(this))); |
| + } |
| +} |
| + |
| +void WindowHangMonitor::OnRetryTimeout() { |
| + MaybeSendPing(); |
|
erikwright (departed)
2015/03/25 18:57:21
why the indirection?
Sigurður Ásgeirsson
2015/03/25 19:56:52
For readability - IMHO marginally clearer than to
|
| +} |
| + |
| +} // namespace browser_watcher |