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

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

Powered by Google App Engine
This is Rietveld 408576698