1 // Copyright (c) 2011 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
5 #include "base/threading/thread_restrictions.h"
6 #include "build/build_config.h"
7 #include "chrome/browser/metrics/metrics_service.h"
8 #include "chrome/browser/metrics/thread_watcher.h"
9 #include "content/common/notification_service.h"
10
11 #if defined(OS_WIN)
12 #include <Objbase.h>
13 #endif
14
15 // static
16 const int ThreadWatcher::kPingCount = 3;
17
18 // ThreadWatcher methods and members.
ThreadWatcher(const BrowserThread::ID & thread_id,const std::string & thread_name,const base::TimeDelta & sleep_time,const base::TimeDelta & unresponsive_time)19 ThreadWatcher::ThreadWatcher(const BrowserThread::ID& thread_id,
20 const std::string& thread_name,
21 const base::TimeDelta& sleep_time,
22 const base::TimeDelta& unresponsive_time)
23 : thread_id_(thread_id),
24 thread_name_(thread_name),
25 sleep_time_(sleep_time),
26 unresponsive_time_(unresponsive_time),
27 ping_time_(base::TimeTicks::Now()),
28 ping_sequence_number_(0),
29 active_(false),
30 ping_count_(kPingCount),
31 histogram_(NULL),
32 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
33 Initialize();
34 }
35
~ThreadWatcher()36 ThreadWatcher::~ThreadWatcher() {}
37
38 // static
StartWatching(const BrowserThread::ID & thread_id,const std::string & thread_name,const base::TimeDelta & sleep_time,const base::TimeDelta & unresponsive_time)39 void ThreadWatcher::StartWatching(const BrowserThread::ID& thread_id,
40 const std::string& thread_name,
41 const base::TimeDelta& sleep_time,
42 const base::TimeDelta& unresponsive_time) {
43 DCHECK_GE(sleep_time.InMilliseconds(), 0);
44 DCHECK_GE(unresponsive_time.InMilliseconds(), sleep_time.InMilliseconds());
45
46 // If we are not on WatchDogThread, then post a task to call StartWatching on
47 // WatchDogThread.
48 if (!WatchDogThread::CurrentlyOnWatchDogThread()) {
49 WatchDogThread::PostTask(
50 FROM_HERE,
51 NewRunnableFunction(
52 &ThreadWatcher::StartWatching,
53 thread_id, thread_name, sleep_time, unresponsive_time));
54 return;
55 }
56
57 DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
58
59 // Create a new thread watcher object for the given thread and activate it.
60 ThreadWatcher* watcher =
61 new ThreadWatcher(thread_id, thread_name, sleep_time, unresponsive_time);
62 DCHECK(watcher);
63 // If we couldn't register the thread watcher object, we are shutting down,
64 // then don't activate thread watching.
65 if (!ThreadWatcherList::IsRegistered(thread_id))
66 return;
67 watcher->ActivateThreadWatching();
68 }
69
ActivateThreadWatching()70 void ThreadWatcher::ActivateThreadWatching() {
71 DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
72 if (active_) return;
73 active_ = true;
74 ping_count_ = kPingCount;
75 MessageLoop::current()->PostTask(
76 FROM_HERE,
77 method_factory_.NewRunnableMethod(&ThreadWatcher::PostPingMessage));
78 }
79
DeActivateThreadWatching()80 void ThreadWatcher::DeActivateThreadWatching() {
81 DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
82 active_ = false;
83 ping_count_ = 0;
84 method_factory_.RevokeAll();
85 }
86
WakeUp()87 void ThreadWatcher::WakeUp() {
88 DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
89 // There is some user activity, PostPingMessage task of thread watcher if
90 // needed.
91 if (!active_) return;
92
93 if (ping_count_ <= 0) {
94 ping_count_ = kPingCount;
95 PostPingMessage();
96 } else {
97 ping_count_ = kPingCount;
98 }
99 }
100
PostPingMessage()101 void ThreadWatcher::PostPingMessage() {
102 DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
103 // If we have stopped watching or if the user is idle, then stop sending
104 // ping messages.
105 if (!active_ || ping_count_ <= 0)
106 return;
107
108 // Save the current time when we have sent ping message.
109 ping_time_ = base::TimeTicks::Now();
110
111 // Send a ping message to the watched thread.
112 Task* callback_task = method_factory_.NewRunnableMethod(
113 &ThreadWatcher::OnPongMessage, ping_sequence_number_);
114 if (BrowserThread::PostTask(
115 thread_id(),
116 FROM_HERE,
117 NewRunnableFunction(
118 &ThreadWatcher::OnPingMessage, thread_id_, callback_task))) {
119 // Post a task to check the responsiveness of watched thread.
120 MessageLoop::current()->PostDelayedTask(
121 FROM_HERE,
122 method_factory_.NewRunnableMethod(
123 &ThreadWatcher::OnCheckResponsiveness, ping_sequence_number_),
124 unresponsive_time_.InMilliseconds());
125 } else {
126 // Watched thread might have gone away, stop watching it.
127 delete callback_task;
128 DeActivateThreadWatching();
129 }
130 }
131
OnPongMessage(uint64 ping_sequence_number)132 void ThreadWatcher::OnPongMessage(uint64 ping_sequence_number) {
133 DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
134 // Record watched thread's response time.
135 base::TimeDelta response_time = base::TimeTicks::Now() - ping_time_;
136 histogram_->AddTime(response_time);
137
138 // Check if there are any extra pings in flight.
139 DCHECK_EQ(ping_sequence_number_, ping_sequence_number);
140 if (ping_sequence_number_ != ping_sequence_number)
141 return;
142
143 // Increment sequence number for the next ping message to indicate watched
144 // thread is responsive.
145 ++ping_sequence_number_;
146
147 // If we have stopped watching or if the user is idle, then stop sending
148 // ping messages.
149 if (!active_ || --ping_count_ <= 0)
150 return;
151
152 MessageLoop::current()->PostDelayedTask(
153 FROM_HERE,
154 method_factory_.NewRunnableMethod(&ThreadWatcher::PostPingMessage),
155 sleep_time_.InMilliseconds());
156 }
157
OnCheckResponsiveness(uint64 ping_sequence_number)158 bool ThreadWatcher::OnCheckResponsiveness(uint64 ping_sequence_number) {
159 DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
160 // If we have stopped watching then consider thread as responding.
161 if (!active_)
162 return true;
163 // If the latest ping_sequence_number_ is not same as the ping_sequence_number
164 // that is passed in, then we can assume OnPongMessage was called.
165 // OnPongMessage increments ping_sequence_number_.
166 return ping_sequence_number_ != ping_sequence_number;
167 }
168
Initialize()169 void ThreadWatcher::Initialize() {
170 ThreadWatcherList::Register(this);
171 const std::string histogram_name =
172 "ThreadWatcher.ResponseTime." + thread_name_;
173 histogram_ = base::Histogram::FactoryTimeGet(
174 histogram_name,
175 base::TimeDelta::FromMilliseconds(1),
176 base::TimeDelta::FromSeconds(100), 50,
177 base::Histogram::kUmaTargetedHistogramFlag);
178 }
179
180 // static
OnPingMessage(const BrowserThread::ID & thread_id,Task * callback_task)181 void ThreadWatcher::OnPingMessage(const BrowserThread::ID& thread_id,
182 Task* callback_task) {
183 // This method is called on watched thread.
184 DCHECK(BrowserThread::CurrentlyOn(thread_id));
185 WatchDogThread::PostTask(FROM_HERE, callback_task);
186 }
187
188 // ThreadWatcherList methods and members.
189 //
190 // static
191 ThreadWatcherList* ThreadWatcherList::global_ = NULL;
192
ThreadWatcherList()193 ThreadWatcherList::ThreadWatcherList()
194 : last_wakeup_time_(base::TimeTicks::Now()) {
195 // Assert we are not running on WATCHDOG thread. Would be ideal to assert we
196 // are on UI thread, but Unit tests are not running on UI thread.
197 DCHECK(!WatchDogThread::CurrentlyOnWatchDogThread());
198 CHECK(!global_);
199 global_ = this;
200 // Register Notifications observer.
201 MetricsService::SetUpNotifications(®istrar_, this);
202 }
203
~ThreadWatcherList()204 ThreadWatcherList::~ThreadWatcherList() {
205 base::AutoLock auto_lock(lock_);
206 DCHECK(this == global_);
207 global_ = NULL;
208 }
209
210 // static
Register(ThreadWatcher * watcher)211 void ThreadWatcherList::Register(ThreadWatcher* watcher) {
212 if (!global_)
213 return;
214 base::AutoLock auto_lock(global_->lock_);
215 DCHECK(!global_->PreLockedFind(watcher->thread_id()));
216 global_->registered_[watcher->thread_id()] = watcher;
217 }
218
219 // static
IsRegistered(const BrowserThread::ID thread_id)220 bool ThreadWatcherList::IsRegistered(const BrowserThread::ID thread_id) {
221 return NULL != ThreadWatcherList::Find(thread_id);
222 }
223
224 // static
StartWatchingAll()225 void ThreadWatcherList::StartWatchingAll() {
226 if (!WatchDogThread::CurrentlyOnWatchDogThread()) {
227 WatchDogThread::PostDelayedTask(
228 FROM_HERE,
229 NewRunnableFunction(&ThreadWatcherList::StartWatchingAll),
230 base::TimeDelta::FromSeconds(5).InMilliseconds());
231 return;
232 }
233 DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
234 const base::TimeDelta kSleepTime = base::TimeDelta::FromSeconds(5);
235 const base::TimeDelta kUnresponsiveTime = base::TimeDelta::FromSeconds(10);
236 if (BrowserThread::IsMessageLoopValid(BrowserThread::UI)) {
237 ThreadWatcher::StartWatching(BrowserThread::UI, "UI", kSleepTime,
238 kUnresponsiveTime);
239 }
240 if (BrowserThread::IsMessageLoopValid(BrowserThread::IO)) {
241 ThreadWatcher::StartWatching(BrowserThread::IO, "IO", kSleepTime,
242 kUnresponsiveTime);
243 }
244 if (BrowserThread::IsMessageLoopValid(BrowserThread::DB)) {
245 ThreadWatcher::StartWatching(BrowserThread::DB, "DB", kSleepTime,
246 kUnresponsiveTime);
247 }
248 if (BrowserThread::IsMessageLoopValid(BrowserThread::FILE)) {
249 ThreadWatcher::StartWatching(BrowserThread::FILE, "FILE", kSleepTime,
250 kUnresponsiveTime);
251 }
252 if (BrowserThread::IsMessageLoopValid(BrowserThread::CACHE)) {
253 ThreadWatcher::StartWatching(BrowserThread::CACHE, "CACHE", kSleepTime,
254 kUnresponsiveTime);
255 }
256 }
257
258 // static
StopWatchingAll()259 void ThreadWatcherList::StopWatchingAll() {
260 // Assert we are not running on WATCHDOG thread. Would be ideal to assert we
261 // are on UI thread, but Unit tests are not running on UI thread.
262 DCHECK(!WatchDogThread::CurrentlyOnWatchDogThread());
263 if (!global_)
264 return;
265
266 // Remove all notifications for all watched threads.
267 RemoveNotifications();
268
269 // Delete all thread watcher objects on WatchDogThread.
270 WatchDogThread::PostTask(
271 FROM_HERE,
272 NewRunnableMethod(global_, &ThreadWatcherList::DeleteAll));
273 }
274
275 // static
RemoveNotifications()276 void ThreadWatcherList::RemoveNotifications() {
277 // Assert we are not running on WATCHDOG thread. Would be ideal to assert we
278 // are on UI thread, but Unit tests are not running on UI thread.
279 DCHECK(!WatchDogThread::CurrentlyOnWatchDogThread());
280 if (!global_)
281 return;
282 base::AutoLock auto_lock(global_->lock_);
283 global_->registrar_.RemoveAll();
284 }
285
DeleteAll()286 void ThreadWatcherList::DeleteAll() {
287 DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
288 base::AutoLock auto_lock(lock_);
289 while (!registered_.empty()) {
290 RegistrationList::iterator it = registered_.begin();
291 delete it->second;
292 registered_.erase(it->first);
293 }
294 }
295
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)296 void ThreadWatcherList::Observe(NotificationType type,
297 const NotificationSource& source,
298 const NotificationDetails& details) {
299 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
300 // There is some user activity, see if thread watchers are to be awakened.
301 bool need_to_awaken = false;
302 base::TimeTicks now = base::TimeTicks::Now();
303 {
304 base::AutoLock lock(lock_);
305 if (now - last_wakeup_time_ > base::TimeDelta::FromSeconds(2)) {
306 need_to_awaken = true;
307 last_wakeup_time_ = now;
308 }
309 }
310 if (need_to_awaken) {
311 WatchDogThread::PostTask(
312 FROM_HERE,
313 NewRunnableMethod(this, &ThreadWatcherList::WakeUpAll));
314 }
315 }
316
WakeUpAll()317 void ThreadWatcherList::WakeUpAll() {
318 DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
319 if (!global_)
320 return;
321 base::AutoLock auto_lock(lock_);
322 for (RegistrationList::iterator it = global_->registered_.begin();
323 global_->registered_.end() != it;
324 ++it)
325 it->second->WakeUp();
326 }
327
328 // static
Find(const BrowserThread::ID & thread_id)329 ThreadWatcher* ThreadWatcherList::Find(const BrowserThread::ID& thread_id) {
330 if (!global_)
331 return NULL;
332 base::AutoLock auto_lock(global_->lock_);
333 return global_->PreLockedFind(thread_id);
334 }
335
PreLockedFind(const BrowserThread::ID & thread_id)336 ThreadWatcher* ThreadWatcherList::PreLockedFind(
337 const BrowserThread::ID& thread_id) {
338 RegistrationList::iterator it = registered_.find(thread_id);
339 if (registered_.end() == it)
340 return NULL;
341 return it->second;
342 }
343
344 // WatchDogThread methods and members.
345 //
346 // static
347 base::Lock WatchDogThread::lock_;
348 // static
349 WatchDogThread* WatchDogThread::watchdog_thread_ = NULL;
350
351 // The WatchDogThread object must outlive any tasks posted to the IO thread
352 // before the Quit task.
353 DISABLE_RUNNABLE_METHOD_REFCOUNT(WatchDogThread);
354
WatchDogThread()355 WatchDogThread::WatchDogThread() : Thread("WATCHDOG") {
356 }
357
~WatchDogThread()358 WatchDogThread::~WatchDogThread() {
359 // We cannot rely on our base class to stop the thread since we want our
360 // CleanUp function to run.
361 Stop();
362 }
363
364 // static
CurrentlyOnWatchDogThread()365 bool WatchDogThread::CurrentlyOnWatchDogThread() {
366 base::AutoLock lock(lock_);
367 return watchdog_thread_ &&
368 watchdog_thread_->message_loop() == MessageLoop::current();
369 }
370
371 // static
PostTask(const tracked_objects::Location & from_here,Task * task)372 bool WatchDogThread::PostTask(const tracked_objects::Location& from_here,
373 Task* task) {
374 return PostTaskHelper(from_here, task, 0);
375 }
376
377 // static
PostDelayedTask(const tracked_objects::Location & from_here,Task * task,int64 delay_ms)378 bool WatchDogThread::PostDelayedTask(const tracked_objects::Location& from_here,
379 Task* task,
380 int64 delay_ms) {
381 return PostTaskHelper(from_here, task, delay_ms);
382 }
383
384 // static
PostTaskHelper(const tracked_objects::Location & from_here,Task * task,int64 delay_ms)385 bool WatchDogThread::PostTaskHelper(
386 const tracked_objects::Location& from_here,
387 Task* task,
388 int64 delay_ms) {
389 {
390 base::AutoLock lock(lock_);
391
392 MessageLoop* message_loop = watchdog_thread_ ?
393 watchdog_thread_->message_loop() : NULL;
394 if (message_loop) {
395 message_loop->PostDelayedTask(from_here, task, delay_ms);
396 return true;
397 }
398 }
399 delete task;
400
401 return false;
402 }
403
Init()404 void WatchDogThread::Init() {
405 // This thread shouldn't be allowed to perform any blocking disk I/O.
406 base::ThreadRestrictions::SetIOAllowed(false);
407
408 #if defined(OS_WIN)
409 // Initializes the COM library on the current thread.
410 HRESULT result = CoInitialize(NULL);
411 CHECK(result == S_OK);
412 #endif
413
414 base::AutoLock lock(lock_);
415 CHECK(!watchdog_thread_);
416 watchdog_thread_ = this;
417 }
418
CleanUp()419 void WatchDogThread::CleanUp() {
420 base::AutoLock lock(lock_);
421 watchdog_thread_ = NULL;
422 }
423
CleanUpAfterMessageLoopDestruction()424 void WatchDogThread::CleanUpAfterMessageLoopDestruction() {
425 #if defined(OS_WIN)
426 // Closes the COM library on the current thread. CoInitialize must
427 // be balanced by a corresponding call to CoUninitialize.
428 CoUninitialize();
429 #endif
430 }
431