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

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

Powered by Google App Engine
This is Rietveld 408576698