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

Side by Side 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: Move wait chain to base and added test + fix nits 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 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/win/wait_chain.h"
6
7 #include <string>
8
9 #include "base/bind.h"
10 #include "base/command_line.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/test/multiprocess_test.h"
13 #include "base/threading/simple_thread.h"
14 #include "base/win/win_util.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 #include "testing/multiprocess_func_list.h"
17
18 namespace base {
19 namespace win {
20
21 namespace {
22
23 // Appends |handle| as a command line switch.
24 void AppendSwitchHandle(base::CommandLine* command_line,
25 std::string switch_name,
26 HANDLE handle) {
27 command_line->AppendSwitchASCII(
28 switch_name, base::UintToString(base::win::HandleToUint32(handle)));
29 }
30
31 // Retrieves the |handle| associated to |switch_name| from the command line.
32 ScopedHandle GetSwitchValueHandle(base::CommandLine* command_line,
33 std::string switch_name) {
Sigurður Ásgeirsson 2016/04/05 20:07:01 nit: StringPiece here avoids string construction a
Patrick Monette 2016/04/05 22:55:31 Unfortunatly AppendSwitchASCII takes in a std::str
Sigurður Ásgeirsson 2016/04/06 14:57:46 Ah, that sucks.
34 std::string switch_string = command_line->GetSwitchValueASCII(switch_name);
35 unsigned int switch_uint = 0;
36 if (switch_string.empty() ||
37 !base::StringToUint(switch_string, &switch_uint)) {
38 DLOG(ERROR) << "Missing or invalid " << switch_name << " argument.";
39 return ScopedHandle();
40 }
41 return ScopedHandle(reinterpret_cast<HANDLE>(switch_uint));
42 }
43
44 // Helper function to create a mutex.
45 ScopedHandle CreateMutex(bool inheritable) {
46 SECURITY_ATTRIBUTES security_attributes = {sizeof(SECURITY_ATTRIBUTES),
47 nullptr, inheritable};
48 return ScopedHandle(::CreateMutex(&security_attributes, FALSE, NULL));
49 }
50
51 // Helper function to create an event.
52 ScopedHandle CreateEvent(bool inheritable) {
53 SECURITY_ATTRIBUTES security_attributes = {sizeof(SECURITY_ATTRIBUTES),
54 nullptr, inheritable};
55 return ScopedHandle(
56 ::CreateEvent(&security_attributes, FALSE, FALSE, nullptr));
57 }
58
59 class SimpleThreadWithHandle : public SimpleThread {
60 public:
61 explicit SimpleThreadWithHandle(const std::string& name_prefix)
62 : SimpleThread(name_prefix) {}
63
64 // Returns a handle to the thread so that other threads can wait on it.
65 HANDLE get_thread_handle() {
66 if (!thread_handle_.Get()) {
67 thread_handle_.Set(
68 ::OpenThread(SYNCHRONIZE | THREAD_TERMINATE, FALSE, tid()));
69 }
70 return thread_handle_.Get();
71 }
72
73 private:
74 ScopedHandle thread_handle_;
75
76 DISALLOW_COPY_AND_ASSIGN(SimpleThreadWithHandle);
77 };
78
79 // Helper thread class that runs the callback then stops.
80 class SingleTaskThread : public SimpleThreadWithHandle {
81 public:
82 explicit SingleTaskThread(const Closure& task)
83 : SimpleThreadWithHandle("WaitChainTest SingleTaskThread"), task_(task) {}
84
85 void Run() override { task_.Run(); }
86
87 private:
88 Closure task_;
89
90 DISALLOW_COPY_AND_ASSIGN(SingleTaskThread);
91 };
92
93 // Helper thread to cause a deadlock by acquiring 2 mutex in a given order.
Sigurður Ásgeirsson 2016/04/05 20:07:01 nit: mutexes?
Patrick Monette 2016/04/05 22:55:31 Done.
94 class DeadlockThread : public SimpleThreadWithHandle {
95 public:
96 DeadlockThread(HANDLE mutex_1, HANDLE mutex_2)
97 : SimpleThreadWithHandle("WaitChainTest SingleTaskThread"),
98 wait_event_(CreateEvent(false)),
99 mutex_acquired_event_(CreateEvent(false)),
100 mutex_1_(mutex_1),
101 mutex_2_(mutex_2) {}
102
103 void Run() override {
104 // Acquire the mutex then signal the main thread.
105 EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(mutex_1_, INFINITE));
106 EXPECT_TRUE(::SetEvent(mutex_acquired_event_.Get()));
107
108 // Wait until both thread are holding their mutex before trying to acquire
Sigurður Ásgeirsson 2016/04/05 20:07:01 nit: both threads
Patrick Monette 2016/04/05 22:55:31 Done.
109 // the other one.
110 EXPECT_EQ(WAIT_OBJECT_0,
111 ::WaitForSingleObject(wait_event_.Get(), INFINITE));
112
113 // To unblock the deadlock, one of the thread will get terminated (via
Sigurður Ásgeirsson 2016/04/05 20:07:01 nit: thread->threads
Patrick Monette 2016/04/05 22:55:31 Done.
114 // TerminateThread()) without releasing the mutex. This causes the other
115 // thread to wake up with WAIT_ABANDONED.
116 EXPECT_EQ(WAIT_ABANDONED, ::WaitForSingleObject(mutex_2_, INFINITE));
117 }
118
119 // Blocks until a mutex is acquired.
120 void WaitForMutexAcquired() {
121 EXPECT_EQ(WAIT_OBJECT_0,
122 ::WaitForSingleObject(mutex_acquired_event_.Get(), INFINITE));
123 }
124
125 // Signal the thread to acquire the second mutex.
126 void SignalToAcquireMutex() { EXPECT_TRUE(::SetEvent(wait_event_.Get())); }
127
128 private:
129 ScopedHandle wait_event_;
130 ScopedHandle mutex_acquired_event_;
131
132 // The 2 mutex to acquire.
133 HANDLE mutex_1_;
134 HANDLE mutex_2_;
135
136 DISALLOW_COPY_AND_ASSIGN(DeadlockThread);
137 };
138
139 // Creates a thread that calls WaitForSingleObject() on the handle and then
140 // terminates when it unblocks.
141 scoped_ptr<SingleTaskThread> CreateWaitingThread(HANDLE handle) {
142 scoped_ptr<SingleTaskThread> thread(new SingleTaskThread(
143 Bind(base::IgnoreResult(&::WaitForSingleObject), handle, INFINITE)));
144 thread->Start();
145
146 return std::move(thread);
147 }
148
149 // Creates a thread that calls WaitForSingleObject() on the handle and then
150 // terminates when it unblocks.
151 scoped_ptr<DeadlockThread> CreateDeadlockThread(HANDLE mutex_1,
152 HANDLE mutex_2) {
153 scoped_ptr<DeadlockThread> thread(new DeadlockThread(mutex_1, mutex_2));
154 thread->Start();
155
156 // Wait until the first mutex is acquired before returning.
157 thread->WaitForMutexAcquired();
158
159 return std::move(thread);
160 }
161
162 // Child process to test the cross-process capability of the WCT api.
163 // This process will simulate a hang while holding a mutex that the parent
164 // process is waiting on.
165 MULTIPROCESS_TEST_MAIN(WaitChainTestProc) {
166 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
167
168 ScopedHandle mutex = GetSwitchValueHandle(command_line, "mutex");
169 CHECK(mutex.IsValid());
170
171 ScopedHandle sync_event(GetSwitchValueHandle(command_line, "sync_event"));
172 CHECK(sync_event.IsValid());
173
174 // Acquire mutex.
175 CHECK(::WaitForSingleObject(mutex.Get(), INFINITE) == WAIT_OBJECT_0);
176
177 // Signal back to the parent process that the mutex is hold.
178 CHECK(::SetEvent(sync_event.Get()));
179
180 // Wait on a signal from the parent process before terminating.
181 CHECK(::WaitForSingleObject(sync_event.Get(), INFINITE) == WAIT_OBJECT_0);
182
183 return 0;
184 }
185
186 // Start a child process and passes the |mutex| and the |sync_event| to the
187 // command line.
188 base::Process StartChildProcess(HANDLE mutex, HANDLE sync_event) {
189 CommandLine command_line = GetMultiProcessTestChildBaseCommandLine();
190
191 AppendSwitchHandle(&command_line, "mutex", mutex);
192 AppendSwitchHandle(&command_line, "sync_event", sync_event);
193
194 LaunchOptions options;
195 options.inherit_handles = true;
Sigurður Ásgeirsson 2016/04/05 20:07:02 nit: use the handle inheritance vector: https://co
Patrick Monette 2016/04/05 22:55:31 Huh, interesting. Done.
196 return SpawnMultiProcessTestChild("WaitChainTestProc", command_line, options);
197 }
198
199 // Returns true if the |wait_chain| goes through more than 1 process.
200 bool WaitChainIsCrossProcess(const WaitChain& wait_chain) {
201 if (wait_chain.size() == 0)
202 return false;
203
204 // Just check that the process id changes somewhere in the chain.
205 // Note: ThreadObjects are every 2 nodes.
206 DWORD first_process = wait_chain[0].ThreadObject.ProcessId;
207 for (size_t i = 2; i < wait_chain.size(); i += 2) {
208 if (first_process != wait_chain[i].ThreadObject.ProcessId)
Sigurður Ásgeirsson 2016/04/05 20:07:01 nit: EXPECT that the node type is as expected? may
Patrick Monette 2016/04/05 22:55:31 Done.
209 return true;
210 }
211 return false;
212 }
213
214 } // namespace
215
216 // Creates 2 threads that acquire their designated mutex and then try to
Sigurður Ásgeirsson 2016/04/05 20:07:01 love the quick description!
Patrick Monette 2016/04/05 22:55:31 :)
217 // acquire each others' mutex to cause a deadlock.
218 TEST(WaitChainTest, Deadlock) {
219 // 2 mutexes are needed to get a deadlock.
220 ScopedHandle mutex_1 = CreateMutex(false);
221 ASSERT_TRUE(mutex_1.IsValid());
222 ScopedHandle mutex_2 = CreateMutex(false);
223 ASSERT_TRUE(mutex_2.IsValid());
224
225 scoped_ptr<DeadlockThread> deadlock_thread_1 =
226 CreateDeadlockThread(mutex_1.Get(), mutex_2.Get());
227 scoped_ptr<DeadlockThread> deadlock_thread_2 =
228 CreateDeadlockThread(mutex_2.Get(), mutex_1.Get());
229
230 // Signal the threads to try to acquire the other mutex.
231 deadlock_thread_1->SignalToAcquireMutex();
232 deadlock_thread_2->SignalToAcquireMutex();
233 // Sleep to make sure the 2 threads got a chance to execute.
234 Sleep(10);
235
236 // Create a few waiting threads to get a longer wait chain.
237 scoped_ptr<SingleTaskThread> waiting_thread_1 =
238 CreateWaitingThread(deadlock_thread_1->get_thread_handle());
239 scoped_ptr<SingleTaskThread> waiting_thread_2 =
240 CreateWaitingThread(waiting_thread_1->get_thread_handle());
241
242 WaitChain wait_chain;
243 bool is_deadlock;
244 ASSERT_TRUE(
245 GetThreadWaitChain(waiting_thread_2->tid(), &wait_chain, &is_deadlock));
246
247 EXPECT_EQ(9, wait_chain.size());
248 EXPECT_TRUE(is_deadlock);
249 EXPECT_FALSE(WaitChainIsCrossProcess(wait_chain));
250
251 ASSERT_TRUE(::TerminateThread(deadlock_thread_1->get_thread_handle(), 0));
252 }
253
254 // Creates a child process that acquire a mutex and then blocks. A chain of
Sigurður Ásgeirsson 2016/04/05 20:07:01 nit: acquires
Patrick Monette 2016/04/05 22:55:31 Done.
255 // threads then blocks on that mutex.
256 TEST(WaitChainTest, CrossProcess) {
257 ScopedHandle mutex = CreateMutex(true);
258 ASSERT_TRUE(mutex.IsValid());
259 ScopedHandle sync_event = CreateEvent(true);
260 ASSERT_TRUE(sync_event.IsValid());
261
262 base::Process child_process =
263 StartChildProcess(mutex.Get(), sync_event.Get());
264 ASSERT_TRUE(child_process.IsValid());
265
266 // Wait for the child process to signal when it's holding the mutex.
267 EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(sync_event.Get(), INFINITE));
268
269 ::TerminateProcess(child_process.Handle(), 0);
Sigurður Ásgeirsson 2016/04/05 20:07:02 this looks out of place?
Patrick Monette 2016/04/05 22:55:31 Oops! It is!
270
271 // Create a few waiting threads to get a longer wait chain.
272 scoped_ptr<SingleTaskThread> waiting_thread_1 =
273 CreateWaitingThread(mutex.Get());
274 scoped_ptr<SingleTaskThread> waiting_thread_2 =
275 CreateWaitingThread(waiting_thread_1->get_thread_handle());
276 scoped_ptr<SingleTaskThread> waiting_thread_3 =
277 CreateWaitingThread(waiting_thread_2->get_thread_handle());
278
279 WaitChain wait_chain;
280 bool is_deadlock;
281 ASSERT_TRUE(
282 GetThreadWaitChain(waiting_thread_3->tid(), &wait_chain, &is_deadlock));
283
284 EXPECT_EQ(7, wait_chain.size());
285 EXPECT_FALSE(is_deadlock);
286 EXPECT_TRUE(WaitChainIsCrossProcess(wait_chain));
287
288 // Unblock child process and wait for it to terminate.
289 ASSERT_TRUE(::SetEvent(sync_event.Get()));
290 ASSERT_TRUE(child_process.WaitForExit(nullptr));
291 }
292
293 } // namespace win
294 } // namespace base
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698