Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(769)

Side by Side Diff: components/browser_watcher/window_hang_monitor_win.cc

Issue 1036623002: Implements a monitor to watch for the browser hanging. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Finish testing, simplify callback. Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698