Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2015 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 #include "components/browser_watcher/window_hang_monitor_win.h" | |
| 5 | |
| 6 #include "base/message_loop/message_loop.h" | |
| 7 #include "base/win/message_window.h" | |
| 8 | |
| 9 namespace browser_watcher { | |
| 10 | |
| 11 namespace { | |
| 12 | |
| 13 const size_t kPingIntervalSeconds = 60; | |
| 14 const size_t kHangTimeoutSeconds = 20; | |
| 15 | |
| 16 bool IsWindowValid(HWND window, | |
| 17 const base::string16& window_name, | |
| 18 base::ProcessId pid) { | |
| 19 // Validate the Window in two respects: | |
| 20 // 1. The window handle might have been re-assigned to a different window | |
| 21 // from the time we found it to the point where we query for its owning | |
| 22 // process. | |
| 23 // 2. The window handle might have been re-assigned to a different process | |
| 24 // at any point after we found it. | |
| 25 if (window != base::win::MessageWindow::FindWindow(window_name)) { | |
| 26 // The window handle has been reassigned, bail. | |
| 27 return false; | |
| 28 } | |
| 29 | |
| 30 // Re-do the process ID lookup. | |
| 31 DWORD new_process_id = 0; | |
| 32 DWORD thread_id = ::GetWindowThreadProcessId(window, &new_process_id); | |
| 33 if (thread_id == 0 || pid != new_process_id) { | |
| 34 // Another process has taken over the handle. | |
| 35 return false; | |
| 36 } | |
| 37 | |
| 38 return true; | |
| 39 } | |
| 40 | |
| 41 } // namespace | |
| 42 | |
| 43 WindowHangMonitor::WindowHangMonitor(const WindowEventCallback& callback) | |
| 44 : callback_(callback), | |
| 45 window_(NULL), | |
| 46 outstanding_ping_(nullptr), | |
| 47 timer_(false /* don't retain user task */, false /* don't repeat */), | |
| 48 ping_interval_(base::TimeDelta::FromSeconds(kPingIntervalSeconds)), | |
| 49 hang_timeout_(base::TimeDelta::FromSeconds(kHangTimeoutSeconds)) { | |
| 50 } | |
| 51 | |
| 52 WindowHangMonitor::~WindowHangMonitor() { | |
| 53 if (outstanding_ping_) { | |
| 54 // We have an outstanding ping, disable it and leak it intentionally as | |
| 55 // if the callback arrives eventually, it'll cause a use-after-free. | |
| 56 outstanding_ping_->monitor = nullptr; | |
| 57 outstanding_ping_ = nullptr; | |
| 58 } | |
| 59 } | |
| 60 | |
| 61 bool WindowHangMonitor::Initialize(const base::string16& window_name) { | |
| 62 window_name_ = window_name; | |
| 63 timer_.SetTaskRunner(base::MessageLoop::current()->task_runner()); | |
| 64 | |
| 65 // This code is fraught with all kinds of races. As the purpose here is | |
| 66 // only monitoring, this code simply bails if any kind of race is encountered. | |
| 67 // Find the window to monitor by name. | |
| 68 window_ = base::win::MessageWindow::FindWindow(window_name); | |
| 69 if (window_ == NULL) | |
| 70 return false; | |
| 71 | |
| 72 // Find and open the process owning this window. | |
| 73 DWORD process_id = 0; | |
| 74 DWORD thread_id = ::GetWindowThreadProcessId(window_, &process_id); | |
| 75 if (thread_id == 0 || process_id == 0) { | |
| 76 // The window has vanished or there was some other problem with the handle. | |
| 77 return false; | |
| 78 } | |
| 79 | |
| 80 // Keep an open handle on the process to make sure the PID isn't reused. | |
| 81 window_process_ = base::Process::Open(process_id); | |
| 82 if (!window_process_.IsValid()) { | |
| 83 // The process may be at a different security level. | |
| 84 return false; | |
| 85 } | |
| 86 | |
| 87 return MaybeSendPing(); | |
| 88 } | |
| 89 | |
| 90 void WindowHangMonitor::OnPongReceived(LRESULT lresult) { | |
| 91 // 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.
| |
| 92 outstanding_ping_ = nullptr; | |
| 93 } | |
| 94 | |
| 95 void WindowHangMonitor::SetPingIntervalForTesting( | |
| 96 base::TimeDelta ping_interval) { | |
| 97 ping_interval_ = ping_interval; | |
| 98 } | |
| 99 | |
| 100 void WindowHangMonitor::SetHangTimeoutForTesting(base::TimeDelta hang_timeout) { | |
| 101 hang_timeout_ = hang_timeout; | |
| 102 } | |
| 103 | |
| 104 void CALLBACK WindowHangMonitor::SendAsyncProc(HWND window, | |
| 105 UINT msg, | |
| 106 ULONG_PTR data, | |
| 107 LRESULT lresult) { | |
| 108 OutstandingPing* outstanding = reinterpret_cast<OutstandingPing*>(data); | |
| 109 | |
| 110 // Delegate to the monitor if it's still around. | |
| 111 if (outstanding->monitor) { | |
| 112 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.
| |
| 113 } | |
| 114 | |
| 115 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
| |
| 116 } | |
| 117 | |
| 118 bool WindowHangMonitor::MaybeSendPing() { | |
| 119 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.
| |
| 120 DCHECK(window_); | |
| 121 DCHECK(!outstanding_ping_); | |
| 122 | |
| 123 if (!IsWindowValid(window_, window_name_, window_process_.Pid())) { | |
| 124 // The window is no longer valid, issue the callback. | |
| 125 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.
| |
| 126 return false; | |
| 127 } | |
| 128 | |
| 129 // The window checks out, issue a ping against it. Set up all state ahead of | |
| 130 // time to allow for the possibility of the callback being invoked from within | |
| 131 // SendMessageCallback. | |
| 132 outstanding_ping_ = new OutstandingPing; | |
| 133 outstanding_ping_->monitor = this; | |
| 134 | |
| 135 // Note that this is still racy to |window_| having been re-assigned, but | |
| 136 // the race is as small as we can make it, and the next attempt will re-try. | |
| 137 if (!::SendMessageCallback(window_, WM_NULL, 0, 0, &SendAsyncProc, | |
| 138 reinterpret_cast<ULONG_PTR>(outstanding_ping_))) { | |
| 139 // 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.
| |
| 140 // issue the callback and stop the polling. | |
| 141 delete outstanding_ping_; | |
| 142 outstanding_ping_ = nullptr; | |
| 143 | |
| 144 callback_.Run(WINDOW_VANISHED); | |
| 145 return false; | |
| 146 } | |
| 147 | |
| 148 // Issue the count-out callback. | |
| 149 timer_.Start( | |
| 150 FROM_HERE, hang_timeout_, | |
| 151 base::Bind(&WindowHangMonitor::OnHangTimeout, base::Unretained(this))); | |
| 152 | |
| 153 return true; | |
| 154 } | |
| 155 | |
| 156 void WindowHangMonitor::OnHangTimeout() { | |
| 157 DCHECK(window_process_.IsValid()); | |
| 158 DCHECK(window_); | |
| 159 | |
| 160 if (outstanding_ping_) { | |
| 161 // The ping is still outstanding, the window is hung or has vanished. | |
| 162 // Orphan the outstanding ping. If the callback arrives late, it will | |
| 163 // delete it, or if the callback never arrives it'll leak. | |
| 164 outstanding_ping_->monitor = NULL; | |
| 165 outstanding_ping_ = NULL; | |
| 166 | |
| 167 if (!IsWindowValid(window_, window_name_, window_process_.Pid())) { | |
| 168 // The window vanished. | |
| 169 callback_.Run(WINDOW_VANISHED); | |
| 170 } else { | |
| 171 // The window hung. | |
| 172 callback_.Run(WINDOW_HUNG); | |
| 173 } | |
| 174 } else { | |
| 175 // No ping outstanding, window is not yet hung. Schedule the next retry. | |
| 176 timer_.Start( | |
| 177 FROM_HERE, hang_timeout_ - ping_interval_, | |
| 178 base::Bind(&WindowHangMonitor::OnRetryTimeout, base::Unretained(this))); | |
| 179 } | |
| 180 } | |
| 181 | |
| 182 void WindowHangMonitor::OnRetryTimeout() { | |
| 183 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
| |
| 184 } | |
| 185 | |
| 186 } // namespace browser_watcher | |
| OLD | NEW |