blob: 9d55e59336fb6eaf56e83d266470da01cb5be0ec [file] [log] [blame]
// Copyright 2016 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 "base/win/wait_chain.h"
#include <memory>
#include <string>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/test/multiprocess_test.h"
#include "base/threading/simple_thread.h"
#include "base/win/win_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
namespace base {
namespace win {
namespace {
// Appends |handle| as a command line switch.
void AppendSwitchHandle(CommandLine* command_line,
StringPiece switch_name,
HANDLE handle) {
command_line->AppendSwitchASCII(switch_name.as_string(),
UintToString(HandleToUint32(handle)));
}
// Retrieves the |handle| associated to |switch_name| from the command line.
ScopedHandle GetSwitchValueHandle(CommandLine* command_line,
StringPiece switch_name) {
std::string switch_string =
command_line->GetSwitchValueASCII(switch_name.as_string());
unsigned int switch_uint = 0;
if (switch_string.empty() || !StringToUint(switch_string, &switch_uint)) {
DLOG(ERROR) << "Missing or invalid " << switch_name << " argument.";
return ScopedHandle();
}
return ScopedHandle(reinterpret_cast<HANDLE>(switch_uint));
}
// Helper function to create a mutex.
ScopedHandle CreateMutex(bool inheritable) {
SECURITY_ATTRIBUTES security_attributes = {sizeof(SECURITY_ATTRIBUTES),
nullptr, inheritable};
return ScopedHandle(::CreateMutex(&security_attributes, FALSE, NULL));
}
// Helper function to create an event.
ScopedHandle CreateEvent(bool inheritable) {
SECURITY_ATTRIBUTES security_attributes = {sizeof(SECURITY_ATTRIBUTES),
nullptr, inheritable};
return ScopedHandle(
::CreateEvent(&security_attributes, FALSE, FALSE, nullptr));
}
// Helper thread class that runs the callback then stops.
class SingleTaskThread : public SimpleThread {
public:
explicit SingleTaskThread(const Closure& task)
: SimpleThread("WaitChainTest SingleTaskThread"), task_(task) {}
void Run() override { task_.Run(); }
private:
Closure task_;
DISALLOW_COPY_AND_ASSIGN(SingleTaskThread);
};
// Helper thread to cause a deadlock by acquiring 2 mutexes in a given order.
class DeadlockThread : public SimpleThread {
public:
DeadlockThread(HANDLE mutex_1, HANDLE mutex_2)
: SimpleThread("WaitChainTest DeadlockThread"),
wait_event_(CreateEvent(false)),
mutex_acquired_event_(CreateEvent(false)),
mutex_1_(mutex_1),
mutex_2_(mutex_2) {}
void Run() override {
// Acquire the mutex then signal the main thread.
EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(mutex_1_, INFINITE));
EXPECT_TRUE(::SetEvent(mutex_acquired_event_.Get()));
// Wait until both threads are holding their mutex before trying to acquire
// the other one.
EXPECT_EQ(WAIT_OBJECT_0,
::WaitForSingleObject(wait_event_.Get(), INFINITE));
// To unblock the deadlock, one of the threads will get terminated (via
// TerminateThread()) without releasing the mutex. This causes the other
// thread to wake up with WAIT_ABANDONED.
EXPECT_EQ(WAIT_ABANDONED, ::WaitForSingleObject(mutex_2_, INFINITE));
}
// Blocks until a mutex is acquired.
void WaitForMutexAcquired() {
EXPECT_EQ(WAIT_OBJECT_0,
::WaitForSingleObject(mutex_acquired_event_.Get(), INFINITE));
}
// Signal the thread to acquire the second mutex.
void SignalToAcquireMutex() { EXPECT_TRUE(::SetEvent(wait_event_.Get())); }
// Terminates the thread.
bool Terminate() {
ScopedHandle thread_handle(::OpenThread(THREAD_TERMINATE, FALSE, tid()));
return ::TerminateThread(thread_handle.Get(), 0);
}
private:
ScopedHandle wait_event_;
ScopedHandle mutex_acquired_event_;
// The 2 mutex to acquire.
HANDLE mutex_1_;
HANDLE mutex_2_;
DISALLOW_COPY_AND_ASSIGN(DeadlockThread);
};
// Creates a thread that joins |thread_to_join| and then terminates when it
// finishes execution.
std::unique_ptr<SingleTaskThread> CreateJoiningThread(
SimpleThread* thread_to_join) {
std::unique_ptr<SingleTaskThread> thread(new SingleTaskThread(
Bind(&SimpleThread::Join, Unretained(thread_to_join))));
thread->Start();
return thread;
}
// Creates a thread that calls WaitForSingleObject() on the handle and then
// terminates when it unblocks.
std::unique_ptr<SingleTaskThread> CreateWaitingThread(HANDLE handle) {
std::unique_ptr<SingleTaskThread> thread(new SingleTaskThread(
Bind(IgnoreResult(&::WaitForSingleObject), handle, INFINITE)));
thread->Start();
return thread;
}
// Creates a thread that blocks on |mutex_2| after acquiring |mutex_1|.
std::unique_ptr<DeadlockThread> CreateDeadlockThread(HANDLE mutex_1,
HANDLE mutex_2) {
std::unique_ptr<DeadlockThread> thread(new DeadlockThread(mutex_1, mutex_2));
thread->Start();
// Wait until the first mutex is acquired before returning.
thread->WaitForMutexAcquired();
return thread;
}
// Child process to test the cross-process capability of the WCT api.
// This process will simulate a hang while holding a mutex that the parent
// process is waiting on.
MULTIPROCESS_TEST_MAIN(WaitChainTestProc) {
CommandLine* command_line = CommandLine::ForCurrentProcess();
ScopedHandle mutex = GetSwitchValueHandle(command_line, "mutex");
CHECK(mutex.IsValid());
ScopedHandle sync_event(GetSwitchValueHandle(command_line, "sync_event"));
CHECK(sync_event.IsValid());
// Acquire mutex.
CHECK(::WaitForSingleObject(mutex.Get(), INFINITE) == WAIT_OBJECT_0);
// Signal back to the parent process that the mutex is hold.
CHECK(::SetEvent(sync_event.Get()));
// Wait on a signal from the parent process before terminating.
CHECK(::WaitForSingleObject(sync_event.Get(), INFINITE) == WAIT_OBJECT_0);
return 0;
}
// Start a child process and passes the |mutex| and the |sync_event| to the
// command line.
Process StartChildProcess(HANDLE mutex, HANDLE sync_event) {
CommandLine command_line = GetMultiProcessTestChildBaseCommandLine();
AppendSwitchHandle(&command_line, "mutex", mutex);
AppendSwitchHandle(&command_line, "sync_event", sync_event);
LaunchOptions options;
options.handles_to_inherit.push_back(mutex);
options.handles_to_inherit.push_back(sync_event);
return SpawnMultiProcessTestChild("WaitChainTestProc", command_line, options);
}
// Returns true if the |wait_chain| is an alternating sequence of thread objects
// and synchronization objects.
bool WaitChainStructureIsCorrect(const WaitChainNodeVector& wait_chain) {
// Checks thread objects.
for (size_t i = 0; i < wait_chain.size(); i += 2) {
if (wait_chain[i].ObjectType != WctThreadType)
return false;
}
// Check synchronization objects.
for (size_t i = 1; i < wait_chain.size(); i += 2) {
if (wait_chain[i].ObjectType == WctThreadType)
return false;
}
return true;
}
// Returns true if the |wait_chain| goes through more than 1 process.
bool WaitChainIsCrossProcess(const WaitChainNodeVector& wait_chain) {
if (wait_chain.size() == 0)
return false;
// Just check that the process id changes somewhere in the chain.
// Note: ThreadObjects are every 2 nodes.
DWORD first_process = wait_chain[0].ThreadObject.ProcessId;
for (size_t i = 2; i < wait_chain.size(); i += 2) {
if (first_process != wait_chain[i].ThreadObject.ProcessId)
return true;
}
return false;
}
} // namespace
// Creates 2 threads that acquire their designated mutex and then try to
// acquire each others' mutex to cause a deadlock.
TEST(WaitChainTest, Deadlock) {
// 2 mutexes are needed to get a deadlock.
ScopedHandle mutex_1 = CreateMutex(false);
ASSERT_TRUE(mutex_1.IsValid());
ScopedHandle mutex_2 = CreateMutex(false);
ASSERT_TRUE(mutex_2.IsValid());
std::unique_ptr<DeadlockThread> deadlock_thread_1 =
CreateDeadlockThread(mutex_1.Get(), mutex_2.Get());
std::unique_ptr<DeadlockThread> deadlock_thread_2 =
CreateDeadlockThread(mutex_2.Get(), mutex_1.Get());
// Signal the threads to try to acquire the other mutex.
deadlock_thread_1->SignalToAcquireMutex();
deadlock_thread_2->SignalToAcquireMutex();
// Sleep to make sure the 2 threads got a chance to execute.
Sleep(10);
// Create a few waiting threads to get a longer wait chain.
std::unique_ptr<SingleTaskThread> waiting_thread_1 =
CreateJoiningThread(deadlock_thread_1.get());
std::unique_ptr<SingleTaskThread> waiting_thread_2 =
CreateJoiningThread(waiting_thread_1.get());
WaitChainNodeVector wait_chain;
bool is_deadlock;
ASSERT_TRUE(GetThreadWaitChain(waiting_thread_2->tid(), &wait_chain,
&is_deadlock, nullptr, nullptr));
EXPECT_EQ(9U, wait_chain.size());
EXPECT_TRUE(is_deadlock);
EXPECT_TRUE(WaitChainStructureIsCorrect(wait_chain));
EXPECT_FALSE(WaitChainIsCrossProcess(wait_chain));
ASSERT_TRUE(deadlock_thread_1->Terminate());
// The SimpleThread API expect Join() to be called before destruction.
deadlock_thread_2->Join();
waiting_thread_2->Join();
}
// Creates a child process that acquires a mutex and then blocks. A chain of
// threads then blocks on that mutex.
TEST(WaitChainTest, CrossProcess) {
ScopedHandle mutex = CreateMutex(true);
ASSERT_TRUE(mutex.IsValid());
ScopedHandle sync_event = CreateEvent(true);
ASSERT_TRUE(sync_event.IsValid());
Process child_process = StartChildProcess(mutex.Get(), sync_event.Get());
ASSERT_TRUE(child_process.IsValid());
// Wait for the child process to signal when it's holding the mutex.
EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(sync_event.Get(), INFINITE));
// Create a few waiting threads to get a longer wait chain.
std::unique_ptr<SingleTaskThread> waiting_thread_1 =
CreateWaitingThread(mutex.Get());
std::unique_ptr<SingleTaskThread> waiting_thread_2 =
CreateJoiningThread(waiting_thread_1.get());
std::unique_ptr<SingleTaskThread> waiting_thread_3 =
CreateJoiningThread(waiting_thread_2.get());
WaitChainNodeVector wait_chain;
bool is_deadlock;
ASSERT_TRUE(GetThreadWaitChain(waiting_thread_3->tid(), &wait_chain,
&is_deadlock, nullptr, nullptr));
EXPECT_EQ(7U, wait_chain.size());
EXPECT_FALSE(is_deadlock);
EXPECT_TRUE(WaitChainStructureIsCorrect(wait_chain));
EXPECT_TRUE(WaitChainIsCrossProcess(wait_chain));
// Unblock child process and wait for it to terminate.
ASSERT_TRUE(::SetEvent(sync_event.Get()));
ASSERT_TRUE(child_process.WaitForExit(nullptr));
// The SimpleThread API expect Join() to be called before destruction.
waiting_thread_3->Join();
}
} // namespace win
} // namespace base