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 |