blob: 93f70d7a0bb73328fee4c9c839647299449721cc [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/metrics/thread_watcher.h"
#include <math.h>
#include <stdint.h>
#include <memory>
#include "base/bind.h"
#include "base/cancelable_callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_tokenizer.h"
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/post_task.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/common/chrome_switches.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/test/test_browser_thread.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
using base::TimeDelta;
using base::TimeTicks;
using content::BrowserThread;
enum State {
INITIALIZED, // Created ThreadWatch object.
ACTIVATED, // Thread watching activated.
SENT_PING, // Sent ping message to watched thread.
RECEIVED_PONG, // Received Pong message.
DEACTIVATED, // Thread watching de-activated.
};
enum WaitState {
UNINITIALIZED,
STARTED_WAITING, // Start waiting for state_ to change to expected_state.
STOPPED_WAITING, // Done with the waiting.
ALL_DONE, // Done with waiting for STOPPED_WAITING.
};
enum CheckResponseState {
UNKNOWN,
SUCCESSFUL, // CheckResponse was successful.
FAILED, // CheckResponse has failed.
};
// This class helps to track and manipulate thread state during tests. This
// class also has utility method to simulate hanging of watched thread by making
// the watched thread wait for a very long time by posting a task on watched
// thread that keeps it busy. It also has an utility method to block running of
// tests until ThreadWatcher object's post-condition state changes to an
// expected state.
class CustomThreadWatcher : public ThreadWatcher {
public:
State thread_watcher_state_;
// Wait state may be accessed from VeryLongMethod on another thread.
base::Lock wait_state_lock_;
base::ConditionVariable wait_state_changed_;
WaitState wait_state_;
CheckResponseState check_response_state_;
uint64_t ping_sent_;
uint64_t pong_received_;
int32_t success_response_;
int32_t failed_response_;
base::TimeTicks saved_ping_time_;
uint64_t saved_ping_sequence_number_;
base::RepeatingClosure on_state_changed_;
CustomThreadWatcher(const BrowserThread::ID thread_id,
const std::string thread_name,
const TimeDelta& sleep_time,
const TimeDelta& unresponsive_time)
: ThreadWatcher(WatchingParams(thread_id,
thread_name,
sleep_time,
unresponsive_time,
ThreadWatcherList::kUnresponsiveCount,
true)),
thread_watcher_state_(INITIALIZED),
wait_state_changed_(&wait_state_lock_),
wait_state_(UNINITIALIZED),
check_response_state_(UNKNOWN),
ping_sent_(0),
pong_received_(0),
success_response_(0),
failed_response_(0),
saved_ping_time_(base::TimeTicks::Now()),
saved_ping_sequence_number_(0) {}
State UpdateState(State new_state) {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
State old_state = thread_watcher_state_;
if (old_state != DEACTIVATED)
thread_watcher_state_ = new_state;
if (new_state == SENT_PING)
++ping_sent_;
if (new_state == RECEIVED_PONG)
++pong_received_;
saved_ping_time_ = ping_time();
saved_ping_sequence_number_ = ping_sequence_number();
OnStateChanged();
return old_state;
}
void UpdateWaitState(WaitState new_state) {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
{
base::AutoLock auto_lock(wait_state_lock_);
wait_state_ = new_state;
}
wait_state_changed_.Broadcast();
OnStateChanged();
}
void ActivateThreadWatching() override {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
State old_state = UpdateState(ACTIVATED);
EXPECT_EQ(old_state, INITIALIZED);
ThreadWatcher::ActivateThreadWatching();
}
void DeActivateThreadWatching() override {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
State old_state = UpdateState(DEACTIVATED);
EXPECT_TRUE(old_state == ACTIVATED || old_state == SENT_PING ||
old_state == RECEIVED_PONG);
ThreadWatcher::DeActivateThreadWatching();
}
void PostPingMessage() override {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
State old_state = UpdateState(SENT_PING);
EXPECT_TRUE(old_state == ACTIVATED || old_state == RECEIVED_PONG);
ThreadWatcher::PostPingMessage();
}
void OnPongMessage(uint64_t ping_sequence_number) override {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
State old_state = UpdateState(RECEIVED_PONG);
EXPECT_TRUE(old_state == SENT_PING || old_state == DEACTIVATED);
ThreadWatcher::OnPongMessage(ping_sequence_number);
}
void OnCheckResponsiveness(uint64_t ping_sequence_number) override {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
ThreadWatcher::OnCheckResponsiveness(ping_sequence_number);
if (responsive_) {
++success_response_;
check_response_state_ = SUCCESSFUL;
} else {
++failed_response_;
check_response_state_ = FAILED;
}
OnStateChanged();
}
void WaitForWaitStateChange(TimeDelta wait_time, WaitState expected_state) {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
on_state_changed_ = base::BindRepeating(
[](CustomThreadWatcher* watcher, base::RepeatingClosure quit_closure,
WaitState expected_state) {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
// No need to acquire wait_state_lock_ since we're on the same thread
// that modifies wait_state_.
if (watcher->wait_state_ == expected_state)
quit_closure.Run();
},
base::Unretained(this), quit_closure, expected_state);
base::CancelableClosure timeout_closure(base::BindRepeating(
[](base::RepeatingClosure quit_closure) {
FAIL() << "WaitForWaitStateChange timed out";
quit_closure.Run();
},
quit_closure));
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, timeout_closure.callback(), wait_time);
run_loop.Run();
on_state_changed_.Reset();
}
// May be called on any thread other than the WatchDogThread.
void BusyWaitForWaitStateChange(TimeDelta wait_time,
WaitState expected_state) {
DCHECK(!WatchDogThread::CurrentlyOnWatchDogThread());
TimeTicks end_time = TimeTicks::Now() + wait_time;
{
base::AutoLock auto_lock(wait_state_lock_);
while (wait_state_ != expected_state && TimeTicks::Now() < end_time)
wait_state_changed_.TimedWait(end_time - TimeTicks::Now());
}
}
void VeryLongMethod(TimeDelta wait_time) {
DCHECK(!WatchDogThread::CurrentlyOnWatchDogThread());
// ThreadWatcher tasks should not be allowed to execute while we're waiting,
// so hog the thread until the state changes.
BusyWaitForWaitStateChange(wait_time, STOPPED_WAITING);
WatchDogThread::PostTask(
FROM_HERE, base::BindRepeating(&CustomThreadWatcher::UpdateWaitState,
base::Unretained(this), ALL_DONE));
}
State WaitForStateChange(const TimeDelta& wait_time, State expected_state) {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
UpdateWaitState(STARTED_WAITING);
// Keep the watch dog thread looping until the state changes to the
// expected_state or until wait_time elapses enough times for the
// unresponsive threshold to be reached.
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
State exit_state = INITIALIZED;
on_state_changed_ = base::BindRepeating(
[](CustomThreadWatcher* watcher, base::RepeatingClosure quit_closure,
State expected_state, State* exit_state) {
*exit_state = watcher->thread_watcher_state_;
if (watcher->thread_watcher_state_ == expected_state)
quit_closure.Run();
},
base::Unretained(this), quit_closure, expected_state,
base::Unretained(&exit_state));
base::CancelableClosure timeout_closure(base::BindRepeating(
[](base::RepeatingClosure quit_closure) { quit_closure.Run(); },
quit_closure));
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, timeout_closure.callback(),
wait_time * unresponsive_threshold_);
run_loop.Run();
on_state_changed_.Reset();
UpdateWaitState(STOPPED_WAITING);
return exit_state;
}
CheckResponseState WaitForCheckResponse(const TimeDelta& wait_time,
CheckResponseState expected_state) {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
UpdateWaitState(STARTED_WAITING);
// Keep the watch dog thread looping until the state changes to the
// expected_state or until wait_time elapses enough times for the
// unresponsive threshold to be reached.
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
CheckResponseState exit_state = UNKNOWN;
on_state_changed_ = base::BindRepeating(
[](CustomThreadWatcher* watcher, base::RepeatingClosure quit_closure,
CheckResponseState expected_state, CheckResponseState* exit_state) {
*exit_state = watcher->check_response_state_;
if (watcher->check_response_state_ == expected_state)
quit_closure.Run();
},
base::Unretained(this), quit_closure, expected_state,
base::Unretained(&exit_state));
base::CancelableClosure timeout_closure(base::BindRepeating(
[](base::RepeatingClosure quit_closure) { quit_closure.Run(); },
quit_closure));
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, timeout_closure.callback(),
wait_time * unresponsive_threshold_);
run_loop.Run();
on_state_changed_.Reset();
UpdateWaitState(STOPPED_WAITING);
return exit_state;
}
void OnStateChanged() {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
if (on_state_changed_)
on_state_changed_.Run();
}
};
class ThreadWatcherTest : public ::testing::Test {
public:
static constexpr TimeDelta kSleepTime = TimeDelta::FromMilliseconds(50);
static constexpr TimeDelta kUnresponsiveTime =
TimeDelta::FromMilliseconds(500);
static constexpr char kIOThreadName[] = "IO";
static constexpr char kUIThreadName[] = "UI";
static constexpr char kCrashOnHangThreadNames[] = "UI,IO";
static constexpr char kCrashOnHangThreadData[] = "UI:12,IO:12";
CustomThreadWatcher* io_watcher_;
CustomThreadWatcher* ui_watcher_;
ThreadWatcherList* thread_watcher_list_;
ThreadWatcherTest()
: thread_bundle_(content::TestBrowserThreadBundle::REAL_IO_THREAD),
setup_complete_(&lock_),
initialized_(false) {
// Make sure UI and IO threads are started and ready.
thread_bundle_.RunIOThreadUntilIdle();
watchdog_thread_.reset(new WatchDogThread());
watchdog_thread_->StartAndWaitForTesting();
WatchDogThread::PostTask(
FROM_HERE, base::BindRepeating(&ThreadWatcherTest::SetUpObjects,
base::Unretained(this)));
WaitForSetUp(TimeDelta::FromMinutes(1));
}
void SetUpObjects() {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
// Setup the registry for thread watchers.
thread_watcher_list_ = new ThreadWatcherList();
// Create thread watcher object for the IO thread.
std::unique_ptr<CustomThreadWatcher> io_watcher(new CustomThreadWatcher(
BrowserThread::IO, kIOThreadName, kSleepTime, kUnresponsiveTime));
io_watcher_ = io_watcher.get();
ThreadWatcher* registered_io_watcher =
ThreadWatcherList::Register(std::move(io_watcher));
EXPECT_EQ(io_watcher_, registered_io_watcher);
EXPECT_EQ(io_watcher_, thread_watcher_list_->Find(BrowserThread::IO));
// Create thread watcher object for the UI thread.
std::unique_ptr<CustomThreadWatcher> ui_watcher(new CustomThreadWatcher(
BrowserThread::UI, kUIThreadName, kSleepTime, kUnresponsiveTime));
ui_watcher_ = ui_watcher.get();
ThreadWatcher* registered_ui_watcher =
ThreadWatcherList::Register(std::move(ui_watcher));
EXPECT_EQ(ui_watcher_, registered_ui_watcher);
EXPECT_EQ(ui_watcher_, thread_watcher_list_->Find(BrowserThread::UI));
{
base::AutoLock lock(lock_);
initialized_ = true;
}
setup_complete_.Signal();
}
void WaitForSetUp(TimeDelta wait_time) {
DCHECK(!WatchDogThread::CurrentlyOnWatchDogThread());
TimeTicks end_time = TimeTicks::Now() + wait_time;
{
base::AutoLock auto_lock(lock_);
while (!initialized_ && TimeTicks::Now() < end_time)
setup_complete_.TimedWait(end_time - TimeTicks::Now());
}
}
~ThreadWatcherTest() override {
ThreadWatcherList::DeleteAll();
io_watcher_ = nullptr;
ui_watcher_ = nullptr;
watchdog_thread_.reset();
thread_watcher_list_ = nullptr;
}
private:
content::TestBrowserThreadBundle thread_bundle_;
base::Lock lock_;
base::ConditionVariable setup_complete_;
bool initialized_;
std::unique_ptr<WatchDogThread> watchdog_thread_;
DISALLOW_COPY_AND_ASSIGN(ThreadWatcherTest);
};
// Test fixture that runs a test body on the WatchDogThread. Subclasses override
// TestBodyOnWatchDogThread() and should call RunTestOnWatchDogThread() in their
// TEST_F() declaration.
class ThreadWatcherTestOnWatchDogThread : public ThreadWatcherTest {
public:
ThreadWatcherTestOnWatchDogThread()
: test_body_run_loop_(base::RunLoop::Type::kNestableTasksAllowed) {}
protected:
void RunTestOnWatchDogThread() {
WatchDogThread::PostTask(FROM_HERE,
base::BindRepeating(
[](ThreadWatcherTestOnWatchDogThread* test) {
test->TestBodyOnWatchDogThread();
test->test_body_run_loop_.Quit();
},
base::Unretained(this)));
test_body_run_loop_.Run();
}
virtual void TestBodyOnWatchDogThread() = 0;
protected:
base::RunLoop test_body_run_loop_;
};
// Declare storage for ThreadWatcherTest's static constants.
constexpr TimeDelta ThreadWatcherTest::kSleepTime;
constexpr TimeDelta ThreadWatcherTest::kUnresponsiveTime;
constexpr char ThreadWatcherTest::kIOThreadName[];
constexpr char ThreadWatcherTest::kUIThreadName[];
constexpr char ThreadWatcherTest::kCrashOnHangThreadNames[];
constexpr char ThreadWatcherTest::kCrashOnHangThreadData[];
TEST_F(ThreadWatcherTest, ThreadNamesOnlyArgs) {
// Setup command_line arguments.
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
command_line.AppendSwitchASCII(switches::kCrashOnHangThreads,
kCrashOnHangThreadNames);
// Parse command_line arguments.
ThreadWatcherList::CrashOnHangThreadMap crash_on_hang_threads;
uint32_t unresponsive_threshold;
ThreadWatcherList::ParseCommandLine(command_line,
&unresponsive_threshold,
&crash_on_hang_threads);
// Verify the data.
base::CStringTokenizer tokens(
kCrashOnHangThreadNames,
kCrashOnHangThreadNames + (base::size(kCrashOnHangThreadNames) - 1), ",");
while (tokens.GetNext()) {
std::vector<base::StringPiece> values = base::SplitStringPiece(
tokens.token_piece(), ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
std::string thread_name = values[0].as_string();
auto it = crash_on_hang_threads.find(thread_name);
bool crash_on_hang = (it != crash_on_hang_threads.end());
EXPECT_TRUE(crash_on_hang);
EXPECT_LT(0u, it->second);
}
}
TEST_F(ThreadWatcherTest, CrashOnHangThreadsAllArgs) {
// Setup command_line arguments.
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
command_line.AppendSwitchASCII(switches::kCrashOnHangThreads,
kCrashOnHangThreadData);
// Parse command_line arguments.
ThreadWatcherList::CrashOnHangThreadMap crash_on_hang_threads;
uint32_t unresponsive_threshold;
ThreadWatcherList::ParseCommandLine(command_line,
&unresponsive_threshold,
&crash_on_hang_threads);
// Verify the data.
base::CStringTokenizer tokens(
kCrashOnHangThreadData,
kCrashOnHangThreadData + (base::size(kCrashOnHangThreadData) - 1), ",");
while (tokens.GetNext()) {
std::vector<base::StringPiece> values = base::SplitStringPiece(
tokens.token_piece(), ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
std::string thread_name = values[0].as_string();
auto it = crash_on_hang_threads.find(thread_name);
bool crash_on_hang = (it != crash_on_hang_threads.end());
EXPECT_TRUE(crash_on_hang);
uint32_t crash_unresponsive_threshold = it->second;
uint32_t crash_on_unresponsive_seconds =
ThreadWatcherList::kUnresponsiveSeconds * crash_unresponsive_threshold;
EXPECT_EQ(12u, crash_on_unresponsive_seconds);
}
}
// Test registration. When thread_watcher_list_ goes out of scope after
// TearDown, all thread watcher objects will be deleted.
class ThreadWatcherTestRegistration : public ThreadWatcherTestOnWatchDogThread {
protected:
void TestBodyOnWatchDogThread() override {
// Check ThreadWatcher object has all correct parameters.
EXPECT_EQ(BrowserThread::IO, io_watcher_->thread_id());
EXPECT_EQ(kIOThreadName, io_watcher_->thread_name());
EXPECT_EQ(kSleepTime, io_watcher_->sleep_time());
EXPECT_EQ(kUnresponsiveTime, io_watcher_->unresponsive_time());
EXPECT_FALSE(io_watcher_->active());
// Check ThreadWatcher object of watched UI thread has correct data.
EXPECT_EQ(BrowserThread::UI, ui_watcher_->thread_id());
EXPECT_EQ(kUIThreadName, ui_watcher_->thread_name());
EXPECT_EQ(kSleepTime, ui_watcher_->sleep_time());
EXPECT_EQ(kUnresponsiveTime, ui_watcher_->unresponsive_time());
EXPECT_FALSE(ui_watcher_->active());
}
};
TEST_F(ThreadWatcherTestRegistration, RunTest) {
RunTestOnWatchDogThread();
}
// Test ActivateThreadWatching and DeActivateThreadWatching of IO thread. This
// method also checks that pong message was sent by the watched thread and pong
// message was received by the WatchDogThread. It also checks that
// OnCheckResponsiveness has verified the ping-pong mechanism and the watched
// thread is not hung.
class ThreadWatcherTestThreadResponding
: public ThreadWatcherTestOnWatchDogThread {
protected:
void TestBodyOnWatchDogThread() override {
TimeTicks time_before_ping = TimeTicks::Now();
// Activate watching IO thread.
io_watcher_->ActivateThreadWatching();
// Activate would have started ping/pong messaging. Expect at least one
// ping/pong messaging sequence to happen.
io_watcher_->WaitForStateChange(kSleepTime + TimeDelta::FromMinutes(1),
RECEIVED_PONG);
EXPECT_GT(io_watcher_->ping_sent_, static_cast<uint64_t>(0));
EXPECT_GT(io_watcher_->pong_received_, static_cast<uint64_t>(0));
EXPECT_TRUE(io_watcher_->active());
EXPECT_GE(io_watcher_->saved_ping_time_, time_before_ping);
EXPECT_GE(io_watcher_->saved_ping_sequence_number_,
static_cast<uint64_t>(0));
// Verify watched thread is responding with ping/pong messaging.
io_watcher_->WaitForCheckResponse(
kUnresponsiveTime + TimeDelta::FromMinutes(1), SUCCESSFUL);
EXPECT_GT(io_watcher_->success_response_, 0);
EXPECT_EQ(io_watcher_->failed_response_, 0);
// DeActivate thread watching for shutdown.
io_watcher_->DeActivateThreadWatching();
}
};
TEST_F(ThreadWatcherTestThreadResponding, RunTest) {
RunTestOnWatchDogThread();
}
// This test posts a task on watched thread that takes very long time (this is
// to simulate hanging of watched thread). It then checks for
// OnCheckResponsiveness raising an alert (OnCheckResponsiveness returns false
// if the watched thread is not responding).
class ThreadWatcherTestThreadNotResponding
: public ThreadWatcherTestOnWatchDogThread {
protected:
void TestBodyOnWatchDogThread() override {
// Simulate hanging of watched thread by making the watched thread wait for
// a very long time by posting a task on watched thread that keeps it busy.
// It is safe to use base::Unretained because test is waiting for the method
// to finish.
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&CustomThreadWatcher::VeryLongMethod,
base::Unretained(io_watcher_), kUnresponsiveTime * 10));
// Activate thread watching.
io_watcher_->ActivateThreadWatching();
// Verify watched thread is not responding for ping messages.
io_watcher_->WaitForCheckResponse(
kUnresponsiveTime + TimeDelta::FromMinutes(1), FAILED);
EXPECT_EQ(io_watcher_->success_response_, 0);
EXPECT_GT(io_watcher_->failed_response_, 0);
// DeActivate thread watching for shutdown.
io_watcher_->DeActivateThreadWatching();
// Wait for the io_watcher_'s VeryLongMethod to finish.
io_watcher_->WaitForWaitStateChange(kUnresponsiveTime * 10, ALL_DONE);
}
};
TEST_F(ThreadWatcherTestThreadNotResponding, RunTest) {
RunTestOnWatchDogThread();
}
// Test watching of multiple threads with all threads not responding.
class ThreadWatcherTestMultipleThreadsResponding
: public ThreadWatcherTestOnWatchDogThread {
protected:
void TestBodyOnWatchDogThread() override {
// Check for UI thread to perform ping/pong messaging.
ui_watcher_->ActivateThreadWatching();
// Check for IO thread to perform ping/pong messaging.
io_watcher_->ActivateThreadWatching();
// Verify UI thread is responding with ping/pong messaging.
ui_watcher_->WaitForCheckResponse(
kUnresponsiveTime + TimeDelta::FromMinutes(1), SUCCESSFUL);
EXPECT_GT(ui_watcher_->ping_sent_, static_cast<uint64_t>(0));
EXPECT_GT(ui_watcher_->pong_received_, static_cast<uint64_t>(0));
EXPECT_GE(ui_watcher_->ping_sequence_number(), static_cast<uint64_t>(0));
EXPECT_GT(ui_watcher_->success_response_, 0);
EXPECT_EQ(ui_watcher_->failed_response_, 0);
// Verify IO thread is responding with ping/pong messaging.
io_watcher_->WaitForCheckResponse(
kUnresponsiveTime + TimeDelta::FromMinutes(1), SUCCESSFUL);
EXPECT_GT(io_watcher_->ping_sent_, static_cast<uint64_t>(0));
EXPECT_GT(io_watcher_->pong_received_, static_cast<uint64_t>(0));
EXPECT_GE(io_watcher_->ping_sequence_number(), static_cast<uint64_t>(0));
EXPECT_GT(io_watcher_->success_response_, 0);
EXPECT_EQ(io_watcher_->failed_response_, 0);
// DeActivate thread watching for shutdown.
io_watcher_->DeActivateThreadWatching();
ui_watcher_->DeActivateThreadWatching();
}
};
TEST_F(ThreadWatcherTestMultipleThreadsResponding, RunTest) {
RunTestOnWatchDogThread();
}
// Test watching of multiple threads with one of the threads not responding.
class ThreadWatcherTestMultipleThreadsNotResponding
: public ThreadWatcherTestOnWatchDogThread {
protected:
void TestBodyOnWatchDogThread() override {
// Simulate hanging of watched thread by making the watched thread wait for
// a very long time by posting a task on watched thread that keeps it busy.
// It is safe to use base::Unretained because test is waiting for the method
// to finish.
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&CustomThreadWatcher::VeryLongMethod,
base::Unretained(io_watcher_), kUnresponsiveTime * 10));
// Activate watching of UI thread.
ui_watcher_->ActivateThreadWatching();
// Activate watching of IO thread.
io_watcher_->ActivateThreadWatching();
// Verify UI thread is responding with ping/pong messaging.
ui_watcher_->WaitForCheckResponse(
kUnresponsiveTime + TimeDelta::FromMinutes(1), SUCCESSFUL);
EXPECT_GT(ui_watcher_->success_response_, 0);
EXPECT_EQ(ui_watcher_->failed_response_, 0);
// Verify IO thread is not responding for ping messages.
io_watcher_->WaitForCheckResponse(
kUnresponsiveTime + TimeDelta::FromMinutes(1), FAILED);
EXPECT_EQ(io_watcher_->success_response_, 0);
EXPECT_GT(io_watcher_->failed_response_, 0);
// DeActivate thread watching for shutdown.
io_watcher_->DeActivateThreadWatching();
ui_watcher_->DeActivateThreadWatching();
// Wait for the io_watcher_'s VeryLongMethod to finish.
io_watcher_->WaitForWaitStateChange(kUnresponsiveTime * 10, ALL_DONE);
}
};
TEST_F(ThreadWatcherTestMultipleThreadsNotResponding, RunTest) {
RunTestOnWatchDogThread();
}
class ThreadWatcherListTest : public ::testing::Test {
protected:
ThreadWatcherListTest()
: done_(&lock_),
state_available_(false),
has_thread_watcher_list_(false),
stopped_(false) {}
void ReadStateOnWatchDogThread() {
CHECK(WatchDogThread::CurrentlyOnWatchDogThread());
{
base::AutoLock auto_lock(lock_);
has_thread_watcher_list_ =
ThreadWatcherList::g_thread_watcher_list_ != nullptr;
stopped_ = ThreadWatcherList::g_stopped_;
state_available_ = true;
}
done_.Signal();
}
void CheckState(bool has_thread_watcher_list,
bool stopped,
const char* const msg) {
CHECK(!WatchDogThread::CurrentlyOnWatchDogThread());
{
base::AutoLock auto_lock(lock_);
state_available_ = false;
}
WatchDogThread::PostTask(
FROM_HERE,
base::BindRepeating(&ThreadWatcherListTest::ReadStateOnWatchDogThread,
base::Unretained(this)));
{
base::AutoLock auto_lock(lock_);
while (!state_available_)
done_.Wait();
EXPECT_EQ(has_thread_watcher_list, has_thread_watcher_list_) << msg;
EXPECT_EQ(stopped, stopped_) << msg;
}
}
content::TestBrowserThreadBundle thread_bundle_;
base::Lock lock_;
base::ConditionVariable done_;
bool state_available_;
bool has_thread_watcher_list_;
bool stopped_;
};
TEST_F(ThreadWatcherListTest, Restart) {
ThreadWatcherList::g_initialize_delay_seconds = 1;
std::unique_ptr<WatchDogThread> watchdog_thread_(new WatchDogThread());
watchdog_thread_->StartAndWaitForTesting();
// See http://crbug.com/347887.
// StartWatchingAll() will PostDelayedTask to create g_thread_watcher_list_,
// whilst StopWatchingAll() will just PostTask to destroy it.
// Ensure that when Stop is called, Start will NOT create
// g_thread_watcher_list_ later on.
ThreadWatcherList::StartWatchingAll(*base::CommandLine::ForCurrentProcess());
ThreadWatcherList::StopWatchingAll();
{
base::RunLoop run_loop;
base::PostDelayedTaskWithTraits(
FROM_HERE, {BrowserThread::UI}, run_loop.QuitWhenIdleClosure(),
base::TimeDelta::FromSeconds(
ThreadWatcherList::g_initialize_delay_seconds));
run_loop.Run();
}
CheckState(false /* has_thread_watcher_list */, true /* stopped */,
"Start / Stopped");
// Proceed with just |StartWatchingAll| and ensure it'll be started.
ThreadWatcherList::StartWatchingAll(*base::CommandLine::ForCurrentProcess());
{
base::RunLoop run_loop;
base::PostDelayedTaskWithTraits(
FROM_HERE, {BrowserThread::UI}, run_loop.QuitWhenIdleClosure(),
base::TimeDelta::FromSeconds(
ThreadWatcherList::g_initialize_delay_seconds + 1));
run_loop.Run();
}
CheckState(true /* has_thread_watcher_list */, false /* stopped */,
"Started");
// Finally, StopWatchingAll() must stop.
ThreadWatcherList::StopWatchingAll();
{
base::RunLoop run_loop;
base::PostDelayedTaskWithTraits(
FROM_HERE, {BrowserThread::UI}, run_loop.QuitWhenIdleClosure(),
base::TimeDelta::FromSeconds(
ThreadWatcherList::g_initialize_delay_seconds));
run_loop.Run();
}
CheckState(false /* has_thread_watcher_list */, true /* stopped */,
"Stopped");
}