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

Unified Diff: base/win/wait_chain_unittest.cc

Issue 1834463002: Identify the hung thread using the Wait Chain Traversal API (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: BASE_EXPORT Created 4 years, 8 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « base/win/wait_chain.cc ('k') | chrome/browser/process_singleton_win.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: base/win/wait_chain_unittest.cc
diff --git a/base/win/wait_chain_unittest.cc b/base/win/wait_chain_unittest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..3362a1098a1a0397c21c920f97fb95d5ac298ef8
--- /dev/null
+++ b/base/win/wait_chain_unittest.cc
@@ -0,0 +1,318 @@
+// 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;
+ HandlesToInheritVector handle_vector;
+ handle_vector.push_back(mutex);
+ handle_vector.push_back(sync_event);
+ options.handles_to_inherit = &handle_vector;
+ 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));
+
+ 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));
+
+ 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
« no previous file with comments | « base/win/wait_chain.cc ('k') | chrome/browser/process_singleton_win.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698